From 5682c31f68ad30a2240d8c487449f7c45a1d4e0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gustavo=20Andr=C3=A9=20dos=20Santos=20Lopes?= Date: Wed, 4 Mar 2026 15:54:18 +0000 Subject: [PATCH] Try to prevent dangling tracked_streams MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We sometimes find corrupt tracked_streams pointers: Crash stacktrace (abbreviated): zend_hash_str_find ← SIGSEGV here dd_php_stdiop_close_wrapper ← exec_integration.c:89 _php_stream_free zend_file_handle_dtor compile_filename dd_execute_php_file ← autoload triggered by end hook zai_sandbox_call / dd_uhook_end ← post-hook for Slim\App::__construct execute_ex / php_execute_script While the reason why this happens is not entirely clear, it's possible that the previous RSHUTDOWN was not completely executed. This can happen if there are bailouts during RSHUTDOWN. Fix: wrap the rshutdown tracked_streams iteration in zend_try/zend_catch so any bailout sets tracked_streams = NULL rather than leaving it dangling. Also guard rinit against a non-NULL tracked_streams at request startup. --- ext/integrations/exec_integration.c | 47 ++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/ext/integrations/exec_integration.c b/ext/integrations/exec_integration.c index 1e38bbaf8a1..edd425db5f9 100644 --- a/ext/integrations/exec_integration.c +++ b/ext/integrations/exec_integration.c @@ -5,6 +5,7 @@ #include #endif +#include #include #include @@ -298,7 +299,11 @@ void ddtrace_exec_handlers_rinit() { // OTOH ddtrace_exec_handlers_rshutdown is not called when ddtrace is // disabled because it needs to be called earlier on upon the real rshutodown - if (tracked_streams) { + if (PG(during_request_startup)) { + if (tracked_streams) { + LOG(WARN, "tracked_streams is non-NULL during request startup"); + } + } else { dd_exec_destroy_tracked_streams(); } @@ -307,21 +312,33 @@ void ddtrace_exec_handlers_rinit() { void ddtrace_exec_handlers_rshutdown() { if (tracked_streams) { - zend_ulong h; - zend_string *key; - zval *val; - ZEND_HASH_REVERSE_FOREACH_KEY_VAL(tracked_streams, h, key, val) { - (void)h; - (void)val; - php_stream *stream; - memcpy(&stream, ZSTR_VAL(key), sizeof stream); - // manually close the tracked stream on rshutdown in case they - // lived till the end of the request so we can finish the span - zend_list_close(stream->res); - } - ZEND_HASH_FOREACH_END(); + // generally, a bailout during rshutdown would prevent the rest of the + // shutdown from that extension from running. + // Here, we're force-closing streams (the keys of tracked_streams) and + // potentially destroying spans (the values of tracked_streams). + // There is at least one avenue for an error to escape: when executing + // onClose callbacks on spans, if there has already been a PHP timeout, + // the zai sandbox will let the error escape. + zend_try { + zend_ulong h; + zend_string *key; + zval *val; + ZEND_HASH_REVERSE_FOREACH_KEY_VAL(tracked_streams, h, key, val) { + (void)h; + (void)val; + php_stream *stream; + memcpy(&stream, ZSTR_VAL(key), sizeof stream); + // manually close the tracked stream on rshutdown in case they + // lived till the end of the request so we can finish the span + zend_list_close(stream->res); + } + ZEND_HASH_FOREACH_END(); - dd_exec_destroy_tracked_streams(); + dd_exec_destroy_tracked_streams(); + } zend_catch { + LOG(WARN, "Bailout during tracked_streams destruction"); + tracked_streams = NULL; + } zend_end_try(); } {