fix: Reject an inner node claimed at leaf depth in verifyProofPath - #7940
Draft
bthomee wants to merge 1 commit into
Draft
Conversation
Contributor
There was a problem hiding this comment.
Pull request overview
Hardens SHAMap proof-path verification against malformed network-supplied paths by rejecting an inner node claimed at leaf depth (preventing an out-of-bounds key read in selectBranch) and by binding the terminal leaf to the requested key (preventing leaf substitution when the hash chain alone matches).
Changes:
- Reject inner proof-path nodes at
depth >= kLeafDepthinSHAMap::verifyProofPathto avoid reading past the 32-byte key. - Verify the terminal leaf’s key matches
keyto tie the proof to the intended leaf. - Add regression tests covering (1) the legitimate 65-element deep path case and (2) the forged all-inner 65-element path rejection.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| src/tests/libxrpl/shamap/SHAMap.cpp | Adds tests asserting valid 65-element deep proof paths and rejecting forged all-inner paths at leaf depth. |
| src/libxrpl/shamap/SHAMapSync.cpp | Updates verifyProofPath to reject inner nodes at leaf depth and validate the terminal leaf key matches the requested key. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
bthomee
force-pushed
the
bthomee/shamap-stack-02-shamapnodeid-isprefixof
branch
from
August 4, 2026 00:12
ef6fda7 to
cc83dd3
Compare
bthomee
force-pushed
the
bthomee/shamap-stack-03-reject-inner-node-leaf-depth
branch
from
August 4, 2026 00:12
24baeb7 to
da20f92
Compare
A SHAMap has 65 levels, and nibbles run out at level 64: `selectBranch` indexes the key byte at `depth / 2`, so at depth 64 it reads byte 32 of a 32-byte key. Only the leaf terminating a path may sit at that depth, but proof path nodes come off the wire, so a peer could send 65 hash-chained inner nodes and drive that read past the end of the buffer. The existing length bound cannot be tightened to catch this: a path for two keys sharing all 63 leading nibbles legitimately holds 64 inner nodes plus a leaf, so 65 elements is valid. The claimed node type at the final depth is the thing to reject, using `>=` rather than `==` to match the convention every other `kLeafDepth` comparison in this codebase already follows. Reachable from `TMProofPathResponse` via `LedgerReplayMsgHandler`; confirmed under ASan with asserts compiled out that the unguarded read lands one byte past a 32-byte heap allocation. Also closes a second gap: nothing checked the terminal leaf's own key against `key`, so the hash chain alone let a peer substitute any leaf whose subtree hashes matched at every level above it. Pinned by tests: the 65-element path that must verify for both keys sharing the deep prefix, and the forged all-inner path that must not.
bthomee
force-pushed
the
bthomee/shamap-stack-02-shamapnodeid-isprefixof
branch
from
August 4, 2026 00:49
cc83dd3 to
bb0a62e
Compare
bthomee
force-pushed
the
bthomee/shamap-stack-03-reject-inner-node-leaf-depth
branch
from
August 4, 2026 00:49
da20f92 to
8af43fd
Compare
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.
Part 3/8 of a stack. Base: part 2 (
bthomee/shamap-stack-02-shamapnodeid-isprefixof).A SHAMap has 65 levels, and nibbles run out at level 64:
selectBranchindexesthe key byte at
depth / 2, so at depth 64 it reads byte 32 of a 32-byte key.Only the leaf terminating a path may sit at that depth, but proof path nodes
come off the wire, so a peer could send 65 hash-chained inner nodes and drive
that read past the end of the buffer.
The existing length bound cannot be tightened to catch this: a path for two
keys sharing all 63 leading nibbles legitimately holds 64 inner nodes plus a
leaf, so 65 elements is valid. The claimed node type at the final depth is the
thing to reject, using
>=rather than==to match the convention everyother
kLeafDepthcomparison in this codebase already follows.Reachable from
TMProofPathResponseviaLedgerReplayMsgHandler; confirmedunder ASan with asserts compiled out that the unguarded read lands one byte
past a 32-byte heap allocation. Also closes a second gap: nothing checked the
terminal leaf's own key against
key, so the hash chain alone let a peersubstitute any leaf whose subtree hashes matched at every level above it.
Pinned by tests: the 65-element path that must verify for both keys sharing
the deep prefix, and the forged all-inner path that must not.