From 43d899a1cd164713fef79e454e1495cab60cbe7a Mon Sep 17 00:00:00 2001 From: Levi Morrison Date: Fri, 25 Sep 2026 14:53:03 -0600 Subject: [PATCH 1/2] Add persistent stream context shutdown reproducer --- NEWS | 1 + .../persistent_stream_context_shutdown.phpt | 62 +++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 ext/standard/tests/streams/persistent_stream_context_shutdown.phpt diff --git a/NEWS b/NEWS index 8d34aad3fe67..b3cc78a5785b 100644 --- a/NEWS +++ b/NEWS @@ -105,6 +105,7 @@ PHP NEWS . Fixed three Windows-only proc_open() defects: an uninitialized PROCESS_INFORMATION, an indeterminate comspec pointer after a failed lookup, and an unchecked CreateFileA() failure. (Ilia Alshanetsky) + . Fix persistent stream context lifetime during shutdown (Levi Morrison) - XSL: . Fixed bug GH-23730 (use-after-free when XSLTProcessor::importStylesheet() diff --git a/ext/standard/tests/streams/persistent_stream_context_shutdown.phpt b/ext/standard/tests/streams/persistent_stream_context_shutdown.phpt new file mode 100644 index 000000000000..d1d32e8ce2a9 --- /dev/null +++ b/ext/standard/tests/streams/persistent_stream_context_shutdown.phpt @@ -0,0 +1,62 @@ +--TEST-- +Persistent stream contexts created during resource shutdown are detached +--FILE-- + ['pipe', 'r'], + 1 => ['pipe', 'w'], + 2 => ['pipe', 'w'], + ], + $pipes, + ); + fclose($pipes[0]); + stream_get_contents($pipes[1]); + fclose($pipes[1]); + stream_get_contents($pipes[2]); + fclose($pipes[2]); + var_dump(proc_close($process)); + return; +} + +final class LateContextWrapper +{ + public $context; + + public function stream_open($path, $mode, $options, &$opened_path): bool + { + return true; + } + + public function stream_close(): void + { + $context = stream_context_create([ + 'socket' => ['tcp_nodelay' => true], + ]); + $GLOBALS['late_persistent_client'] = stream_socket_client( + $GLOBALS['late_context_address'], + $errno, + $error, + 1, + STREAM_CLIENT_CONNECT | STREAM_CLIENT_PERSISTENT, + $context, + ); + } +} + +$server = stream_socket_server('tcp://127.0.0.1:0', $errno, $error); +if (!$server) { + die("server failed: $error ($errno)\n"); +} + +$GLOBALS['late_context_address'] = 'tcp://' . stream_socket_get_name($server, false); +stream_wrapper_register('late-context', LateContextWrapper::class); +$trigger = fopen('late-context://trigger', 'r'); + +?> +--EXPECT-- +int(0) From c5536b339c2a9f3519491a54a727416a58bfb2a2 Mon Sep 17 00:00:00 2001 From: Levi Morrison Date: Fri, 25 Sep 2026 16:37:05 -0600 Subject: [PATCH 2/2] Clear persistent stream request pointers after shutdown --- ext/standard/basic_functions.c | 5 +++-- main/php_streams.h | 2 +- main/streams/streams.c | 9 +++------ 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/ext/standard/basic_functions.c b/ext/standard/basic_functions.c index a1bf86d0bcfa..ae1a1ef8666f 100644 --- a/ext/standard/basic_functions.c +++ b/ext/standard/basic_functions.c @@ -157,7 +157,9 @@ zend_module_entry basic_functions_module = { /* {{{ */ PHP_RSHUTDOWN(basic), /* request shutdown */ PHP_MINFO(basic), /* extension info */ PHP_STANDARD_VERSION, /* extension version */ - STANDARD_MODULE_PROPERTIES + NO_MODULE_GLOBALS, + ZEND_MODULE_POST_ZEND_DEACTIVATE_N(streams), + STANDARD_MODULE_PROPERTIES_EX }; /* }}} */ @@ -463,7 +465,6 @@ PHP_RSHUTDOWN_FUNCTION(basic) /* {{{ */ #endif BASIC_RSHUTDOWN_SUBMODULE(assert) BASIC_RSHUTDOWN_SUBMODULE(url_scanner_ex) - BASIC_RSHUTDOWN_SUBMODULE(streams) #ifdef PHP_WIN32 BASIC_RSHUTDOWN_SUBMODULE(win32_core_globals) #endif diff --git a/main/php_streams.h b/main/php_streams.h index 1c4141c939f1..fc1585ed7139 100644 --- a/main/php_streams.h +++ b/main/php_streams.h @@ -586,7 +586,7 @@ END_EXTERN_C() int php_init_stream_wrappers(int module_number); void php_shutdown_stream_wrappers(int module_number); void php_shutdown_stream_hashes(void); -PHP_RSHUTDOWN_FUNCTION(streams); +ZEND_MODULE_POST_ZEND_DEACTIVATE_D(streams); BEGIN_EXTERN_C() PHPAPI zend_result php_register_url_stream_wrapper(const char *protocol, const php_stream_wrapper *wrapper); diff --git a/main/streams/streams.c b/main/streams/streams.c index 368de1a64774..a37546a4ad2b 100644 --- a/main/streams/streams.c +++ b/main/streams/streams.c @@ -79,17 +79,14 @@ static int forget_persistent_resource_id_numbers(zval *el) fprintf(stderr, "forget_persistent: %s:%p\n", stream->ops->label, stream); #endif + /* Request resources have been destroyed; clear their stale pointers. */ stream->res = NULL; - - if (stream->ctx) { - zend_list_delete(stream->ctx); - stream->ctx = NULL; - } + stream->ctx = NULL; return 0; } -PHP_RSHUTDOWN_FUNCTION(streams) +ZEND_MODULE_POST_ZEND_DEACTIVATE_D(streams) { zval *el;