Skip to content

fix(dpp): bound untrusted length prefixes on the proof-verification decode path - #4629

Merged
QuantumExplorer merged 3 commits into
v4.2-devfrom
fix/dpp-bound-untrusted-length-reads
Sep 8, 2026
Merged

fix(dpp): bound untrusted length prefixes on the proof-verification decode path#4629
QuantumExplorer merged 3 commits into
v4.2-devfrom
fix/dpp-bound-untrusted-length-reads

Conversation

@PastaPastaPasta

Copy link
Copy Markdown
Member

Issue being fixed or feature implemented

Split out of #4618 so the security fix lands ahead of the wire-crate refactor it was stacked with.

A proved response's stored values are decoded before its quorum signature is checked: the root hash only exists after GroveDB replay, so the decoders run on attacker-controlled bytes. Three decoders on that path sized an allocation from a length prefix in the untrusted input:

  1. Document fields. DocumentPropertyType::read_optionally_from read a varint length and immediately did vec![0u8; len], for variable-size string/byte-array fields and for nested object payloads. ~70 bytes of self-consistent proof carrying a document whose length prefix claims 2^62 bytes aborted every proof-verifying client (rs-sdk, wasm-sdk, the Dash Core embedder) on allocation failure.
  2. AssetLockValue. Decoded from proof elements in verify_state_transition_was_executed_with_proof, #[platform_serialize(unversioned)] with no byte budget, so bincode's no-limit config did vec.resize(len, 0) on the tx_out_script prefix.
  3. GroupAction. Decoded from proof elements in verify_active_action_infos, same missing budget, reachable through the Option<String> / Vec<u8> note fields on TokenEvent.

What was done?

  • read_exact_bounded replaces the three vec![0u8; len]; read_exact sites in property/mod.rs. It reads through Read::take(len).read_to_end, so the buffer grows only as bytes actually arrive, and rejects with CorruptedSerialization when the prefix outruns the input. The fixed-size byte-array arm (schema-bounded, never a hazard) goes through the same helper but keeps its original DecodingContractError variant, so no consensus error code moves.
  • AssetLockValue gets #[platform_serialize(limit = 15000)], the same budget Identity uses. A valid value is well under 1 KiB (P2PKH script, at most 16 used tags); the limit leaves room for Core's 10,000-byte script ceiling.
  • GroupAction gets #[platform_serialize(limit = 100000)]. Every stored payload is copied from the proposing StateTransition, which carries the same limit, so no valid stored action can exceed it.

Not consensus-affecting: the only code that wraps a document-decode DataContractError into a consensus error (from_bytes_in_consensus) has no non-test callers, and drive-abci surfaces decode failures as InternalError with the message stripped. Both server-side read-back sites (fetch_asset_lock_outpoint_info, fetch_active_action_info) stay within the new budgets for any value the server can write.

Not covered here: ContestedDocumentVotePollStoredInfo is decoded on the same path with no budget, but its finalized_events vector grows with every re-run of a locked poll, so no static budget can be derived without a design decision. Follow-up.

How Has This Been Tested?

  • cargo test -p dpp --lib: 4100 passed. New tests: test_read_optionally_from_rejects_length_prefix_longer_than_input (2^62 - 1 prefix on the string and object arms), test_read_optionally_from_accepts_length_prefix_equal_to_remaining_input (boundary), rejects_script_length_prefix_beyond_budget_without_allocating and largest_valid_value_round_trips_under_limit for AssetLockValue, rejects_note_length_prefix_beyond_budget_without_allocating for GroupAction.
  • cargo test -p drive --lib -- group asset_lock and cargo test -p drive-abci --lib -- asset_lock: all pass.
  • cargo clippy -p dpp --all-features --lib --tests -- -D warnings and cargo fmt --check: clean.

Breaking Changes

None. Aborts become errors; error variants are unchanged.

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have added or updated relevant unit/integration/functional/e2e tests
  • I have added "!" to the title and described breaking changes in the corresponding section if my code contains any
  • I have made corresponding changes to the documentation if needed

