diff --git a/NEWS b/NEWS index 3346d38ea898..4410bfc0001a 100644 --- a/NEWS +++ b/NEWS @@ -7,6 +7,10 @@ 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..bf495d815dab 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,12 @@ 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))); + } + + data->st_size = ZSTR_LEN(val); + return SUCCESS; } diff --git a/ext/session/tests/session_files_repeated_write.phpt b/ext/session/tests/session_files_repeated_write.phpt new file mode 100644 index 000000000000..d6f3446ba27f --- /dev/null +++ b/ext/session/tests/session_files_repeated_write.phpt @@ -0,0 +1,54 @@ +--TEST-- +Session files handler truncates repeated writes to the current data length +--EXTENSIONS-- +session +--INI-- +session.save_handler=files +session.save_path={PWD} +session.use_cookies=0 +session.cache_limiter= +session.gc_probability=0 +--FILE-- +write($id, $value)); + } + session_abort(); + var_dump(file_get_contents($file)); +} +?> +--CLEAN-- + +--EXPECT-- +bool(true) +string(11) "longer data" +bool(true) +bool(true) +string(5) "short" +bool(true) +bool(true) +bool(true) +string(5) "SHORT" +bool(true) +bool(true) +bool(true) +bool(true) +string(16) "much longer data" +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) +string(0) "" 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..7ef38fccea18 --- /dev/null +++ b/ext/session/tests/session_write_failure_keeps_data.phpt @@ -0,0 +1,59 @@ +--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 +session.save_handler=files +session.serialize_handler=php +--FILE-- + +--CLEAN-- + +--EXPECT-- +Original contents: string(26) "data|s:13:"original data";" +Set file size limit to zero: bool(true) +Contents unchanged after failed write: bool(true)