From 3b8e92b6cb4c49e37c2f48bdcf83896f066450dd Mon Sep 17 00:00:00 2001 From: Levi Morrison Date: Fri, 1 May 2026 09:32:35 -0600 Subject: [PATCH 1/2] test(tracing): SpanStack active reference corruption --- .../span_stack_active_reference.phpt | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 tests/ext/span_stack/span_stack_active_reference.phpt diff --git a/tests/ext/span_stack/span_stack_active_reference.phpt b/tests/ext/span_stack/span_stack_active_reference.phpt new file mode 100644 index 00000000000..2714300f561 --- /dev/null +++ b/tests/ext/span_stack/span_stack_active_reference.phpt @@ -0,0 +1,34 @@ +--TEST-- +SpanStack active references do not corrupt child span opening +--DESCRIPTION-- +DDTrace\SpanStack::$active is exposed as a normal PHP property, but the C span +stack also aliases the same zval slot as a raw ddtrace_span_properties pointer. +If userland acquires that property by reference, the slot becomes IS_REFERENCE. +Opening a later child span must not treat the zend_reference container as a +SpanData pointer while inheriting parent state. + +This reproduces the parent-service corruption path where the next span can crash +while reading or copying properties from what should be the active parent span. +--ENV-- +DD_TRACE_AUTO_FLUSH_ENABLED=0 +DD_TRACE_GENERATE_ROOT_SPAN=0 +DD_CODE_ORIGIN_FOR_SPANS_ENABLED=0 +--FILE-- +service = "parent-service"; +touch_ref(\DDTrace\active_stack()->active); +$child = \DDTrace\start_span(); + +var_dump($child->parent === $root); +var_dump($child->service); +echo "ok\n"; + +?> +--EXPECT-- +bool(true) +string(14) "parent-service" +ok From 189b253831911d1686390cde5a5e8c700acec00b Mon Sep 17 00:00:00 2001 From: Bob Weinand Date: Wed, 6 May 2026 17:06:58 +0200 Subject: [PATCH 2/2] Prevent BP_VAR_RW on SpanStack->active Signed-off-by: Bob Weinand --- ext/compatibility.h | 2 ++ ext/ddtrace.c | 33 +++++++++++++++++++ .../span_stack_active_reference.phpt | 2 +- 3 files changed, 36 insertions(+), 1 deletion(-) diff --git a/ext/compatibility.h b/ext/compatibility.h index 2530cb2ee61..9d66a40ff11 100644 --- a/ext/compatibility.h +++ b/ext/compatibility.h @@ -271,6 +271,8 @@ static inline HashTable *zend_new_array(uint32_t nSize) { ((zend_object*)((char*)(op_array) - sizeof(zend_object))) #define zend_std_read_dimension std_object_handlers.read_dimension +#define zend_std_get_property_ptr_ptr std_object_handlers.get_property_ptr_ptr +#define zend_std_read_property std_object_handlers.read_property // make ZEND_STRL work #undef zend_hash_str_update diff --git a/ext/ddtrace.c b/ext/ddtrace.c index 955fac9654a..9c79bc3799c 100644 --- a/ext/ddtrace.c +++ b/ext/ddtrace.c @@ -1224,6 +1224,37 @@ static zval *ddtrace_root_span_data_write(zend_object *object, zend_string *memb #endif } +#if PHP_VERSION_ID < 80000 +static zval *ddtrace_span_stack_read_property(zval *object, zval *member, int type, void **cache_slot, zval *rv) { + zend_string *prop_name = Z_TYPE_P(member) == IS_STRING ? Z_STR_P(member) : ZSTR_EMPTY_ALLOC(); + ddtrace_span_stack *stack = (ddtrace_span_stack *)Z_OBJ_P(object); +#else +static zval *ddtrace_span_stack_read_property(zend_object *object, zend_string *member, int type, void **cache_slot, zval *rv) { + zend_string *prop_name = member; + ddtrace_span_stack *stack = (ddtrace_span_stack *)object; +#endif + if ((type == BP_VAR_W || type == BP_VAR_RW || type == BP_VAR_UNSET) + && zend_string_equals_literal(prop_name, "active")) { + ZVAL_COPY(rv, &stack->property_active); + return rv; + } + return zend_std_read_property(object, member, type, cache_slot, rv); +} + +#if PHP_VERSION_ID < 80000 +static zval *ddtrace_span_stack_get_property_ptr_ptr(zval *object, zval *member, int type, void **cache_slot) { + zend_string *prop_name = Z_TYPE_P(member) == IS_STRING ? Z_STR_P(member) : ZSTR_EMPTY_ALLOC(); +#else +static zval *ddtrace_span_stack_get_property_ptr_ptr(zend_object *object, zend_string *member, int type, void **cache_slot) { + zend_string *prop_name = member; +#endif + if ((type == BP_VAR_W || type == BP_VAR_RW || type == BP_VAR_UNSET) + && zend_string_equals_literal(prop_name, "active")) { + return NULL; // prevent cache fill; read_property handles the copy + } + return zend_std_get_property_ptr_ptr(object, member, type, cache_slot); +} + #if PHP_VERSION_ID < 80000 #if PHP_VERSION_ID >= 70400 static zval *ddtrace_span_stack_readonly(zval *object, zval *member, zval *value, void **cache_slot) { @@ -1328,6 +1359,8 @@ static void dd_register_span_data_ce(void) { memcpy(&ddtrace_span_stack_handlers, &std_object_handlers, sizeof(zend_object_handlers)); ddtrace_span_stack_handlers.clone_obj = ddtrace_span_stack_clone_obj; ddtrace_span_stack_handlers.dtor_obj = ddtrace_span_stack_dtor_obj; + ddtrace_span_stack_handlers.read_property = ddtrace_span_stack_read_property; + ddtrace_span_stack_handlers.get_property_ptr_ptr = ddtrace_span_stack_get_property_ptr_ptr; ddtrace_span_stack_handlers.write_property = ddtrace_span_stack_readonly; } diff --git a/tests/ext/span_stack/span_stack_active_reference.phpt b/tests/ext/span_stack/span_stack_active_reference.phpt index 2714300f561..3bbc60d1c6f 100644 --- a/tests/ext/span_stack/span_stack_active_reference.phpt +++ b/tests/ext/span_stack/span_stack_active_reference.phpt @@ -16,7 +16,7 @@ DD_CODE_ORIGIN_FOR_SPANS_ENABLED=0 --FILE-- service = "parent-service";