Skip to content

Fix NewPSMT fabricated register reads via non-inclusion proof payload injection - #8674

Merged
zhangchiqing merged 2 commits into
masterfrom
leo/fix-ptrie-fabricated-reads
Sep 24, 2026
Merged

zhangchiqing merged 2 commits into
masterfrom
leo/fix-ptrie-fabricated-reads

Conversation

@zhangchiqing

@zhangchiqing zhangchiqing commented Aug 27, 2026 •

Copy link
Copy Markdown
Member

NewPSMT conditioned leaf hash computation on pr.Inclusion, which is attacker-controlled. A byzantine EN could supply a ChunkDataPack.Proof with a fabricated payload on a non-inclusion proof (or a duplicate proof for an already-proven path), causing the fabricated value to pass the root check and be served as authentic state by GetSinglePayload/Get.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Summary by CodeRabbit

  • Bug Fixes

    • Strengthened proof validation to reject fabricated or tampered payloads, including non-inclusion, duplicate-path, and truncated-path cases.
    • Ensured invalid proofs fail root-hash verification instead of being treated as authentic state.
    • Rejected proofs that terminate at trie nodes with unverified child data.
    • Preserved validation of legitimate proofs.
  • Tests

    • Added regression coverage for fabricated payloads and truncated proofs terminating at the root or an interior trie node.

@coderabbitai

coderabbitai Bot commented Aug 27, 2026 •

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 82002d57-7f7c-4e1a-8ac3-6dabc5590731

📥 Commits

Reviewing files that changed from the base of the PR and between c6bc35d and 2a11a5d.

📒 Files selected for processing (2)
  • ledger/partial/ptrie/partialTrie.go
  • ledger/partial/ptrie/partialTrie_test.go

Included review availability: Your plan provides up to 4 included reviews per hour; 3 remain after this review.


📝 Walkthrough

Walkthrough

NewPSMT now hashes all proof payloads and rejects proofs that terminate at non-leaf nodes. Tests cover valid proofs and fabricated payloads in inclusion, non-inclusion, duplicate-path, and truncated-proof cases.

Changes

PSMT proof validation

Layer / File(s) Summary
Proof payload and terminal validation
ledger/partial/ptrie/partialTrie.go
NewPSMT computes hashes from every proof payload and rejects terminal paths that still have children.
Proof validation regression coverage
ledger/partial/ptrie/partialTrie_test.go
Adds shared fixtures, valid-proof checks, and rejection tests for fabricated payloads, duplicate paths, and truncated proofs.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~20 minutes

Merge Risk: ⚪ Minimal · up to 2a11a

PSMT proof construction now rejects forged payloads and truncated proofs that could otherwise expose fabricated register state. Valid proof behavior and the affected attack cases are covered, with no remaining merge-blocking risk identified.

Suggested reviewers: janezpodhostnik

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the primary change: preventing fabricated register reads through non-inclusion proof payload injection in NewPSMT.
Docstring Coverage ✅ Passed Docstring coverage is 90.91% which is sufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 11 functions across 2 files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch leo/fix-ptrie-fabricated-reads

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@zhangchiqing
zhangchiqing marked this pull request as ready for review August 27, 2026 18:05
@zhangchiqing
zhangchiqing requested a review from a team as a code owner August 27, 2026 18:05
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Comment on lines +139 to +149
// Bind the payload to the node's hash unconditionally, i.e. WITHOUT trusting pr.Inclusion.
// The proofs originate from a potentially byzantine source (e.g. an execution node's
// ChunkDataPack), so pr.Inclusion is attacker-controlled and must not gate hash computation:
// otherwise a non-inclusion proof could carry a fabricated (non-empty) payload while the
// node's hash stayed at the honest default, making the fabrication invisible to the root
// check below and served by GetSinglePayload/Get as authentic state. Computing the hash from
// the payload here mirrors proof.VerifyTrieProof: an empty payload yields the default hash
// (so honest non-inclusion proofs are unaffected), while any fabricated payload yields a
// non-default hash that fails the root check. This likewise defeats a duplicate-path
// overwrite, since the second proof's payload now necessarily changes the node's hash.
currentNode.hashValue = ledger.ComputeCompactValue(hash.Hash(path), payload.Value(), currentNode.height)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This only protects end-of-proof nodes that remain leaves. forceComputeHash recomputes any node with children from its children and discards the payload-derived hashValue, while pathLookUp still serves that node's payload.

A crafted proof with truncated Steps (Steps=0 lands on the root; a short duplicate lands on an interior ancestor) plus one honest proof passes the root check, and GetSinglePayload returns the fabricated payload.

Reject a proof whose terminal node has or gains children, or verify each proof individually (VerifyTrieProof semantics), or only serve payloads from leaf nodes.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, fixed in 2a11a5d

// secPathP plus a duplicate proof for the same path carrying a fabricated payload. The duplicate's
// payload now necessarily changes P's node hash, so the reconstructed root no longer matches and
// NewPSMT rejects the batch.
func TestNewPSMT_RejectsDuplicatePathOverwrite(t *testing.T) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: a related variant is untested: an honest inclusion proof for P plus a duplicate non-inclusion proof carrying an EMPTY payload. Pre-fix this passed the root check while blanking a committed register's value; post-fix it is rejected (empty payload yields the default hash, so the root mismatches).

@zhangchiqing
zhangchiqing added this pull request to the merge queue Sep 24, 2026
Merged via the queue into master with commit 4a8bc1f Sep 24, 2026
62 checks passed
@zhangchiqing
zhangchiqing deleted the leo/fix-ptrie-fabricated-reads branch September 24, 2026 04:06
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.

4 participants