Skip to content

Commit 0f6efeb

Browse files
committed
Merge branch 'PHP-8.4' into PHP-8.5
* PHP-8.4: ext/session: Truncate session file after successful write
2 parents 5828413 + c2b02f2 commit 0f6efeb

4 files changed

Lines changed: 123 additions & 5 deletions

File tree

‎NEWS‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ PHP NEWS
6767
. Fixed a crash in readline_info() when completion_append_character
6868
contains a non-ASCII byte. (Weilin Du)
6969

70+
- Session:
71+
. Fixed session data loss in the files save handler when writing the session
72+
file fails after it was already truncated. (Ilia Alshanetsky)
73+
7074
- Standard:
7175
. Fixed password_get_info() and password_needs_rehash() accepting malformed
7276
bcrypt costs. (Ilia Alshanetsky)

‎ext/session/mod_files.c‎

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -236,11 +236,6 @@ static zend_result ps_files_write(ps_files *data, zend_string *key, zend_string
236236
return FAILURE;
237237
}
238238

239-
/* Truncate file if the amount of new data is smaller than the existing data set. */
240-
if (ZSTR_LEN(val) < data->st_size) {
241-
php_ignore_value(ftruncate(data->fd, 0));
242-
}
243-
244239
#ifdef HAVE_PWRITE
245240
n = pwrite(data->fd, ZSTR_VAL(val), ZSTR_LEN(val), 0);
246241
#else
@@ -274,6 +269,12 @@ static zend_result ps_files_write(ps_files *data, zend_string *key, zend_string
274269
return FAILURE;
275270
}
276271

272+
if (ZSTR_LEN(val) < data->st_size) {
273+
php_ignore_value(ftruncate(data->fd, ZSTR_LEN(val)));
274+
}
275+
276+
data->st_size = ZSTR_LEN(val);
277+
277278
return SUCCESS;
278279
}
279280

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
--TEST--
2+
Session files handler truncates repeated writes to the current data length
3+
--EXTENSIONS--
4+
session
5+
--INI--
6+
session.save_handler=files
7+
session.save_path={PWD}
8+
session.use_cookies=0
9+
session.cache_limiter=
10+
session.gc_probability=0
11+
--FILE--
12+
<?php
13+
$id = 'session-files-repeated-write';
14+
$file = __DIR__ . '/sess_' . $id;
15+
$handler = new SessionHandler();
16+
session_set_save_handler($handler);
17+
$values = ['longer data', 'short', 'SHORT', 'much longer data', ''];
18+
19+
for ($count = 1; $count <= count($values); $count++) {
20+
file_put_contents($file, '');
21+
session_id($id);
22+
session_start();
23+
foreach (array_slice($values, 0, $count) as $value) {
24+
var_dump($handler->write($id, $value));
25+
}
26+
session_abort();
27+
var_dump(file_get_contents($file));
28+
}
29+
?>
30+
--CLEAN--
31+
<?php
32+
unlink(__DIR__ . '/sess_session-files-repeated-write');
33+
?>
34+
--EXPECT--
35+
bool(true)
36+
string(11) "longer data"
37+
bool(true)
38+
bool(true)
39+
string(5) "short"
40+
bool(true)
41+
bool(true)
42+
bool(true)
43+
string(5) "SHORT"
44+
bool(true)
45+
bool(true)
46+
bool(true)
47+
bool(true)
48+
string(16) "much longer data"
49+
bool(true)
50+
bool(true)
51+
bool(true)
52+
bool(true)
53+
bool(true)
54+
string(0) ""
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
--TEST--
2+
Session files handler must not truncate session file when write fails
3+
--EXTENSIONS--
4+
session
5+
posix
6+
pcntl
7+
--INI--
8+
error_reporting=E_ALL
9+
display_errors=1
10+
session.use_strict_mode=0
11+
session.save_handler=files
12+
session.serialize_handler=php
13+
--FILE--
14+
<?php
15+
$dir = __DIR__ . '/session_write_failure_keeps_data';
16+
mkdir($dir);
17+
ini_set('session.save_path', $dir);
18+
19+
$sid = 'write-failure';
20+
$file = $dir . '/sess_' . $sid;
21+
22+
ob_start();
23+
24+
session_id($sid);
25+
session_start();
26+
$_SESSION['data'] = 'original data';
27+
session_write_close();
28+
29+
$before = file_get_contents($file);
30+
echo "Original contents: ";
31+
var_dump($before);
32+
33+
// A zero file size limit makes the next write fail before any byte lands;
34+
// SIGXFSZ has to be ignored or the process is killed instead.
35+
pcntl_signal(SIGXFSZ, SIG_IGN);
36+
echo "Set file size limit to zero: ";
37+
var_dump(posix_setrlimit(POSIX_RLIMIT_FSIZE, 0, POSIX_RLIMIT_INFINITY));
38+
39+
session_start();
40+
$_SESSION['data'] = 'new';
41+
@session_write_close();
42+
43+
echo "Contents unchanged after failed write: ";
44+
var_dump(file_get_contents($file) === $before);
45+
46+
ob_end_flush();
47+
?>
48+
--CLEAN--
49+
<?php
50+
$dir = __DIR__ . '/session_write_failure_keeps_data';
51+
foreach (glob($dir . '/sess_*') as $f) {
52+
unlink($f);
53+
}
54+
rmdir($dir);
55+
?>
56+
--EXPECT--
57+
Original contents: string(26) "data|s:13:"original data";"
58+
Set file size limit to zero: bool(true)
59+
Contents unchanged after failed write: bool(true)

0 commit comments

Comments
 (0)