Skip to content

Commit 7a66ef9

Browse files
morrisonleviclaudebwoebi
authored
fix(tracing): intern process_tags.serialized to avoid ZTS refcount race (#3831)
* fix(tracing): treat process_tags.serialized as interned to avoid ZTS refcount race ext/process_tags.c stores a single process-wide zend_string at first-request init (zend_string_init persistent=1) and shares it across all ZTS threads. Every span serialization on a first-span path passes that pointer to Rust, which calls back into PHP's zend_string_addref/zend_string_release through ddog_init_span_func to manage a refcount on its Bytes wrapper. Those ops are non-atomic ++/-- on a single shared field; under FrankenPHP + PHP 8.4 ZTS with concurrent worker threads at load, the races produce torn refcount values, premature frees, and use-after-free reads of process_tags.serialized during convert_zend_to_bytes_string -> String::from_utf8_lossy, surfacing as SIGSEGV / SIGABRT / "memory allocation of <huge> bytes failed" / glibc "double free or corruption". Mark the string IS_STR_INTERNED (= GC_IMMUTABLE) on creation so the refcount helpers see it as immutable and skip the ++/--.Clear the flag in clear_process_tags before the release so the persistent allocation is actually freed at module shutdown. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * fix(tracing): precompute hash on process_tags.serialized before marking interned Interned zend_strings are expected to have h pre-populated (callers may read ZSTR_H(s) raw). zend_string_init leaves h zero, so setting IS_STR_INTERNED without computing the hash leaves any hash-keyed lookup broken. Force the compute via zend_string_hash_val() before adding the flag. Squashable with the previous fix commit. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]> * Remove unnecessary callback * Adjust comments --------- Co-authored-by: Claude Opus 4.7 (1M context) <[email protected]> Co-authored-by: Bob Weinand <[email protected]>
1 parent 96e825a commit 7a66ef9

1 file changed

Lines changed: 5 additions & 0 deletions

File tree

ext/process_tags.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ static void clear_process_tags(void) {
5555
}
5656

5757
if (process_tags.serialized) {
58+
// Counterpart to the IS_STR_INTERNED flag set in serialize_process_tags
59+
GC_DEL_FLAGS(process_tags.serialized, IS_STR_INTERNED);
5860
zend_string_release(process_tags.serialized);
5961
}
6062

@@ -255,6 +257,9 @@ static void serialize_process_tags(void) {
255257
if (buf.s) {
256258
smart_str_0(&buf);
257259
process_tags.serialized = zend_string_init(ZSTR_VAL(buf.s), ZSTR_LEN(buf.s), 1);
260+
// Intern to avoid races with string touched by refcounting; but just the flag, no need for the whole persisting ceremony
261+
zend_string_hash_val(process_tags.serialized);
262+
GC_ADD_FLAGS(process_tags.serialized, IS_STR_INTERNED);
258263
}
259264

260265
smart_str_free(&buf);

0 commit comments

Comments
 (0)