From ad49f5d1d22388348b2da4697ace4e8b77049352 Mon Sep 17 00:00:00 2001 From: Tianning Li Date: Mon, 11 May 2026 19:27:32 -0400 Subject: [PATCH] feat(rand): add DD_TRACE_SECURE_RANDOM support to span ID generation When DD_TRACE_SECURE_RANDOM=true, ddtrace_generate_span_id() bypasses the MT19937-64 thread-local state and calls php_random_bytes_silent() instead, which reads from the OS entropy pool (getrandom(2)) on every invocation with no userspace PRNG state. This ensures span IDs are drawn from the kernel entropy pool on every call, making them safe in process-snapshot environments where PRNG state seeded at startup would be identical across all resumed instances. The existing MT path and ddtrace_seed_prng() RINIT seeding are unchanged; the 128-bit trace ID .time component is a Unix timestamp and requires no fix. Tests: secure_random_generates_nonzero_ids.phpt verifies non-zero distinct IDs under the CSPRNG path; secure_random_ignores_prng_seed.phpt verifies that a fixed DD_TRACE_DEBUG_PRNG_SEED does not constrain output when DD_TRACE_SECURE_RANDOM=true. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- ext/configuration.h | 1 + ext/random.c | 10 ++++- .../secure_random_generates_nonzero_ids.phpt | 29 ++++++++++++++ .../ext/secure_random_ignores_prng_seed.phpt | 38 +++++++++++++++++++ 4 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 tests/ext/secure_random_generates_nonzero_ids.phpt create mode 100644 tests/ext/secure_random_ignores_prng_seed.phpt diff --git a/ext/configuration.h b/ext/configuration.h index c5f271b1efd..1e1920f85f3 100644 --- a/ext/configuration.h +++ b/ext/configuration.h @@ -191,6 +191,7 @@ enum ddtrace_sidecar_connection_mode { CONFIG(INT, DD_TRACE_AGENT_CONNECT_TIMEOUT, DD_CFG_EXPSTR(DD_TRACE_AGENT_CONNECT_TIMEOUT_VAL), \ .ini_change = zai_config_system_ini_change) \ CONFIG(INT, DD_TRACE_DEBUG_PRNG_SEED, "-1", .ini_change = ddtrace_reseed_seed_change) \ + CONFIG(BOOL, DD_TRACE_SECURE_RANDOM, "false") \ CONFIG(BOOL, DD_LOG_BACKTRACE, "false") \ CONFIG(BOOL, DD_CRASHTRACKING_ENABLED, DD_CRASHTRACKING_ENABLED_DEFAULT) \ CONFIG(BOOL, DD_TRACE_GENERATE_ROOT_SPAN, "true", .ini_change = ddtrace_span_alter_root_span_config) \ diff --git a/ext/random.c b/ext/random.c index 9ddbdcd0960..cd15c22b1b8 100644 --- a/ext/random.c +++ b/ext/random.c @@ -106,7 +106,15 @@ uint64_t ddtrace_parse_hex_span_id(zval *zid) { return ddtrace_parse_hex_span_id_str(Z_STRVAL_P(zid), Z_STRLEN_P(zid)); } -uint64_t ddtrace_generate_span_id(void) { return (uint64_t)genrand64_int64(); } +uint64_t ddtrace_generate_span_id(void) { + if (get_DD_TRACE_SECURE_RANDOM()) { + uint64_t id; + if (php_random_bytes_silent(&id, sizeof(id)) == SUCCESS) { + return id; + } + } + return (uint64_t)genrand64_int64(); +} uint64_t ddtrace_peek_span_id(void) { ddtrace_span_properties *pspan = DDTRACE_G(active_stack) ? DDTRACE_G(active_stack)->active : NULL; diff --git a/tests/ext/secure_random_generates_nonzero_ids.phpt b/tests/ext/secure_random_generates_nonzero_ids.phpt new file mode 100644 index 00000000000..010c5745c72 --- /dev/null +++ b/tests/ext/secure_random_generates_nonzero_ids.phpt @@ -0,0 +1,29 @@ +--TEST-- +DD_TRACE_SECURE_RANDOM generates valid non-zero span IDs +--ENV-- +DD_TRACE_GENERATE_ROOT_SPAN=false +DD_TRACE_DEBUG=false +DD_TRACE_SECURE_RANDOM=true +--FILE-- +id; +DDTrace\close_span(); + +DDTrace\start_span(); +$id2 = DDTrace\active_span()->id; +DDTrace\close_span(); + +// Both IDs must be non-empty numeric strings representing non-zero values. +var_dump(ctype_digit($id1) && $id1 !== '0'); +var_dump(ctype_digit($id2) && $id2 !== '0'); + +// Two consecutive IDs drawn from a CSPRNG must differ. +var_dump($id1 !== $id2); + +?> +--EXPECT-- +bool(true) +bool(true) +bool(true) diff --git a/tests/ext/secure_random_ignores_prng_seed.phpt b/tests/ext/secure_random_ignores_prng_seed.phpt new file mode 100644 index 00000000000..5f5420b8b7c --- /dev/null +++ b/tests/ext/secure_random_ignores_prng_seed.phpt @@ -0,0 +1,38 @@ +--TEST-- +DD_TRACE_SECURE_RANDOM bypasses the deterministic PRNG seed +--ENV-- +DD_TRACE_GENERATE_ROOT_SPAN=false +DD_TRACE_DEBUG=false +DD_TRACE_SECURE_RANDOM=true +DD_TRACE_DEBUG_PRNG_SEED=42 +--FILE-- +id; +DDTrace\close_span(); + +DDTrace\start_span(); +$id2 = DDTrace\active_span()->id; +DDTrace\close_span(); + +// IDs must be valid non-zero numeric strings. +var_dump(ctype_digit($id1) && $id1 !== '0'); +var_dump(ctype_digit($id2) && $id2 !== '0'); + +// CSPRNG output must not be equal across calls; this fails deterministically +// under the MT because seed 42 would produce the same first two values every run. +var_dump($id1 !== $id2); + +?> +--EXPECT-- +bool(true) +bool(true) +bool(true)