Commit 47bebf87a04 for php.net
commit 47bebf87a04485b3e7c5f22c84f5c6ae6004626d
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date: Mon Aug 24 11:16:48 2026 -0400
[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
diff --git a/NEWS b/NEWS
index 31021de0701..cf28751877f 100644
--- a/NEWS
+++ b/NEWS
@@ -61,6 +61,8 @@ PHP NEWS
(Weilin Du)
- Standard:
+ . Fixed a segfault when a stream filter callback unsets StreamBucket::$data
+ before re-attaching the bucket. (iliaal)
. Fixed an out-of-bounds read when following a redirect response with an
empty Location header. (iliaal)
. Fixed a memory leak in array_merge_recursive() when the recursive merge of
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 00000000000..043ea5953e9
--- /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--
+<?php
+class MyFilter extends php_user_filter {
+ public function filter($in, $out, &$consumed, bool $closing): int {
+ while ($bucket = stream_bucket_make_writeable($in)) {
+ unset($bucket->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 735dd8390de..f5e58041ca8 100644
--- a/ext/standard/user_filters.c
+++ b/ext/standard/user_filters.c
@@ -423,7 +423,11 @@ 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))) {
+ if (EG(exception)) {
+ RETURN_THROWS();
+ }
ZVAL_DEREF(pzdata);
+ ZEND_ASSERT(Z_TYPE_P(pzdata) == IS_STRING);
if (!bucket->own_buf) {
bucket = php_stream_bucket_make_writeable(bucket);
}