From 4ca4585b2cd8218d2cdfaaf0311dc2003c536d8c Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Mon, 24 Aug 2026 12:45:45 -0400 Subject: [PATCH] [Session] Truncate session file after successful write The files save handler ftruncated the session file to 0 before writing, so a failed or short write returned FAILURE with the previous session data already destroyed. Write first and truncate the old tail to the new length only after the full buffer was written successfully. Sibling audit: PS_WRITE_FUNC/PS_UPDATE_FUNC callers and the read path are unaffected; the empty-write destroy path truncates identically. --- NEWS | 5 ++ ext/session/mod_files.c | 9 ++- .../session_write_failure_keeps_data.phpt | 60 +++++++++++++++++++ 3 files changed, 69 insertions(+), 5 deletions(-) create mode 100644 ext/session/tests/session_write_failure_keeps_data.phpt diff --git a/NEWS b/NEWS index 3346d38ea898..b999acc768fd 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,11 @@ PHP NEWS registrations are freed while still reachable from the cycle collector. (Ilia Alshanetsky) +- Session: + . Fixed session data loss in the files save handler when writing the session + file fails after it was already truncated. (Ilia Alshanetsky) + + 24 Sep 2026, PHP 8.4.26 diff --git a/ext/session/mod_files.c b/ext/session/mod_files.c index 74e77973405b..a751c8a4daf3 100644 --- a/ext/session/mod_files.c +++ b/ext/session/mod_files.c @@ -236,11 +236,6 @@ static zend_result ps_files_write(ps_files *data, zend_string *key, zend_string return FAILURE; } - /* Truncate file if the amount of new data is smaller than the existing data set. */ - if (ZSTR_LEN(val) < data->st_size) { - php_ignore_value(ftruncate(data->fd, 0)); - } - #ifdef HAVE_PWRITE n = pwrite(data->fd, ZSTR_VAL(val), ZSTR_LEN(val), 0); #else @@ -274,6 +269,10 @@ static zend_result ps_files_write(ps_files *data, zend_string *key, zend_string return FAILURE; } + if (ZSTR_LEN(val) < data->st_size) { + php_ignore_value(ftruncate(data->fd, ZSTR_LEN(val))); + } + return SUCCESS; } diff --git a/ext/session/tests/session_write_failure_keeps_data.phpt b/ext/session/tests/session_write_failure_keeps_data.phpt new file mode 100644 index 000000000000..a4033a095420 --- /dev/null +++ b/ext/session/tests/session_write_failure_keeps_data.phpt @@ -0,0 +1,60 @@ +--TEST-- +Session files handler must not truncate session file when write fails +--EXTENSIONS-- +session +posix +pcntl +--INI-- +error_reporting=E_ALL +display_errors=1 +session.use_strict_mode=0 +--FILE-- + 4096); + +pcntl_signal(SIGXFSZ, SIG_IGN); +var_dump(posix_setrlimit(POSIX_RLIMIT_FSIZE, 16, POSIX_RLIMIT_INFINITY)); + +session_start(); +$_SESSION['data'] = str_repeat('B', 4096); +@session_write_close(); + +clearstatcache(true); +$after = filesize($file); +var_dump($after); +var_dump($after === $before); + +ob_end_flush(); + +foreach (glob($dir . '/sess_*') as $f) { + @unlink($f); +} +@rmdir($dir); +echo "done\n"; +--EXPECT-- +bool(true) +bool(true) +int(8207) +bool(true) +done