CBOR: nested indefinite-length strings/byte strings are accepted (RFC 8949 3.2.3 requires definite-length chunks)
#5,317 建立於 2026年7月27日
倉庫指標
- 星標
- (50,278 顆星)
- PR 合併指標
- (平均合併 9天 4小時) (30 天內合併 16 個 PR)
描述
Description
RFC 8949 §3.2.3 requires that the chunks of an indefinite-length string be definite-length strings of the same major type:
Each chunk of an indefinite-length string is a definite-length string of the same major type.
get_cbor_string() and get_cbor_binary() read each chunk by recursing into themselves (binary_reader.hpp:1025-1037 and 1125-1137), and the recursive call re-enters the full switch including the 0x7F/0x5F case. So a chunk may itself be an indefinite-length string, to arbitrary depth, and the library accepts it.
The cross-major-type rule is already enforced correctly — a byte-string chunk inside a text string is rejected, and vice versa. It is only the "definite-length" half of the rule that is missing.
Secondary effect: because the nesting is unbounded, 0x7F repeated ~50 000 times segfaults. That aspect overlaps with #5104 / #5293, but a depth guard alone would only turn the crash into a clean parse_error — the two-level case below would still be accepted rather than rejected. The conformance fix is independent and cheaper: reject 0x7F/0x5F when already inside an indefinite-length string, which also removes this particular recursion path entirely.
Reproduction steps
Parse a two-level indefinite-length text string and byte string.
Expected vs. actual results
Expected: parse_error.113, same as any other invalid chunk head.
Actual:
| input | meaning | result |
|---|---|---|
7F 7F 61 61 FF FF |
indefinite tstr containing an indefinite tstr | accepted, yields "a" |
5F 5F 41 61 FF FF |
indefinite bstr containing an indefinite bstr | accepted, yields h'61' |
7F 41 61 FF |
bstr chunk inside a tstr | rejected (correct) |
5F 61 61 FF |
tstr chunk inside a bstr | rejected (correct) |
Minimal code example
#include <nlohmann/json.hpp>
#include <iostream>
using json = nlohmann::json;
int main()
{
// 0x7F ( 0x7F ( "a" ) ) -- a chunk that is itself indefinite-length
const std::vector<std::uint8_t> v{0x7F, 0x7F, 0x61, 0x61, 0xFF, 0xFF};
std::cout << json::from_cbor(v) << "\n"; // prints "a"; should throw
// unbounded nesting -> stack exhaustion (cf. #5104)
std::vector<std::uint8_t> deep(50000, 0x7F);
deep.insert(deep.end(), 50000, 0xFF);
(void)json::from_cbor(deep); // segfault
}
Error messages
None — the malformed input is accepted. (The deep case terminates with SIGSEGV.)
Compiler and operating system
g++ 13.3.0, Ubuntu 24.04, -std=c++11
Library version
develop @ 8ec98e2
Would the fix be breaking?
No API/ABI change, and the behavior change is confined to input that is malformed per the spec.
- What changes: byte sequences that RFC 8949 forbids and that other CBOR implementations reject would start throwing
parse_error.113instead of being accepted. There is no valid encoder that produces them, so no legitimate document is affected. - Round-trip safety: the library never emits indefinite-length strings, so nothing it writes can trip the new check.
- Risk: the only realistic breakage is a user whose non-conforming producer happens to emit nested chunks and who relies on this library's leniency. That seems remote, and tightening it matches how the reader already handles the cross-major-type half of the same rule.
- Docs/tests: the "Deserialization" section of
docs/mkdocs/docs/features/binary_formats/cbor.mddoesn't currently say anything about chunk structure; a sentence could be added. I did not find an existing unit test that covers nested chunks, so nothing inunit-cbor.cppshould need to be relaxed.
Fixing this also shrinks the surface of #5104: with nesting rejected, indefinite-length strings stop being a recursion vector at all, and the depth guard in #5293 is left to handle the genuinely recursive cases (nested arrays/maps and chained tags).