Commit 59f8e1d2653 for php

commit 59f8e1d2653c39f8f6e3ef9ada7dac8264c82512
Author: lacatoire <la.catoire@gmail.com>
Date:   Mon Oct 19 14:03:27 2026 +0200

    Fix use-after-free of CURLOPT_STDERR stream on cloned curl handle

    _php_setup_easy_copy_handlers() copies the write, read and write_header
    streams but not std_err. The clone never checks that stream is still
    open, so libcurl keeps writing to a FILE* freed when the stream is
    closed.

    Copy std_err like the other handlers.

    Closes GH-24427.

diff --git a/NEWS b/NEWS
index 91b1663e457..60b978e801d 100644
--- a/NEWS
+++ b/NEWS
@@ -36,6 +36,10 @@ PHP                                                                        NEWS
 - CLI:
   . Fixed bug GH-24697 (PHP_HTTP_PARSER_STRICT build option doesn't work).
     (ndossche, devnexen)
+
+- Curl:
+  . Fix use-after-free of CURLOPT_STDERR stream on cloned curl handle.
+    (lacatoire)

 - Date:
   . Fixed bug GH-20503 (Assertion failure with ext/date DateInterval property
diff --git a/ext/curl/interface.c b/ext/curl/interface.c
index 8aec63ca501..b369de7fc36 100644
--- a/ext/curl/interface.c
+++ b/ext/curl/interface.c
@@ -1305,6 +1305,10 @@ void _php_setup_easy_copy_handlers(php_curl *ch, php_curl *source)
 		Z_ADDREF(source->handlers.write_header->stream);
 	}
 	ch->handlers.write_header->stream = source->handlers.write_header->stream;
+	if (!Z_ISUNDEF(source->handlers.std_err)) {
+		Z_ADDREF(source->handlers.std_err);
+	}
+	ch->handlers.std_err = source->handlers.std_err;

 	ch->handlers.write->fp = source->handlers.write->fp;
 	ch->handlers.write_header->fp = source->handlers.write_header->fp;
diff --git a/ext/curl/tests/curl_copy_handle_stderr_uaf.phpt b/ext/curl/tests/curl_copy_handle_stderr_uaf.phpt
new file mode 100644
index 00000000000..9a46a27dfa0
--- /dev/null
+++ b/ext/curl/tests/curl_copy_handle_stderr_uaf.phpt
@@ -0,0 +1,38 @@
+--TEST--
+Cloning a curl handle keeps CURLOPT_STDERR tracked so a closed stream is detected on the clone too
+--EXTENSIONS--
+curl
+--FILE--
+<?php
+$srcfile = __DIR__ . '/curl_copy_handle_stderr_uaf_src.txt';
+file_put_contents($srcfile, "Test.");
+
+$stderrfile = __DIR__ . '/curl_copy_handle_stderr_uaf_stderr.txt';
+$stream = fopen($stderrfile, 'w+');
+
+$ch1 = curl_init();
+curl_setopt($ch1, CURLOPT_URL, "file://" . $srcfile);
+curl_setopt($ch1, CURLOPT_RETURNTRANSFER, 1);
+curl_setopt($ch1, CURLOPT_VERBOSE, true);
+curl_setopt($ch1, CURLOPT_STDERR, $stream);
+
+$ch2 = clone $ch1;
+
+fclose($stream);
+
+var_dump(curl_exec($ch1));
+var_dump(curl_exec($ch2));
+?>
+--CLEAN--
+<?php
+@unlink(__DIR__ . '/curl_copy_handle_stderr_uaf_src.txt');
+@unlink(__DIR__ . '/curl_copy_handle_stderr_uaf_stderr.txt');
+?>
+--EXPECTF--
+Warning: curl_exec(): CURLOPT_STDERR resource has gone away, resetting to stderr in %s on line %d
+%A
+string(5) "Test."
+
+Warning: curl_exec(): CURLOPT_STDERR resource has gone away, resetting to stderr in %s on line %d
+%A
+string(5) "Test."