[php-src] master: Merge branch 'PHP-8.5'
Ilia Alshanetsky <[email protected]> Fri, 24 Jul 2026 15:35:17 +0000
| Newsgroups | gmane.comp.php.cvs.general |
|---|---|
| Message-ID | <[email protected]> |
Author: Ilia Alshanetsky (iliaal)
Date: 2026-07-24T11:30:37-04:00
Commit: https://github.com/php/php-src/commit/e9ee4c0b9f5b6f6028187ab87236f2f5fee6fc65
Raw diff: https://github.com/php/php-src/commit/e9ee4c0b9f5b6f6028187ab87236f2f5fee6fc65.diff
Merge branch 'PHP-8.5'
* PHP-8.5:
Don't expose a freed stream resource to user filters
Changed paths:
A ext/standard/tests/filters/gh15836.phpt
M NEWS
M ext/standard/tests/filters/bug54350.phpt
M ext/standard/user_filters.c
Diff:
diff --git a/NEWS b/NEWS
index b2f7d0220a86..244ea8ea94f6 100644
--- a/NEWS
+++ b/NEWS
@@ -67,6 +67,8 @@ PHP NEWS
TransmitFile) for faster stream copying. (Jakub Zelenka, David Carlier)
. Fixed bug GH-22841 (php_stream_copy_to_stream_ex() drops progress
notifications when using the copy fast path). (David Carlier)
+ . Fixed bug GH-15836 (Use-after-free when a user stream filter accesses
+ $this->stream during the close flush). (iliaal)
16 Jul 2026, PHP 8.6.0alpha2
diff --git a/ext/standard/tests/filters/bug54350.phpt b/ext/standard/tests/filters/bug54350.phpt
index 046db0483a9a..ec3cd857f1a0 100644
--- a/ext/standard/tests/filters/bug54350.phpt
+++ b/ext/standard/tests/filters/bug54350.phpt
@@ -23,4 +23,4 @@ fwrite($fd, "foo");
?>
--EXPECTF--
Warning: fclose(): cannot close the provided stream, as it must not be manually closed in %s on line %d
-fclose(): Argument #1 ($stream) must be an open stream resource
+fclose(): Argument #1 ($stream) must be of type resource, null given
diff --git a/ext/standard/tests/filters/gh15836.phpt b/ext/standard/tests/filters/gh15836.phpt
new file mode 100644
index 000000000000..593b93dcdc70
--- /dev/null
+++ b/ext/standard/tests/filters/gh15836.phpt
@@ -0,0 +1,34 @@
+--TEST--
+GH-15836 (use-after-free when a user filter reads $this->stream during the close flush)
+--FILE--
+<?php
+class my_filter extends php_user_filter {
+ public static ?Throwable $e = null;
+ function filter($in, $out, &$consumed, $closing): int {
+ if ($closing) {
+ try {
+ stream_bucket_new($this->stream, "x");
+ } catch (TypeError $e) {
+ self::$e = $e;
+ }
+ }
+ return PSFS_PASS_ON;
+ }
+}
+var_dump(stream_filter_register("my_filter", "my_filter"));
+
+function run() {
+ $s = fopen("php://memory", "wb+");
+ stream_filter_append($s, "my_filter", STREAM_FILTER_WRITE);
+}
+run();
+
+echo my_filter::$e->getTraceAsString(), "\n";
+echo "done\n";
+?>
+--EXPECTF--
+bool(true)
+#0 %s(%d): stream_bucket_new(NULL, 'x')
+#1 %s(%d): my_filter->filter(Resource id #%d, Resource id #%d, 0, true)
+#2 {main}
+done
diff --git a/ext/standard/user_filters.c b/ext/standard/user_filters.c
index 986bbbd5f4d3..126b434713ad 100644
--- a/ext/standard/user_filters.c
+++ b/ext/standard/user_filters.c
@@ -143,7 +143,11 @@ static zend_result userfilter_assign_stream(php_stream *stream, zval *obj,
bool stream_property_exists = Z_OBJ_HT_P(obj)->has_property(Z_OBJ_P(obj), stream_name, ZEND_PROPERTY_EXISTS, NULL);
if (stream_property_exists) {
zval stream_zval;
- php_stream_to_zval(stream, &stream_zval);
+ if (EXPECTED(stream->res && stream->res->type >= 0)) {
+ php_stream_to_zval(stream, &stream_zval);
+ } else {
+ ZVAL_NULL(&stream_zval);
+ }
zend_update_property_ex(Z_OBJCE_P(obj), Z_OBJ_P(obj), stream_name, &stream_zval);
/* If property update threw an exception, skip filter execution */
if (EG(exception)) {