diff --git a/NEWS b/NEWS index 500f75cc050d..d7c0e9a1a2bf 100644 --- a/NEWS +++ b/NEWS @@ -34,6 +34,10 @@ PHP NEWS . Fixed read buffer compaction in php_stream_filter_flush(). (crystarm) . Io\Poll\Context::wait() now rejects a $maxEvents value greater than INT_MAX instead of truncating it. (marc-mabe) + . Fixed fseek() accepting $whence values that do not fit in an int, which + were silently truncated onto a valid seek constant. (lacatoire) + . Fixed a failed seek discarding the read buffer, which desynchronized the + stream from its reported position. (lacatoire) 27 Aug 2026, PHP 8.6.0beta2 diff --git a/ext/standard/file.c b/ext/standard/file.c index d6a8b9f1d0ea..c2b395b858de 100644 --- a/ext/standard/file.c +++ b/ext/standard/file.c @@ -1104,6 +1104,10 @@ PHPAPI PHP_FUNCTION(fseek) Z_PARAM_LONG(whence) ZEND_PARSE_PARAMETERS_END(); + if (whence < INT_MIN || whence > INT_MAX) { + RETURN_LONG(-1); + } + php_stream_error_operation_begin(); RETVAL_LONG(php_stream_seek(stream, offset, (int) whence)); php_stream_error_operation_end_for_stream(stream); diff --git a/ext/standard/tests/file/fseek_whence_invalid_inrange.phpt b/ext/standard/tests/file/fseek_whence_invalid_inrange.phpt new file mode 100644 index 000000000000..f0aba239b5f8 --- /dev/null +++ b/ext/standard/tests/file/fseek_whence_invalid_inrange.phpt @@ -0,0 +1,40 @@ +--TEST-- +fseek(): an invalid $whence that fits in an int must not desynchronize the stream +--FILE-- + +--CLEAN-- + +--EXPECT-- +whence=99 +string(4) "0123" +int(-1) +int(4) +string(6) "456789" + +whence=-2147483648 +string(4) "0123" +int(-1) +int(4) +string(6) "456789" + +whence=2147483647 +string(4) "0123" +int(-1) +int(4) +string(6) "456789" diff --git a/ext/standard/tests/file/fseek_whence_overflow.phpt b/ext/standard/tests/file/fseek_whence_overflow.phpt new file mode 100644 index 000000000000..760659dd9ee0 --- /dev/null +++ b/ext/standard/tests/file/fseek_whence_overflow.phpt @@ -0,0 +1,46 @@ +--TEST-- +fseek(): $whence values that overflow int must return -1, not alias onto a valid constant +--SKIPIF-- + +--FILE-- + +--EXPECT-- +int(-1) +int(4) +int(-1) +int(4) +int(-1) +int(4) +int(0) +int(7) diff --git a/main/streams/streams.c b/main/streams/streams.c index a09a2180921d..bbc5047ab357 100644 --- a/main/streams/streams.c +++ b/main/streams/streams.c @@ -1367,6 +1367,7 @@ PHPAPI int php_stream_seek(php_stream *stream, zend_off_t offset, int whence) if (stream->ops->seek && (stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0) { + zend_off_t old_position = stream->position; int ret; switch(whence) { case SEEK_CUR: @@ -1386,6 +1387,13 @@ PHPAPI int php_stream_seek(php_stream *stream, zend_off_t offset, int whence) ret = stream->ops->seek(stream, offset, whence, &stream->position); if (((stream->flags & PHP_STREAM_FLAG_NO_SEEK) == 0) || ret == 0) { + if (ret != 0 && stream->position == old_position) { + /* the seek failed without moving the stream, so the buffered + * data and the filter state still describe the current + * position and must be left alone */ + return ret; + } + if (ret == 0) { stream->eof = 0; stream->fatal_error = 0;