fix(rpc): use constant-time comparison for cookie auth#10567
Open
dingledropper wants to merge 1 commit into
Open
fix(rpc): use constant-time comparison for cookie auth#10567dingledropper wants to merge 1 commit into
dingledropper wants to merge 1 commit into
Conversation
The previous `*passwd == self.0` is byte-wise with early-exit on the first mismatch, leaking the cookie one byte at a time via response timing. With cookie auth as the only gate covering all 64 RPC methods (including mining control), and `enable_cookie_auth: true` as the default whenever RPC is enabled, the timing oracle is reachable pre-auth from anyone with TCP access to the RPC port. Switch to `subtle::ConstantTimeEq` over the cookie byte slice. The length check is intentionally early-exit — only the cookie *contents* are secret, not its length, which is fixed by `Cookie::default`. `subtle` is already in `Cargo.lock` (v2.6.1) as a transitive dep, so this only adds a top-level declaration. CWE-208 (Observable Timing Discrepancy).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
Closes #10546.
Cookie::authenticateinzebra-rpc/src/server/cookie.rscompared the supplied password against the server cookie usingString == String, which lowers to amemcmp-style block-wise compare with early-exit on the first mismatching word. A network-side adversary observing response timing could recover the 44-character base64 cookie byte-by-byte.Cookie auth is the only gate covering all 64 RPC methods (including mining control), and
enable_cookie_auth: trueis the default whenever RPC is enabled, so the timing oracle is reachable pre-auth from anyone with TCP access to the RPC port.CWE-208 (Observable Timing Discrepancy).
Solution
Replace the comparison with
subtle::ConstantTimeEq, per @mpguerra's exact suggestion in #10546:The length short-circuit is intentional — only the cookie contents are secret, not its length, which is fixed at 44 bytes by
Cookie::default(base64 of 32 random bytes).This mirrors
zcashd'sTimingResistantEqualinsrc/httprpc.cpp(originally Bitcoin Core PR #6390, 2015).subtleas a direct depsubtlewas previously available transitively viabellman→sapling-crypto→zcash_primitives→zcash_proofs→zebra-consensus, but Rust's import-visibility rule requires direct declaration inCargo.tomlforuse subtle::ConstantTimeEq;to compile. Adding it to[workspace.dependencies]and consuming it viasubtle = { workspace = true }inzebra-rpc/Cargo.tomlfollows the workspace's existing dep-management convention; no version churn (already pinned to2.6.1inCargo.lockfrom the transitive chain).Test evidence
cargo fmt --all -- --check✓cargo build -p zebra-rpc✓cargo test -p zebra-rpc --lib server::cookie✓ (4 new tests pass: exact match, wrong content same length, wrong length, prefix-only match)cargo clippy -p zebra-rpc --all-targets -- -D warnings✓origin/main(commitd4cd662c).The new
cookie.rstest module (#[cfg(test)] mod tests) covers:authenticate_accepts_exact_match— sanity check.authenticate_rejects_wrong_content_same_length— three same-length-but-wrong guesses; this is the bug class — withoutct_eqthe compare would short-circuit on the first wrong byte.authenticate_rejects_wrong_length— shorter, longer, and empty guesses; verifies the length short-circuit.authenticate_rejects_prefix_only_match— regression guard for the timing-oracle class: a 43-byte prefix match with a different last byte must be rejected exactly like any other wrong guess.Context
zcashd'sTimingResistantEqual, Bitcoin Core PR Epic: Zebra Stable Release #6390.subtle::ConstantTimeEq::ct_eqis the standard Rust ecosystem primitive for this — already used byzcash_primitives,bellman,sapling-crypto, etc.CHANGELOG
Updated
CHANGELOG.mdunder[Unreleased]→ Security with the standard form referencing #10546.AI disclosure
Patch and PR description drafted with assistance from Claude (Anthropic), following @mpguerra's exact code snippet in #10546 plus the test-coverage shape ZF reviewers typically request. I reviewed each step and am the responsible author.