fix(dpp): bound untrusted length prefixes on the proof-verification decode path - #4629
Conversation
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.
|
Warning Review limit reachedNext included review available in 58 minutes. View limit detailsLimit 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. Review configuration: ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (3)
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. Comment |
Codecov Report❌ Patch coverage is 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
🚀 New features to boost your workflow:
|
|
🔍 Review in progress — actively reviewing now (commit 5fa2964) · triage: critical · Phase 2 only (queue backlog) |
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:
DocumentPropertyType::read_optionally_fromread a varint length and immediately didvec![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.AssetLockValue. Decoded from proof elements inverify_state_transition_was_executed_with_proof,#[platform_serialize(unversioned)]with no byte budget, so bincode's no-limit config didvec.resize(len, 0)on thetx_out_scriptprefix.GroupAction. Decoded from proof elements inverify_active_action_infos, same missing budget, reachable through theOption<String>/Vec<u8>note fields onTokenEvent.What was done?
read_exact_boundedreplaces the threevec![0u8; len]; read_exactsites inproperty/mod.rs. It reads throughRead::take(len).read_to_end, so the buffer grows only as bytes actually arrive, and rejects withCorruptedSerializationwhen the prefix outruns the input. The fixed-size byte-array arm (schema-bounded, never a hazard) goes through the same helper but keeps its originalDecodingContractErrorvariant, so no consensus error code moves.AssetLockValuegets#[platform_serialize(limit = 15000)], the same budgetIdentityuses. 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.GroupActiongets#[platform_serialize(limit = 100000)]. Every stored payload is copied from the proposingStateTransition, which carries the same limit, so no valid stored action can exceed it.Not consensus-affecting: the only code that wraps a document-decode
DataContractErrorinto a consensus error (from_bytes_in_consensus) has no non-test callers, and drive-abci surfaces decode failures asInternalErrorwith 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:
ContestedDocumentVotePollStoredInfois decoded on the same path with no budget, but itsfinalized_eventsvector 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_allocatingandlargest_valid_value_round_trips_under_limitforAssetLockValue,rejects_note_length_prefix_beyond_budget_without_allocatingforGroupAction.cargo test -p drive --lib -- group asset_lockandcargo test -p drive-abci --lib -- asset_lock: all pass.cargo clippy -p dpp --all-features --lib --tests -- -D warningsandcargo fmt --check: clean.Breaking Changes
None. Aborts become errors; error variants are unchanged.
Checklist:
For repository code-owners and collaborators only