From 9d54cd42d2806eba164de1b58f09e6e3eea93bbd Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Sat, 24 May 2025 17:37:22 +0200 Subject: [PATCH] Fix GH-18642: Signed integer overflow in ext/phar fseek The overflow checking code already existed, but didn't work because the math was done on signed numbers instead of unsigned numbers. In the process I also discovered a pre-existing issue that needs to be fixed (and seems that other stream wrappers can have this issue too). --- ext/phar/stream.c | 20 +++++++++++--------- ext/phar/tests/gh18642.phpt | 29 +++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 9 deletions(-) create mode 100644 ext/phar/tests/gh18642.phpt diff --git a/ext/phar/stream.c b/ext/phar/stream.c index b53d4297c4227..fee100cc31a10 100644 --- a/ext/phar/stream.c +++ b/ext/phar/stream.c @@ -405,7 +405,7 @@ static int phar_stream_seek(php_stream *stream, zend_off_t offset, int whence, z phar_entry_data *data = (phar_entry_data *)stream->abstract; phar_entry_info *entry; int res; - zend_off_t temp; + zend_ulong temp; if (data->internal_file->link) { entry = phar_get_link_source(data->internal_file); @@ -415,26 +415,28 @@ static int phar_stream_seek(php_stream *stream, zend_off_t offset, int whence, z switch (whence) { case SEEK_END : - temp = data->zero + entry->uncompressed_filesize + offset; + temp = (zend_ulong) data->zero + (zend_ulong) entry->uncompressed_filesize + (zend_ulong) offset; break; case SEEK_CUR : - temp = data->zero + data->position + offset; + temp = (zend_ulong) data->zero + (zend_ulong) data->position + (zend_ulong) offset; break; case SEEK_SET : - temp = data->zero + offset; + temp = (zend_ulong) data->zero + (zend_ulong) offset; break; default: temp = 0; } - if (temp > data->zero + (zend_off_t) entry->uncompressed_filesize) { - *newoffset = -1; + + zend_off_t temp_signed = (zend_off_t) temp; + if (temp_signed > data->zero + (zend_off_t) entry->uncompressed_filesize) { + *newoffset = -1; /* FIXME: this will invalidate the ZEND_ASSERT(stream->position >= 0); assertion in streams.c */ return -1; } - if (temp < data->zero) { - *newoffset = -1; + if (temp_signed < data->zero) { + *newoffset = -1; /* FIXME: this will invalidate the ZEND_ASSERT(stream->position >= 0); assertion in streams.c */ return -1; } - res = php_stream_seek(data->fp, temp, SEEK_SET); + res = php_stream_seek(data->fp, temp_signed, SEEK_SET); *newoffset = php_stream_tell(data->fp) - data->zero; data->position = *newoffset; return res; diff --git a/ext/phar/tests/gh18642.phpt b/ext/phar/tests/gh18642.phpt new file mode 100644 index 0000000000000..a6872f7a62d86 --- /dev/null +++ b/ext/phar/tests/gh18642.phpt @@ -0,0 +1,29 @@ +--TEST-- +GH-18642 (Signed integer overflow in ext/phar fseek) +--EXTENSIONS-- +phar +--INI-- +phar.require_hash=0 +--FILE-- +setInfoClass('MyFile'); +$f = $phar['a.php']; +var_dump($f->fseek(PHP_INT_MAX)); +var_dump($f->fseek(0)); +var_dump($f->fseek(PHP_INT_MIN, SEEK_END)); +var_dump($f->fseek(0, SEEK_SET)); +var_dump($f->fseek(1, SEEK_CUR)); +var_dump($f->fseek(PHP_INT_MAX, SEEK_CUR)); +?> +--EXPECT-- +int(-1) +int(0) +int(-1) +int(0) +int(0) +int(-1)