Skip to content

Commit 457bc28

Browse files
authored
fix(cbor): reject overflowing negative integers (#5039)
* fix(cbor): reject negative ints overflowing int64 CBOR encodes negative integers as "-1 - n" where n is uint64_t. When n > INT64_MAX, casting to int64_t caused undefined behavior and silent data corruption. Large negative values were incorrectly parsed as positive integers (e.g., -9223372036854775809 became 9223372036854775807). Add bounds check for to reject values that exceed int64_t representable range, returning parse_error instead of silently corrupting data. Added regression test cases to verify. Signed-off-by: Ville Vesilehto <ville@vesilehto.fi> * chore: clarify tests Add test for "n=0" case (result=-1) to cover the smallest magnitude boundary. Update comments to explain CBOR 0x3B encoding and why "result=0" is not possible. Clarify that n is an unsigned integer in the formula "result = -1 - n" to help understanding the tests. Signed-off-by: Ville Vesilehto <ville@vesilehto.fi> * fix(cbor): extend overflow checks for other types Extend negative integer overflow detection to all CBOR negative integer cases (0x38, 0x39, 0x3A) for consistency with the existing 0x3B check. Signed-off-by: Ville Vesilehto <ville@vesilehto.fi> --------- Signed-off-by: Ville Vesilehto <ville@vesilehto.fi>
1 parent 8f75700 commit 457bc28

File tree

3 files changed

+122
-34
lines changed

3 files changed

+122
-34
lines changed

include/nlohmann/detail/input/binary_reader.hpp

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -425,6 +425,25 @@ class binary_reader
425425
426426
@return whether a valid CBOR value was passed to the SAX parser
427427
*/
428+
429+
template<typename NumberType>
430+
bool get_cbor_negative_integer()
431+
{
432+
NumberType number{};
433+
if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::cbor, number)))
434+
{
435+
return false;
436+
}
437+
const auto max_val = static_cast<NumberType>((std::numeric_limits<number_integer_t>::max)());
438+
if (number > max_val)
439+
{
440+
return sax->parse_error(chars_read, get_token_string(),
441+
parse_error::create(112, chars_read,
442+
exception_message(input_format_t::cbor, "negative integer overflow", "value"), nullptr));
443+
}
444+
return sax->number_integer(static_cast<number_integer_t>(-1) - static_cast<number_integer_t>(number));
445+
}
446+
428447
bool parse_cbor_internal(const bool get_char,
429448
const cbor_tag_handler_t tag_handler)
430449
{
@@ -513,29 +532,16 @@ class binary_reader
513532
return sax->number_integer(static_cast<std::int8_t>(0x20 - 1 - current));
514533

515534
case 0x38: // Negative integer (one-byte uint8_t follows)
516-
{
517-
std::uint8_t number{};
518-
return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast<number_integer_t>(-1) - number);
519-
}
535+
return get_cbor_negative_integer<std::uint8_t>();
520536

521537
case 0x39: // Negative integer -1-n (two-byte uint16_t follows)
522-
{
523-
std::uint16_t number{};
524-
return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast<number_integer_t>(-1) - number);
525-
}
538+
return get_cbor_negative_integer<std::uint16_t>();
526539

527540
case 0x3A: // Negative integer -1-n (four-byte uint32_t follows)
528-
{
529-
std::uint32_t number{};
530-
return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast<number_integer_t>(-1) - number);
531-
}
541+
return get_cbor_negative_integer<std::uint32_t>();
532542

533543
case 0x3B: // Negative integer -1-n (eight-byte uint64_t follows)
534-
{
535-
std::uint64_t number{};
536-
return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast<number_integer_t>(-1)
537-
- static_cast<number_integer_t>(number));
538-
}
544+
return get_cbor_negative_integer<std::uint64_t>();
539545

540546
// Binary data (0x00..0x17 bytes follow)
541547
case 0x40:

single_include/nlohmann/json.hpp

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10266,6 +10266,25 @@ class binary_reader
1026610266

