From fe7e8c50f389728483ee6649c2070a5838590d60 Mon Sep 17 00:00:00 2001 From: wadakatu Date: Thu, 28 May 2026 02:02:04 +0900 Subject: [PATCH] Fix GH-20921: Avoid signed negation of PHP_INT_MIN in memory stream seek In php_stream_memory_seek(), the SEEK_END handler computed `(size_t)(-offset)` to obtain the absolute value of a negative offset. When `offset` is `LONG_MIN` (PHP_INT_MIN), the unary minus is performed in signed `zend_off_t` and overflows, which is undefined behavior. UBSan reports: runtime error: negation of -9223372036854775808 cannot be represented in type 'zend_off_t' (aka 'long') Cast to unsigned first so that the negation happens in unsigned arithmetic (defined to wrap modulo 2^N), yielding the same absolute value without invoking UB. A matching pattern exists in the SEEK_CUR branch, but _php_stream_seek() converts SEEK_CUR to SEEK_SET before invoking the memory ops, so that path is unreachable via fseek() and is left for a separate cleanup. --- NEWS | 4 ++++ ext/standard/tests/streams/gh20921.phpt | 12 ++++++++++++ main/streams/memory.c | 2 +- 3 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 ext/standard/tests/streams/gh20921.phpt diff --git a/NEWS b/NEWS index 3899aea32cab..9036826ceb77 100644 --- a/NEWS +++ b/NEWS @@ -15,6 +15,10 @@ PHP NEWS IntlCalendar::equals(), ::before(), ::after(), and ::isEquivalentTo(). (Weilin Du) +- Streams: + . Fixed bug GH-20921 (UBSan: Negation of PHP_INT_MIN triggers runtime error + in streams/memory.c via SplTempFileObject::fseek). (wadakatu) + - Zlib: . Fixed memory leak if deflate initialization fails and there is a dict. (ndossche) diff --git a/ext/standard/tests/streams/gh20921.phpt b/ext/standard/tests/streams/gh20921.phpt new file mode 100644 index 000000000000..54cd72bb8e14 --- /dev/null +++ b/ext/standard/tests/streams/gh20921.phpt @@ -0,0 +1,12 @@ +--TEST-- +GH-20921: SplTempFileObject::fseek with PHP_INT_MIN must not trigger UB +--FILE-- +fwrite("abcdef"); + +var_dump($f->fseek(PHP_INT_MIN, SEEK_END)); + +?> +--EXPECT-- +int(-1) diff --git a/main/streams/memory.c b/main/streams/memory.c index cdc6c60bd97d..5d691e2afe7b 100644 --- a/main/streams/memory.c +++ b/main/streams/memory.c @@ -165,7 +165,7 @@ static int php_stream_memory_seek(php_stream *stream, zend_off_t offset, int whe stream->eof = 0; stream->fatal_error = 0; return 0; - } else if (ZSTR_LEN(ms->data) < (size_t)(-offset)) { + } else if (ZSTR_LEN(ms->data) < -(size_t)offset) { ms->fpos = 0; *newoffs = -1; return -1;