Sitelet https://github.com/RustCrypto/formats/pull/2463
Skip to content

serdect: check the decoded length on the hex path too - #2463

Open
yuxi-liu-wired wants to merge 1 commit into
RustCrypto:masterfrom
yuxi-liu-wired:fix/serdect-hex-exact-length
Open

yuxi-liu-wired wants to merge 1 commit into
RustCrypto:masterfrom
yuxi-liu-wired:fix/serdect-hex-exact-length

Conversation

@yuxi-liu-wired

Copy link
Copy Markdown
Contributor

array::deserialize_hex_or_bin is documented to fail "if the buffer isn't the exact same size as the resulting array". The binary path (SliceVisitor) enforces that through LengthCheck. The human-readable path (StrIntoBufVisitor) only decodes into the buffer, and base16ct::mixed::decode accepts shorter input, so a short hex string is accepted and the rest of the array is left zeroed:

// 15 bytes of hex for a 16-byte array
serde_json::from_str::<serdect::array::HexLowerOrBin<16>>("\"000102030405060708090a0b0c0d0e\"") // Ok
serde_json::from_str::<serdect::array::HexUpperOrBin<16>>("\"AB\"")                              // Ok

Downstream, crypto-bigint 0.7 deserializes Uint/Int through this function. With JSON:

serde_json::to_string(&U256::from_u64(0x1234)) // "3412000000…000" (64 hex digits)
serde_json::from_str::<U256>("\"34\"")          // Ok(0x34)
serde_json::from_str::<U256>("\"3412\"")        // Ok(0x1234)

elliptic-curve (ScalarValue) and ecdsa (Signature) use it the same way. A truncated value is read as a different number instead of being rejected. For signatures the later range checks happen to reject my examples, but nothing in serdect stops it.

The fix applies the same LengthCheck to the decoded length (v.len() / 2) before decoding, giving the same "invalid length" error as the binary path. For slices the check is the existing upper bound, so slice::deserialize_hex_or_bin behaves as before. Odd-length input still reports "an even number of hex digits".

Test: deserialize_array_wrong_length in tests/serde_json.rs checks that short and long hex strings are rejected for arrays (typed wrapper and deserialize_hex_or_bin), and that slices still accept shorter input and reject longer. It fails on master. The serdect.yml matrix passed locally (powerset on stable and 1.85, thumbv7em), along with sec1 all-features tests, fmt and clippy.

This PR was produced by AI agents (Claude) and reviewed by me before submission.

array::deserialize_hex_or_bin is documented to fail "if the buffer isn't
the exact same size as the resulting array". The binary path
(SliceVisitor) checks LengthCheck, but the hex path (StrIntoBufVisitor)
only decoded into the buffer, which base16ct accepts when the input is
shorter. So a human-readable format accepted a short hex string and left
the rest of the array zeroed: HexLowerOrBin<16> from 15 bytes of hex, or
crypto-bigint's U256 from "34" (= 0x34; its full encoding is 64 hex
digits).

Apply the same LengthCheck to the decoded length before decoding: exact
for arrays, an upper bound for slices (unchanged behaviour there).

This branch has not been deployed

No deployments
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.

1 participant