1026710267
@return whether a valid CBOR value was passed to the SAX parser
1026810268
*/
10269+
10270+
template<typename NumberType>
10271+
bool get_cbor_negative_integer()
10272+
{
10273+
NumberType number{};
10274+
if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::cbor, number)))
10275+
{
10276+
return false;
10277+
}
10278+
const auto max_val = static_cast<NumberType>((std::numeric_limits<number_integer_t>::max)());
10279+
if (number > max_val)
10280+
{
10281+
return sax->parse_error(chars_read, get_token_string(),
10282+
parse_error::create(112, chars_read,
10283+
exception_message(input_format_t::cbor, "negative integer overflow", "value"), nullptr));
10284+
}
10285+
return sax->number_integer(static_cast<number_integer_t>(-1) - static_cast<number_integer_t>(number));
10286+
}
10287+
1026910288
bool parse_cbor_internal(const bool get_char,
1027010289
const cbor_tag_handler_t tag_handler)
1027110290
{
@@ -10354,29 +10373,16 @@ class binary_reader
1035410373
return sax->number_integer(static_cast<std::int8_t>(0x20 - 1 - current));
1035510374

1035610375
case 0x38: // Negative integer (one-byte uint8_t follows)
10357-
{
10358-
std::uint8_t number{};
10359-
return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast<number_integer_t>(-1) - number);
10360-
}
10376+
return get_cbor_negative_integer<std::uint8_t>();
1036110377

1036210378
case 0x39: // Negative integer -1-n (two-byte uint16_t follows)
10363-
{
10364-
std::uint16_t number{};
10365-
return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast<number_integer_t>(-1) - number);
10366-
}
10379+
return get_cbor_negative_integer<std::uint16_t>();
1036710380

1036810381
case 0x3A: // Negative integer -1-n (four-byte uint32_t follows)
10369-
{
10370-
std::uint32_t number{};
10371-
return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast<number_integer_t>(-1) - number);
10372-
}
10382+
return get_cbor_negative_integer<std::uint32_t>();
1037310383

1037410384
case 0x3B: // Negative integer -1-n (eight-byte uint64_t follows)
10375-
{
10376-
std::uint64_t number{};
10377-
return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast<number_integer_t>(-1)
10378-
- static_cast<number_integer_t>(number));
10379-
}
10385+
return get_cbor_negative_integer<std::uint64_t>();
1038010386

1038110387
// Binary data (0x00..0x17 bytes follow)
1038210388
case 0x40:

tests/src/unit-cbor.cpp

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1705,6 +1705,16 @@ TEST_CASE("CBOR")
17051705
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x1b, 0x00, 0x00, 0x00, 0x00, 0x00})), "[json.exception.parse_error.110] parse error at byte 7: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
17061706
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})), "[json.exception.parse_error.110] parse error at byte 8: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
17071707
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})), "[json.exception.parse_error.110] parse error at byte 9: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
1708+
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x38})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
1709+
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x39})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
1710+
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x39, 0x00})), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
1711+
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x3a})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
1712+
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x3a, 0x00})), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
1713+
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x3a, 0x00, 0x00})), "[json.exception.parse_error.110] parse error at byte 4: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
1714+
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x3a, 0x00, 0x00, 0x00})), "[json.exception.parse_error.110] parse error at byte 5: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
1715+
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x3b})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
1716+
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x3b, 0x00})), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
1717+
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x3b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00})), "[json.exception.parse_error.110] parse error at byte 9: syntax error while parsing CBOR number: unexpected end of input", json::parse_error&);
17081718
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x62})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR string: unexpected end of input", json::parse_error&);
17091719
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x62, 0x60})), "[json.exception.parse_error.110] parse error at byte 3: syntax error while parsing CBOR string: unexpected end of input", json::parse_error&);
17101720
CHECK_THROWS_WITH_AS(_ = json::from_cbor(std::vector<uint8_t>({0x7F})), "[json.exception.parse_error.110] parse error at byte 2: syntax error while parsing CBOR string: unexpected end of input", json::parse_error&);
@@ -1733,6 +1743,16 @@ TEST_CASE("CBOR")
17331743
CHECK(json::from_cbor(std::vector<uint8_t>({0x1b, 0x00, 0x00, 0x00, 0x00, 0x00}), true, false).is_discarded());
17341744
CHECK(json::from_cbor(std::vector<uint8_t>({0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}), true, false).is_discarded());
17351745
CHECK(json::from_cbor(std::vector<uint8_t>({0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}), true, false).is_discarded());
1746+
CHECK(json::from_cbor(std::vector<uint8_t>({0x38}), true, false).is_discarded());
1747+
CHECK(json::from_cbor(std::vector<uint8_t>({0x39}), true, false).is_discarded());
1748+
CHECK(json::from_cbor(std::vector<uint8_t>({0x39, 0x00}), true, false).is_discarded());
1749+
CHECK(json::from_cbor(std::vector<uint8_t>({0x3a}), true, false).is_discarded());
1750+
CHECK(json::from_cbor(std::vector<uint8_t>({0x3a, 0x00}), true, false).is_discarded());
1751+
CHECK(json::from_cbor(std::vector<uint8_t>({0x3a, 0x00, 0x00}), true, false).is_discarded());
1752+
CHECK(json::from_cbor(std::vector<uint8_t>({0x3a, 0x00, 0x00, 0x00}), true, false).is_discarded());
1753+
CHECK(json::from_cbor(std::vector<uint8_t>({0x3b}), true, false).is_discarded());
1754+
CHECK(json::from_cbor(std::vector<uint8_t>({0x3b, 0x00}), true, false).is_discarded());
1755+
CHECK(json::from_cbor(std::vector<uint8_t>({0x3b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}), true, false).is_discarded());
17361756
CHECK(json::from_cbor(std::vector<uint8_t>({0x62}), true, false).is_discarded());
17371757
CHECK(json::from_cbor(std::vector<uint8_t>({0x62, 0x60}), true, false).is_discarded());
17381758
CHECK(json::from_cbor(std::vector<uint8_t>({0x7F}), true, false).is_discarded());
@@ -2681,6 +2701,62 @@ TEST_CASE("Tagged values")
26812701
}
26822702
}
26832703

