Skip to content

Commit a17996e

Browse files
committed
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>
1 parent 7c2b2ed commit a17996e

File tree

3 files changed

+75
-5
lines changed

3 files changed

+75
-5
lines changed

include/nlohmann/detail/input/binary_reader.hpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -533,8 +533,18 @@ class binary_reader
533533
case 0x3B: // Negative integer -1-n (eight-byte uint64_t follows)
534534
{
535535
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));
536+
if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::cbor, number)))
537+
{
538+
return false;
539+
}
540+
const auto max_val = static_cast<std::uint64_t>((std::numeric_limits<number_integer_t>::max)());
541+
if (number > max_val)
542+
{
543+
return sax->parse_error(chars_read, get_token_string(),
544+
parse_error::create(112, chars_read,
545+
exception_message(input_format_t::cbor, "negative integer overflow", "value"), nullptr));
546+
}
547+
return sax->number_integer(static_cast<number_integer_t>(-1) - static_cast<number_integer_t>(number));
538548
}
539549

540550
// Binary data (0x00..0x17 bytes follow)

single_include/nlohmann/json.hpp

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10350,10 +10350,21 @@ class binary_reader
1035010350
case 0x37:
1035110351
return sax->number_integer(static_cast<std::int8_t>(0x20 - 1 - current));
1035210352

10353-
case 0x38: // Negative integer (one-byte uint8_t follows)
10353+
case 0x3B: // Negative integer -1-n (eight-byte uint64_t follows)
1035410354
{
10355-
std::uint8_t number{};
10356-
return get_number(input_format_t::cbor, number) && sax->number_integer(static_cast<number_integer_t>(-1) - number);
10355+
std::uint64_t number{};
10356+
if (JSON_HEDLEY_UNLIKELY(!get_number(input_format_t::cbor, number)))
10357+
{
10358+
return false;
10359+
}
10360+
const auto max_val = static_cast<std::uint64_t>((std::numeric_limits<number_integer_t>::max)());
10361+
if (number > max_val)
10362+
{
10363+
return sax->parse_error(chars_read, get_token_string(),
10364+
parse_error::create(112, chars_read,
10365+
exception_message(input_format_t::cbor, "negative integer overflow", "value"), nullptr));
10366+
}
10367+
return sax->number_integer(static_cast<number_integer_t>(-1) - static_cast<number_integer_t>(number));
1035710368
}
1035810369

1035910370
case 0x39: // Negative integer -1-n (two-byte uint16_t follows)

tests/src/unit-cbor.cpp

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1705,6 +1705,9 @@ 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>({0x3b})), "[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>({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&);
1710+
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&);
17081711
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&);
17091712
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&);
17101713
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 +1736,9 @@ TEST_CASE("CBOR")
17331736
CHECK(json::from_cbor(std::vector<uint8_t>({0x1b, 0x00, 0x00, 0x00, 0x00, 0x00}), true, false).is_discarded());
17341737
CHECK(json::from_cbor(std::vector<uint8_t>({0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}), true, false).is_discarded());
17351738
CHECK(json::from_cbor(std::vector<uint8_t>({0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}), true, false).is_discarded());
1739+
CHECK(json::from_cbor(std::vector<uint8_t>({0x3b}), true, false).is_discarded());
1740+
CHECK(json::from_cbor(std::vector<uint8_t>({0x3b, 0x00}), true, false).is_discarded());
1741+
CHECK(json::from_cbor(std::vector<uint8_t>({0x3b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}), true, false).is_discarded());
17361742
CHECK(json::from_cbor(std::vector<uint8_t>({0x62}), true, false).is_discarded());
17371743
CHECK(json::from_cbor(std::vector<uint8_t>({0x62, 0x60}), true, false).is_discarded());
17381744
CHECK(json::from_cbor(std::vector<uint8_t>({0x7F}), true, false).is_discarded());
@@ -2681,6 +2687,49 @@ TEST_CASE("Tagged values")
26812687
}
26822688
}
26832689

2690+
SECTION("negative integer overflow")
2691+
{
2692+
// CBOR encodes negative integers as -1 - n, where n is uint64_t.
2693+
// When n > INT64_MAX, the result cannot be represented in int64_t.
2694+
// The library should reject such values with a parse error.
2695+
2696+
SECTION("n = INT64_MAX is valid (result = INT64_MIN)")
2697+
{
2698+
// n = 0x7FFFFFFFFFFFFFFF (INT64_MAX), result = -1 - INT64_MAX = INT64_MIN
2699+
const std::vector<uint8_t> v = {0x3B, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
2700+
const auto j = json::from_cbor(v);
2701+
CHECK(j.is_number_integer());
2702+
CHECK(j.get<int64_t>() == (std::numeric_limits<int64_t>::min)());
2703+
}
2704+
2705+
SECTION("n = INT64_MAX + 1 causes overflow")
2706+
{
2707+
// n = 0x8000000000000000 (INT64_MAX + 1), result would be -9223372036854775809
2708+
const std::vector<uint8_t> v = {0x3B, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
2709+
json _;
2710+
CHECK_THROWS_WITH_AS(_ = json::from_cbor(v),
2711+
"[json.exception.parse_error.112] parse error at byte 9: syntax error while parsing CBOR value: negative integer overflow",
2712+
json::parse_error);
2713+
}
2714+
2715+
SECTION("n = UINT64_MAX causes overflow")
2716+
{
2717+
// n = 0xFFFFFFFFFFFFFFFF (UINT64_MAX), result would be -18446744073709551616
2718+
const std::vector<uint8_t> v = {0x3B, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
2719+
json _;
2720+
CHECK_THROWS_WITH_AS(_ = json::from_cbor(v),
2721+
"[json.exception.parse_error.112] parse error at byte 9: syntax error while parsing CBOR value: negative integer overflow",
2722+
json::parse_error);
2723+
}
2724+
2725+
SECTION("overflow with allow_exceptions=false returns discarded")
2726+
{
2727+
const std::vector<uint8_t> v = {0x3B, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
2728+
const auto j = json::from_cbor(v, true, false);
2729+
CHECK(j.is_discarded());
2730+
}
2731+
}
2732+
26842733
SECTION("tagged binary")
26852734
{
26862735
// create a binary value of subtype 42

0 commit comments

Comments
 (0)