Skip to content

Commit 5ce795b

Browse files
authored
Fix GH-20964: fseek() on php://memory with PHP_INT_MIN causes undefined behavior (#21433)
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 78c0865 commit 5ce795b

6 files changed

Lines changed: 75 additions & 6 deletions

File tree

ext/pdo_sqlite/pdo_sqlite.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ static int php_pdosqlite3_stream_seek(php_stream *stream, zend_off_t offset, int
203203
switch(whence) {
204204
case SEEK_CUR:
205205
if (offset < 0) {
206-
if (sqlite3_stream->position < (size_t)(-offset)) {
206+
if (sqlite3_stream->position < -(size_t)offset) {
207207
sqlite3_stream->position = 0;
208208
*newoffs = -1;
209209
return -1;
@@ -241,7 +241,7 @@ static int php_pdosqlite3_stream_seek(php_stream *stream, zend_off_t offset, int
241241
sqlite3_stream->position = sqlite3_stream->size;
242242
*newoffs = -1;
243243
return -1;
244-
} else if (sqlite3_stream->size < (size_t)(-offset)) {
244+
} else if (sqlite3_stream->size < -(size_t)offset) {
245245
sqlite3_stream->position = 0;
246246
*newoffs = -1;
247247
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
@@ -1158,7 +1158,7 @@ static int php_sqlite3_stream_seek(php_stream *stream, zend_off_t offset, int wh
11581158
switch(whence) {
11591159
case SEEK_CUR:
11601160
if (offset < 0) {
1161-
if (sqlite3_stream->position < (size_t)(-offset)) {
1161+
if (sqlite3_stream->position < -(size_t)offset) {
11621162
sqlite3_stream->position = 0;
11631163
*newoffs = -1;
11641164
return -1;
@@ -1199,7 +1199,7 @@ static int php_sqlite3_stream_seek(php_stream *stream, zend_off_t offset, int wh
11991199
sqlite3_stream->position = sqlite3_stream->size;
12001200
*newoffs = -1;
12011201
return -1;
1202-
} else if (sqlite3_stream->size < (size_t)(-offset)) {
1202+
} else if (sqlite3_stream->size < -(size_t)offset) {
12031203
sqlite3_stream->position = 0;
12041204
*newoffs = -1;
12051205
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
@@ -126,7 +126,7 @@ static int php_stream_memory_seek(php_stream *stream, zend_off_t offset, int whe
126126
switch(whence) {
127127
case SEEK_CUR:
128128
if (offset < 0) {
129-
if (ms->fpos < (size_t)(-offset)) {
129+
if (ms->fpos < -(size_t)offset) {
130130
ms->fpos = 0;
131131
*newoffs = -1;
132132
return -1;
@@ -163,7 +163,7 @@ static int php_stream_memory_seek(php_stream *stream, zend_off_t offset, int whe
163163
stream->eof = 0;
164164
stream->fatal_error = 0;
165165
return 0;
166-
} else if (ZSTR_LEN(ms->data) < (size_t)(-offset)) {
166+
} else if (ZSTR_LEN(ms->data) < -(size_t)offset) {
167167
ms->fpos = 0;
168168
*newoffs = -1;
169169
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)