For repository code-owners and collaborators only

  • I have assigned this pull request to a milestone

Document deserialization read a varint length from the (untrusted) serialized bytes and immediately allocated a Vec of that size, for both variable-size string/byte-array fields and nested object payloads. A proved getDocuments response is decoded before its quorum signature is checked, so a self-consistent GroveDB proof carrying a document whose length prefix claims gigabytes made every client that verifies proofs (rs-sdk, wasm-sdk, and the Dash Core embedder) abort on allocation failure from ~70 bytes of input.

Both sites now check the declared length against the bytes remaining in the reader and reject with CorruptedSerialization before allocating. A regression test feeds a 2^62 length prefix to both the string and object arms.
AssetLockValue is decoded from GroveDB proof elements on the client before the quorum signature is checked, and it carried no platform_serialize limit, so a proof element whose tx_out_script length prefix claimed gigabytes aborted the verifier on allocation. Apply the same 15000-byte budget Identity uses; a valid value is well under 1 KiB.
GroupAction is decoded from GroveDB proof elements on the client before the quorum signature is checked, and it carried no platform_serialize limit, so a proof element whose token-event note length prefix claimed gigabytes aborted the verifier on allocation. Every stored payload is copied from the proposing state transition, which is capped at 100,000 bytes, so the same budget applies.
@coderabbitai

coderabbitai Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Warning

Review limit reached

Next included review available in 58 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 78ce42b2-f6ac-4601-9e26-3514f38f5cf2

📥 Commits

Reviewing files that changed from the base of the PR and between f712cf6 and 5fa2964.

📒 Files selected for processing (3)
  • packages/rs-dpp/src/asset_lock/reduced_asset_lock_value/mod.rs
  • packages/rs-dpp/src/data_contract/document_type/property/mod.rs
  • packages/rs-dpp/src/group/group_action/mod.rs

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.

@codecov

codecov Bot commented Sep 8, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 78.70968% with 33 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.24%. Comparing base (fa44292) to head (5fa2964).
⚠️ Report is 14 commits behind head on v4.2-dev.

Files with missing lines Patch % Lines
...pp/src/data_contract/document_type/property/mod.rs 69.30% 31 Missing ⚠️
...dpp/src/asset_lock/reduced_asset_lock_value/mod.rs 97.05% 1 Missing ⚠️
packages/rs-dpp/src/group/group_action/mod.rs 95.00% 1 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##           v4.2-dev    #4629      +/-   ##
============================================
- Coverage     85.36%   84.24%   -1.13%     
============================================
  Files          2792     2794       +2     
  Lines        370547   376651    +6104     
============================================
+ Hits         316331   317307     +976     
- Misses        54216    59344    +5128     
Components Coverage Δ
dpp 83.26% <78.70%> (-2.39%) ⬇️
drive 83.55% <ø> (-0.46%) ⬇️
drive-abci 88.36% <ø> (-0.25%) ⬇️
sdk ∅ <ø> (∅)
dapi-client ∅ <ø> (∅)
platform-version ∅ <ø> (∅)
platform-value 92.92% <ø> (ø)
platform-wallet ∅ <ø> (∅)
drive-proof-verifier 39.85% <ø> (-8.97%) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@thepastaclaw

thepastaclaw commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

🔍 Review in progress — actively reviewing now (commit 5fa2964) · triage: critical · Phase 2 only (queue backlog)

@PastaPastaPasta PastaPastaPasta added the ready for final review Ready for the final review. If AI was involved in producing this PR, it has already had a reviewer. label Sep 8, 2026
@QuantumExplorer
QuantumExplorer merged commit 791795f into v4.2-dev Sep 8, 2026
32 checks passed
@QuantumExplorer
QuantumExplorer deleted the fix/dpp-bound-untrusted-length-reads branch September 8, 2026 16:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready for final review Ready for the final review. If AI was involved in producing this PR, it has already had a reviewer.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants