Repository navigation
Stop CBOR indefinite-length strings from recursing per chunk - #5502
Merged
Merged
Conversation
nlohmann
marked this pull request as ready for review
September 6, 2026 17:43
nlohmann
force-pushed
the
cbor-flatten-chunk-recursion
branch
from
September 7, 2026 05:58
25a4333 to
00e5d21
Compare
nlohmann
force-pushed
the
cbor-flatten-chunk-recursion
branch
from
September 8, 2026 11:13
00e5d21 to
271a013
Compare
nlohmann
force-pushed
the
cbor-flatten-chunk-recursion
branch
from
September 9, 2026 08:21
271a013 to
d61ef62
Compare
nlohmann
force-pushed
the
cbor-flatten-chunk-recursion
branch
from
September 10, 2026 15:15
d61ef62 to
e8c4b9a
Compare
gregmarr
approved these changes
Sep 10, 2026
get_cbor_string() and get_cbor_binary() handled the indefinite-length forms (0x7F and 0x5F) by calling themselves once per chunk. Each chunk therefore cost a native stack frame, and since a chunk may itself be an indefinite- length string, an input of repeated 0x7F bytes reached one frame per input byte: 200,000 of them crash the process with SIGSEGV before a single byte is rejected. This is the same defect as #5104, in a path the container-level work does not touch. Count the open levels instead of recursing through them. That is enough here because every chunk is appended to the same result -- get_bytes() writes at result.size() -- so there is no per-level state to keep. The temporary chunk string and its copy into the result go away with the recursion. The definite-length cases move to get_cbor_string_chunk() and get_cbor_binary_chunk() unchanged, including their error messages, which still name 0x7F and 0x5F because those are handled one level up. Behaviour is unchanged. Comparing against develop over the interesting byte sequences -- empty, single-chunk, nested, over-closed and truncated forms, both strings and byte arrays, and an indefinite-length map key -- produces identical values, error codes, messages and byte offsets. The 200,000-level input now reports parse_error.110 at byte 200001 instead of crashing. Note that nesting these is not valid CBOR: RFC 8949, Section 3.2.3 forbids it. This does not change that either way -- it has always been accepted, and rejecting it is a separate decision (#5317, #5325). Should it be rejected later, that is now one condition on the level counter rather than a change to the control flow. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
nlohmann
force-pushed
the
cbor-flatten-chunk-recursion
branch
from
September 11, 2026 06:23
e8c4b9a to
d31cfac
Compare
This was referenced Sep 25, 2026
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.
Closes one vector of #5104.
What
get_cbor_string()andget_cbor_binary()handled the indefinite-length forms (0x7Fand0x5F) by calling themselves once per chunk. Since a chunk may itself be an indefinite-length string, an input of repeated0x7Fbytes reached one native stack frame per input byte: 200,000 of them crash the process with SIGSEGV before a single byte is rejected.This is the path that blocked #5293, and it is untouched by the container-level work in the rest of this stack.
How
Count the open levels instead of recursing through them. That is enough here because every chunk is appended to the same result —
get_bytes()writes atresult.size()— so there is no per-level state to keep. The temporary chunk string and its copy into the result go away with the recursion.The definite-length cases move to
get_cbor_string_chunk()/get_cbor_binary_chunk()unchanged, including their error messages, which still name0x7Fand0x5Fbecause those are handled one level up.Verification
Compared against
developover the interesting byte sequences — empty, single-chunk, nested, over-closed and truncated forms, both strings and byte arrays, and an indefinite-length map key: identical values, error codes, messages and byte offsets. The 200,000-level input now reportsparse_error.110at byte 200001 instead of crashing.Relationship to #5325
#5325 proposes rejecting nested indefinite-length chunks per RFC 8949 §3.2.3. This PR is orthogonal and behaviour-preserving: it removes the recursion without changing what is accepted, which keeps the
cbor_binary.cborfixture and its decoding assertions working. If #5325 is later accepted, it becomes a single condition on the level counter here rather than a change to the control flow.API impact
No breaking changes. Same inputs accepted, same errors, same byte offsets.
Checklist
make amalgamate.🤖 Generated with Claude Code