Commit a2011dbfc17 for php

commit a2011dbfc17f52ba4a15a3a1040992e3404e870f
Author: Edmond <1571649+edmonddantes@users.noreply.github.com>
Date:   Sat Nov 7 12:30:05 2026 +0100

    Fix use-after-free when pclose() closes a stream from its user filter

    fclose() refuses a stream marked PHP_STREAM_FLAG_NO_FCLOSE, which a user
    filter's callback sets on its stream; pclose() closed it anyway, freeing the
    stream and the filter under the running callback.

    Closes GH-24175

diff --git a/NEWS b/NEWS
index 713e6833d40..26abaf2f03a 100644
--- a/NEWS
+++ b/NEWS
@@ -28,6 +28,8 @@ PHP                                                                        NEWS
     callback). (Ilia Alshanetsky)
   . Fixed leaks and data loss when user filter leaves buckets on input
     brigade. (ndossche, Matthias Goergens, Jakub Zelenka)
+  . Fixed bug GH-24175 (Use-after-free when pclose() closes a stream from its
+    user filter). (Edmond)

 19 Nov 2026, PHP 8.4.28

diff --git a/ext/standard/file.c b/ext/standard/file.c
index 933747ef8cf..412af159f19 100644
--- a/ext/standard/file.c
+++ b/ext/standard/file.c
@@ -851,6 +851,11 @@ PHP_FUNCTION(pclose)

 	PHP_STREAM_FROM_ZVAL(stream, res);

+	if ((stream->flags & PHP_STREAM_FLAG_NO_FCLOSE) != 0) {
+		php_error_docref(NULL, E_WARNING, ZEND_LONG_FMT " is not a valid stream resource", stream->res->handle);
+		RETURN_LONG(-1);
+	}
+
 	FG(pclose_wait) = 1;
 	zend_list_close(stream->res);
 	FG(pclose_wait) = 0;
diff --git a/ext/standard/tests/filters/pclose_in_filter.phpt b/ext/standard/tests/filters/pclose_in_filter.phpt
new file mode 100644
index 00000000000..32c6183db3c
--- /dev/null
+++ b/ext/standard/tests/filters/pclose_in_filter.phpt
@@ -0,0 +1,28 @@
+--TEST--
+pclose() of the stream from its user filter's callback
+--FILE--
+<?php
+class Closer extends php_user_filter {
+    public function filter($in, $out, &$consumed, bool $closing): int {
+        global $fp;
+        if (!$closing) {
+            var_dump(pclose($fp));
+        }
+        while ($bucket = stream_bucket_make_writeable($in)) {
+            $consumed += $bucket->datalen;
+            stream_bucket_append($out, $bucket);
+        }
+        return PSFS_PASS_ON;
+    }
+}
+
+stream_filter_register('closer', 'Closer');
+$fp = fopen('php://memory', 'w+');
+stream_filter_append($fp, 'closer', STREAM_FILTER_WRITE);
+fwrite($fp, 'abc');
+var_dump(pclose($fp));
+?>
+--EXPECTF--
+Warning: pclose(): %d is not a valid stream resource in %s on line %d
+int(-1)
+int(0)