From b73e7ccec1ca4c89f89aa943f3244ab590bde426 Mon Sep 17 00:00:00 2001 From: Levi Morrison Date: Thu, 5 Feb 2026 10:13:51 -0700 Subject: [PATCH] test(tracing): fake-closure hook teardown ASAN regression Add a (somewhat) minimal PHPT that installs HOOK_INSTANCE hooks on Reflection-created fake closures and removes the hook from the posthook (self-removal while unwinding). This reproduces deterministic ASAN SEGVs seen on PHP 8.1 debug-zts-asan in `zai_hook_install_address()` for me locally. Keep runtime noise low by disabling root spans, auto-flush, sidecar sending, telemetry, and opcache. --- ...move_fake_closure_teardown_asan_php81.phpt | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 tests/ext/sandbox/install_hook/self_remove_fake_closure_teardown_asan_php81.phpt diff --git a/tests/ext/sandbox/install_hook/self_remove_fake_closure_teardown_asan_php81.phpt b/tests/ext/sandbox/install_hook/self_remove_fake_closure_teardown_asan_php81.phpt new file mode 100644 index 00000000000..8fbc47fdc65 --- /dev/null +++ b/tests/ext/sandbox/install_hook/self_remove_fake_closure_teardown_asan_php81.phpt @@ -0,0 +1,75 @@ +--TEST-- +ASAN regression: self-removing HOOK_INSTANCE on fake closures +--DESCRIPTION-- +This test exists because we observed deterministic AddressSanitizer crashes on +PHP 8.1 debug-zts-asan when exercising hook teardown for "fake closures" +created via Reflection. + +The core pattern is: +- Create a fake closure from Reflection (user function, user method, and an + internal function). +- Install a `DDTrace\HOOK_INSTANCE` hook on that closure. +- In the posthook, immediately remove the hook (self-removal while unwinding + the call). + +On the affected builds, this can corrupt/invalidly access hook bookkeeping such +that the tracer later attempts to compute a hook address from a NULL/invalid +`zend_function`, leading to an ASAN SEGV in `zai_hook_install_address()`. +--SKIPIF-- + +--INI-- +; Keep the runtime as "quiet" as possible so failures are about the hook +; teardown path. +datadog.trace.generate_root_span=0 +datadog.trace.auto_flush_enabled=0 +datadog.trace.sidecar_trace_sender=0 +datadog.instrumentation_telemetry_enabled=Off +opcache.enable=0 +opcache.enable_cli=0 +--FILE-- +getClosure(), + (new ReflectionClass(K::class))->getMethod('bar')->getClosure(), + (new ReflectionFunction('intval'))->getClosure(), +]; + +foreach ($closures as $c) { + $id = null; + $id = DDTrace\install_hook( + $c, + null, + static function () use (&$id): void { + if ($id !== null) { + DDTrace\remove_hook($id); + $id = null; + } + }, + DDTrace\HOOK_INSTANCE + ); + + // Trigger the hook; the posthook removes itself during this call. + $c(1); + + // Encourage closure teardown paths between iterations. + unset($c); +} + +gc_collect_cycles(); +echo "ok\n"; +?> +--EXPECT-- +ok +