From 08b58278716800bab5667620be154b950959eb9a Mon Sep 17 00:00:00 2001 From: lacatoire Date: Mon, 19 Oct 2026 14:03:27 +0200 Subject: [PATCH] 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. --- ext/curl/interface.c | 4 ++ .../tests/curl_copy_handle_stderr_uaf.phpt | 38 +++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 ext/curl/tests/curl_copy_handle_stderr_uaf.phpt diff --git a/ext/curl/interface.c b/ext/curl/interface.c index 61b423220898..c9a5838a7394 100644 --- a/ext/curl/interface.c +++ b/ext/curl/interface.c @@ -1289,6 +1289,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 000000000000..9a46a27dfa0c --- /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-- + +--CLEAN-- + +--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."