Skip to content

feat(rand): add DD_TRACE_SECURE_RANDOM support to span ID generation#3873

Draft
litianningdatadog wants to merge 1 commit into
DataDog:masterfrom
litianningdatadog:tianning.li/dd-trace-secure-random
Draft

feat(rand): add DD_TRACE_SECURE_RANDOM support to span ID generation#3873
litianningdatadog wants to merge 1 commit into
DataDog:masterfrom
litianningdatadog:tianning.li/dd-trace-secure-random

Conversation

@litianningdatadog
Copy link
Copy Markdown

@litianningdatadog litianningdatadog commented May 11, 2026

Tech Doc

Summary

  • Adds DD_TRACE_SECURE_RANDOM boolean config option (default false) to ext/configuration.h
  • When DD_TRACE_SECURE_RANDOM=true, ddtrace_generate_span_id() calls php_random_bytes_silent() — which reads from the OS entropy pool (getrandom(2)) per call — instead of the MT19937-64 PRNG
  • The existing MT path and ddtrace_seed_prng() RINIT seeding are fully preserved for non-secure-random deployments

Motivation

PHP's span ID generator uses MT19937-64, a Mersenne Twister whose
312-element state array lives as a static C variable in ddtrace.so.
The state is seeded at RINIT (per PHP request) from php_random_int_silent()
which is cryptographically secure, but the derived PRNG state is
captured verbatim in any process memory snapshot.

In process-snapshot environments, instances restored from the same
snapshot have identical MT state and thus produce the same span ID
sequence. When DD_TRACE_SECURE_RANDOM=true, the MT is bypassed
entirely: php_random_bytes_silent() calls getrandom(2) directly
on each invocation with no userspace PRNG state, so snapshot-restored
instances draw independent entropy immediately.

This flag is intended to be injected automatically by the serverless
init container process before spawning the user application.

Test plan

  • tests/ext/secure_random_generates_nonzero_ids.phpt — verifies non-zero,
    distinct IDs under the DD_TRACE_SECURE_RANDOM=true path
  • tests/ext/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 (proving the MT is fully bypassed)
  • Both tests pass on PHP 8.3 debug build

Compatibility

php_random_bytes_silent is available via existing conditional includes
in ext/random.cext/standard/php_random.h (PHP < 8.4) and
ext/random/php_random.h (PHP ≥ 8.4) — no new header dependencies.

🤖 Generated with Claude Code

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) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant