Skip to content

Commit e43198f

Browse files
committed
Fix undefined behavior in php_stream_memory_seek()
When offset is ZEND_LONG_MIN, (size_t)(-offset) triggers signed integer overflow which is undefined behavior. Replace with (size_t)0 - (size_t)offset to perform the negation in unsigned arithmetic, which is well-defined. Also adds a test to verify that seeking with PHP_INT_MIN does not crash and that the stream remains usable afterwards.
1 parent 77925b9 commit e43198f

2 files changed

Lines changed: 60 additions & 2 deletions

File tree

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
--TEST--
2+
php://memory and php://temp stream seek with PHP_INT_MIN does not trigger undefined behavior
3+
--FILE--
4+
<?php
5+
foreach (['php://memory', 'php://temp'] as $wrapper) {
6+
echo "=== $wrapper ===\n";
7+
8+
$stream = fopen($wrapper, 'r+');
9+
fwrite($stream, 'hello');
10+
11+
// SEEK_CUR with PHP_INT_MIN from middle of stream
12+
fseek($stream, 2, SEEK_SET);
13+
var_dump(fseek($stream, PHP_INT_MIN, SEEK_CUR));
14+
15+
// SEEK_CUR with PHP_INT_MIN from beginning of stream
16+
fseek($stream, 0, SEEK_SET);
17+
var_dump(fseek($stream, PHP_INT_MIN, SEEK_CUR));
18+
19+
// SEEK_END with PHP_INT_MIN
20+
var_dump(fseek($stream, PHP_INT_MIN, SEEK_END));
21+
22+
// Normal negative SEEK_CUR that should succeed
23+
fseek($stream, 4, SEEK_SET);
24+
var_dump(fseek($stream, -2, SEEK_CUR));
25+
var_dump(ftell($stream));
26+
27+
// Normal negative SEEK_END that should succeed
28+
var_dump(fseek($stream, -3, SEEK_END));
29+
var_dump(ftell($stream));
30+
31+
// Verify stream still works after all edge-case seeks
32+
fseek($stream, 0, SEEK_SET);
33+
var_dump(fread($stream, 5));
34+
35+
fclose($stream);
36+
}
37+
echo "Done\n";
38+
?>
39+
--EXPECT--
40+
=== php://memory ===
41+
int(-1)
42+
int(-1)
43+
int(-1)
44+
int(0)
45+
int(2)
46+
int(0)
47+
int(2)
48+
string(5) "hello"
49+
=== php://temp ===
50+
int(-1)
51+
int(-1)
52+
int(-1)
53+
int(0)
54+
int(2)
55+
int(0)
56+
int(2)
57+
string(5) "hello"
58+
Done

main/streams/memory.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ static int php_stream_memory_seek(php_stream *stream, zend_off_t offset, int whe
128128
switch(whence) {
129129
case SEEK_CUR:
130130
if (offset < 0) {
131-
if (ms->fpos < (size_t)(-offset)) {
131+
if (ms->fpos < (size_t)0 - (size_t)offset) {
132132
ms->fpos = 0;
133133
*newoffs = -1;
134134
return -1;
@@ -165,7 +165,7 @@ static int php_stream_memory_seek(php_stream *stream, zend_off_t offset, int whe
165165
stream->eof = 0;
166166
stream->fatal_error = 0;
167167
return 0;
168-
} else if (ZSTR_LEN(ms->data) < (size_t)(-offset)) {
168+
} else if (ZSTR_LEN(ms->data) < (size_t)0 - (size_t)offset) {
169169
ms->fpos = 0;
170170
*newoffs = -1;
171171
return -1;

0 commit comments

Comments
 (0)