Skip to content

Commit da3dad2

Browse files
committed
Fix GH-20964: fseek() on php://memory with PHP_INT_MIN causes undefined behavior
Negate after casting to unsigned instead of before, avoiding signed integer overflow when offset is ZEND_LONG_MIN. The same pattern existed in the pdo_sqlite and sqlite3 stream seek handlers. Closes GH-20964 Closes GH-20927
1 parent 92ba1e4 commit da3dad2

File tree

6 files changed

+75
-6
lines changed

6 files changed

+75
-6
lines changed

ext/pdo_sqlite/pdo_sqlite.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ static int php_pdosqlite3_stream_seek(php_stream *stream, zend_off_t offset, int
205205
switch(whence) {
206206
case SEEK_CUR:
207207
if (offset < 0) {
208-
if (sqlite3_stream->position < (size_t)(-offset)) {
208+
if (sqlite3_stream->position < -(size_t)offset) {
209209
sqlite3_stream->position = 0;
210210
*newoffs = -1;
211211
return -1;
@@ -243,7 +243,7 @@ static int php_pdosqlite3_stream_seek(php_stream *stream, zend_off_t offset, int
243243
sqlite3_stream->position = sqlite3_stream->size;
244244
*newoffs = -1;
245245
return -1;
246-
} else if (sqlite3_stream->size < (size_t)(-offset)) {
246+
} else if (sqlite3_stream->size < -(size_t)offset) {
247247
sqlite3_stream->position = 0;
248248
*newoffs = -1;
249249
return -1;

ext/pdo_sqlite/tests/gh20964.phpt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
--TEST--
2+
GH-20964 (fseek() on PDO SQLite blob stream with PHP_INT_MIN causes undefined behavior)
3+
--EXTENSIONS--
4+
pdo_sqlite
5+
--FILE--
6+
<?php
7+
$db = new Pdo\Sqlite('sqlite::memory:');
8+
9+
$db->exec('CREATE TABLE test (id INTEGER PRIMARY KEY, data BLOB)');
10+
$db->exec("INSERT INTO test (id, data) VALUES (1, 'hello')");
11+
12+
$stream = $db->openBlob('test', 'data', 1);
13+
14+
var_dump(fseek($stream, PHP_INT_MIN, SEEK_END));
15+
16+
rewind($stream);
17+
var_dump(fseek($stream, PHP_INT_MIN, SEEK_CUR));
18+
19+
fclose($stream);
20+
?>
21+
--EXPECT--
22+
int(-1)
23+
int(-1)

ext/sqlite3/sqlite3.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1131,7 +1131,7 @@ static int php_sqlite3_stream_seek(php_stream *stream, zend_off_t offset, int wh
11311131
switch(whence) {
11321132
case SEEK_CUR:
11331133
if (offset < 0) {
1134-
if (sqlite3_stream->position < (size_t)(-offset)) {
1134+
if (sqlite3_stream->position < -(size_t)offset) {
11351135
sqlite3_stream->position = 0;
11361136
*newoffs = -1;
11371137
return -1;
@@ -1172,7 +1172,7 @@ static int php_sqlite3_stream_seek(php_stream *stream, zend_off_t offset, int wh
11721172
sqlite3_stream->position = sqlite3_stream->size;
11731173
*newoffs = -1;
11741174
return -1;
1175-
} else if (sqlite3_stream->size < (size_t)(-offset)) {
1175+
} else if (sqlite3_stream->size < -(size_t)offset) {
11761176
sqlite3_stream->position = 0;
11771177
*newoffs = -1;
11781178
return -1;

ext/sqlite3/tests/gh20964.phpt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
--TEST--
2+
GH-20964 (fseek() on SQLite3 blob stream with PHP_INT_MIN causes undefined behavior)
3+
--EXTENSIONS--
4+
sqlite3
5+
--FILE--
6+
<?php
7+
require_once __DIR__ . '/new_db.inc';
8+
9+
$db->exec('CREATE TABLE test (id INTEGER PRIMARY KEY, data BLOB)');
10+
$db->exec("INSERT INTO test (id, data) VALUES (1, 'hello')");
11+
12+
$stream = $db->openBlob('test', 'data', 1);
13+
14+
var_dump(fseek($stream, PHP_INT_MIN, SEEK_END));
15+
16+
rewind($stream);
17+
var_dump(fseek($stream, PHP_INT_MIN, SEEK_CUR));
18+
19+
fclose($stream);
20+
$db->close();
21+
?>
22+
--EXPECT--
23+
int(-1)
24+
int(-1)

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)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)offset) {
169169
ms->fpos = 0;
170170
*newoffs = -1;
171171
return -1;

tests/basic/gh20964.phpt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
--TEST--
2+
GH-20964 (fseek() on php://memory with PHP_INT_MIN causes undefined behavior)
3+
--FILE--
4+
<?php
5+
$stream = fopen('php://memory', 'r+');
6+
fwrite($stream, 'hello');
7+
rewind($stream);
8+
9+
var_dump(fseek($stream, PHP_INT_MIN, SEEK_END));
10+
var_dump(ftell($stream));
11+
12+
rewind($stream);
13+
var_dump(fseek($stream, PHP_INT_MIN, SEEK_CUR));
14+
var_dump(ftell($stream));
15+
16+
fclose($stream);
17+
?>
18+
--EXPECT--
19+
int(-1)
20+
bool(false)
21+
int(-1)
22+
bool(false)

0 commit comments

Comments
 (0)