From fec5f29727bec561d56f4e1081dd462f57ec37e9 Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Mon, 24 Aug 2026 11:16:48 -0400 Subject: [PATCH] [standard] Fix crash when filter callback unsets StreamBucket::$data stream_bucket_prepend()/append() assumed a successful zend_read_property() of StreamBucket::$data always yields a string, but for an unset typed property it throws while returning &EG(uninitialized_zval), so Z_STRLEN_P() dereferenced a NULL string pointer and crashed when the brigade was consumed. Reject non-string reads up front (rethrowing any pending exception) so the bucket is never re-attached with undefined data; the sibling $bucket property path is already safe because zend_fetch_resource_ex() rejects non-resources. Closes GH-23466 --- NEWS | 2 ++ .../tests/filters/bucket_data_unset.phpt | 27 +++++++++++++++++++ ext/standard/user_filters.c | 4 +++ 3 files changed, 33 insertions(+) create mode 100644 ext/standard/tests/filters/bucket_data_unset.phpt diff --git a/NEWS b/NEWS index e56e562a4c92..de79d329795d 100644 --- a/NEWS +++ b/NEWS @@ -48,6 +48,8 @@ PHP NEWS with no other live PDO handle. (iliaal) - Standard: + . Fixed a segfault when a stream filter callback unsets StreamBucket::$data + before re-attaching the bucket. (iliaal) . Fixed a memory leak in array_merge_recursive() when the recursive merge of an object converted to an array fails. (David Carlier) diff --git a/ext/standard/tests/filters/bucket_data_unset.phpt b/ext/standard/tests/filters/bucket_data_unset.phpt new file mode 100644 index 000000000000..043ea5953e92 --- /dev/null +++ b/ext/standard/tests/filters/bucket_data_unset.phpt @@ -0,0 +1,27 @@ +--TEST-- +unset(StreamBucket::$data) in filter callback must not crash when bucket is re-attached +--FILE-- +data); + stream_bucket_prepend($out, $bucket); + } + return PSFS_PASS_ON; + } +} +stream_filter_register("myfilter", "MyFilter"); +$fp = fopen("php://temp", "w+"); +fwrite($fp, str_repeat("A", 100)); +rewind($fp); +stream_filter_append($fp, "myfilter"); +try { + var_dump(stream_get_contents($fp)); +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; +} +echo "DONE\n"; +--EXPECT-- +Error: Typed property StreamBucket::$data must not be accessed before initialization +DONE diff --git a/ext/standard/user_filters.c b/ext/standard/user_filters.c index 735dd8390de8..29e1c3704ba5 100644 --- a/ext/standard/user_filters.c +++ b/ext/standard/user_filters.c @@ -424,6 +424,10 @@ static void php_stream_bucket_attach(int append, INTERNAL_FUNCTION_PARAMETERS) if (NULL != (pzdata = zend_read_property(NULL, Z_OBJ_P(zobject), "data", sizeof("data")-1, false, &rv))) { ZVAL_DEREF(pzdata); + if (EG(exception)) { + RETURN_THROWS(); + } + ZEND_ASSERT(Z_TYPE_P(pzdata) == IS_STRING); if (!bucket->own_buf) { bucket = php_stream_bucket_make_writeable(bucket); }