Repository navigation
std.cbor: decode limits and duplicate-key refusal by default, deterministic encoding - #3469
Merged
Merged
Conversation
…coder - Add CborDecodeOptions with safe default limits and parse_with - Refuse duplicate map keys by default comparing decoded key values - Enforce bounds on declared string lengths and container counts before looping - Implement encode_deterministic following RFC 8949 Core Deterministic Encoding - Add test_cbor_hardening.ae and update docs and changelog Co-authored-by: paul-hammant <82182+paul-hammant@users.noreply.github.com>
The duplicate-key check compared each key with every earlier one, and encode_deterministic sorted keys with an insertion sort. Both are quadratic, so a map at the default 131072-pair limit (about 400 KB) cost billions of comparisons: the input parse_with exists to refuse cheaply became a way to spend minutes of CPU. Duplicates are now found through a set keyed by the hex of each key's deterministic encoding (equal values encode equally, however they were written), and keys are merge-sorted. test_large_maps parses and sorts 131072 keys in under a second; on the quadratic version it was still running when killed at 90 s. Comments the first commit removed are back: why lengths are compared in long, why counts are checked against the bytes left, parse's #2470 note. Changelog fragment rewritten; docs/api regenerated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
check-raw-calls refuses a std module calling another module's raw C binding (map_put_raw). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Importing std.map into std.cbor fails check-warnings: std.map's get mangles to map_get, the symbol of cbor's own map_get. Once a map is parsed, its keys' deterministic encodings are now merge-sorted and neighbours compared, which is still n log n. test_large_maps still parses and sorts 131072 keys in about a second; with the check disabled, the four duplicate-key cases fail. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
std.cborgets ready to decode bytes from an untrusted peer, such as the planned sandboxed-child IPC, where the parent decodes whatever the child sends.CborDecodeOptions/default_decode_options()/parse_with(data, opts), followingstd/tar'sExtractOptions. Defaults: depth 256 (unchanged), 131072 array elements and 131072 map pairs (fxamacker/cbor's defaults), duplicate keys refused, indefinite lengths allowed.parserefuses duplicate map keys (breaking, on purpose):{"grant":"none","grant":"all"}is an error, not a guess about which value wins. Keys are duplicates when they are the same value however encoded, so a definite and an indefinite"a"collide.parse_withwithallow_duplicate_keys = 1gives the old behaviour.encode_deterministic: RFC 8949 §4.2.1 core deterministic encoding (shortest heads and floats, definite lengths, map keys in bytewise order of their encodings, duplicate keys an error).encodestill writes a parsed value back byte for byte (cbor/msgpack: trailing bytes accepted, cbor not-well-formed input accepted, cbor diagnose lossy #2470).Drafted by Jules (paul-hammant#44): options, limits, deterministic encoder, and
test_cbor_hardening.aewith vectors cited from fxamacker/cbor. Its PR lists a disable-the-protection check for each limit.Shaped here (second commit):
std.mapset would collide with cbor's ownmap_get), andencode_deterministicmerge-sorts keys too. The newtest_large_mapsparses and sorts 131072 keys; the whole hardening test takes 0.84 s, and against the first commit's code it was still running when killed at 90 s.long, why counts are checked against the bytes left,parse's cbor/msgpack: trailing bytes accepted, cbor not-well-formed input accepted, cbor diagnose lossy #2470 note).docs/apiregenerated.Checked:
test_cbor(all RFC 8949 Appendix A vectors),test_cbor_hardeningandtests/regression/test_cbor_roundtrip_no_leak.aepass; both std tests are valgrind-clean (0 errors);ae fmt,make check-docs,check-changelogandcheck-testspass.🤖 Generated with Claude Code