2704+
SECTION("negative integer overflow")
2705+
{
2706+
// CBOR encodes negative integers as: result = -1 - n
2707+
// For type 0x3B, n is an 8-byte uint64_t. Valid range for n with
2708+
// the default int64_t is [0, INT64_MAX], producing results in [INT64_MIN, -1].
2709+
// When n > INT64_MAX, the result exceeds int64_t range and is rejected.
2710+
2711+
SECTION("n = 0 is valid (result = -1)")
2712+
{
2713+
// n = 0, result = -1 - 0 = -1 (smallest magnitude negative)
2714+
const std::vector<uint8_t> input = {0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
2715+
const auto result = json::from_cbor(input);
2716+
CHECK(result.is_number_integer());
2717+
CHECK(result.get<int64_t>() == -1);
2718+
}
2719+
2720+
SECTION("n = INT64_MAX is valid (result = INT64_MIN)")
2721+
{
2722+
// n = INT64_MAX (0x7FFFFFFFFFFFFFFF)
2723+
// result = -1 - INT64_MAX = INT64_MIN (-9223372036854775808)
2724+
const std::vector<uint8_t> input = {0x3B, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
2725+
const auto result = json::from_cbor(input);
2726+
CHECK(result.is_number_integer());
2727+
CHECK(result.get<int64_t>() == (std::numeric_limits<int64_t>::min)());
2728+
}
2729+
2730+
SECTION("n = INT64_MAX + 1 is rejected (overflow)")
2731+
{
2732+
// n = INT64_MAX + 1 (0x8000000000000000)
2733+
// result = -1 - n = -9223372036854775809, which exceeds int64_t range
2734+
const std::vector<uint8_t> input = {0x3B, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
2735+
json _;
2736+
CHECK_THROWS_WITH_AS(_ = json::from_cbor(input),
2737+
"[json.exception.parse_error.112] parse error at byte 9: syntax error while parsing CBOR value: negative integer overflow",
2738+
json::parse_error);
2739+
}
2740+
2741+
SECTION("n = UINT64_MAX is rejected (overflow)")
2742+
{
2743+
// n = UINT64_MAX (0xFFFFFFFFFFFFFFFF)
2744+
// result = -1 - n = -18446744073709551616, which exceeds int64_t range
2745+
const std::vector<uint8_t> input = {0x3B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
2746+
json _;
2747+
CHECK_THROWS_WITH_AS(_ = json::from_cbor(input),
2748+
"[json.exception.parse_error.112] parse error at byte 9: syntax error while parsing CBOR value: negative integer overflow",
2749+
json::parse_error);
2750+
}
2751+
2752+
SECTION("overflow with allow_exceptions=false returns discarded")
2753+
{
2754+
const std::vector<uint8_t> input = {0x3B, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
2755+
const auto result = json::from_cbor(input, true, false);
2756+
CHECK(result.is_discarded());
2757+
}
2758+
}
2759+
26842760
SECTION("tagged binary")
26852761
{
26862762
// create a binary value of subtype 42

0 commit comments

Comments
 (0)