mirror of
https://github.com/nlohmann/json.git
synced 2026-11-10 09:28:20 +00:00
json_view: keep tokens without an integer digit out of the float batch
dump() batches float tokens of 16 or 17 digits with at least 8 fraction digits. A damaged image loaded with image_check::bounds can record a token with no digit before the point and 17 after it, which no JSON number has. Its q = -17 is outside the range of locate_token(), which then read fives[17] past its 17 entries; on x86-64 the resulting digits had 18 digits and failed the assertion of write_decimal() (OSS-Fuzz issue 583898810, json_view_image_fuzzer). The batch now takes 8 to 16 fraction digits, which every valid token of 16 or 17 digits has, so it batches the same tokens of valid documents. Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
3 files changed
+50
-8
No files matched your search
@@ -393,7 +393,7 @@ struct depth_counter<false>
|
||||
|
||||
/*!
|
||||
@brief where the double x nearest to a token w * 10^q lies, for 10^15 <= w <
|
||||
10^17 and -17 <= q <= -1 (Eisel-Lemire, then a fixed-point position)
|
||||
10^17 and -16 <= q <= -1 (Eisel-Lemire, then a fixed-point position)
|
||||
|
||||
dump() writes the shortest decimal in the rounding interval of x (the closest
|
||||
one if there are several, see zmij::to_shortest()). Its digits are those of a
|
||||
@@ -1258,13 +1258,15 @@ class view_serializer
|
||||
}
|
||||
for (; p != stop && batch.count != float_batch::capacity(); ++p)
|
||||
{
|
||||
// a token of the source: 16 or 17 digits, at least 8 after the
|
||||
// point, no exponent, 32 bytes to read
|
||||
// a token of the source: 16 or 17 digits, 8 to 16 after the
|
||||
// point, no exponent, 32 bytes to read (17 after the point, with
|
||||
// no digit before it, only in a damaged image loaded with
|
||||
// image_check::bounds: beyond the range of locate_token())
|
||||
const unsigned frac_digits = p->extra >> 8u;
|
||||
const unsigned int_digits = p->extra & 0xFFu;
|
||||
const unsigned count = int_digits + frac_digits;
|
||||
if (p->kind == node_kind::number_float && (p->flags & node_flags::storage) == 0
|
||||
&& count - 16u <= 1u && frac_digits >= 8 && src_end - (src + p->off) >= 33)
|
||||
&& count - 16u <= 1u && frac_digits - 8u <= 8u && src_end - (src + p->off) >= 33)
|
||||
{
|
||||
const auto* const token = reinterpret_cast<const unsigned char*>(src + p->off); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
const unsigned sign = token[0] == '-' ? 1 : 0;
|
||||
|
||||
Generated
+6
-4
@@ -8841,7 +8841,7 @@ struct depth_counter<false>
|
||||
|
||||
/*!
|
||||
@brief where the double x nearest to a token w * 10^q lies, for 10^15 <= w <
|
||||
10^17 and -17 <= q <= -1 (Eisel-Lemire, then a fixed-point position)
|
||||
10^17 and -16 <= q <= -1 (Eisel-Lemire, then a fixed-point position)
|
||||
|
||||
dump() writes the shortest decimal in the rounding interval of x (the closest
|
||||
one if there are several, see zmij::to_shortest()). Its digits are those of a
|
||||
@@ -9706,13 +9706,15 @@ class view_serializer
|
||||
}
|
||||
for (; p != stop && batch.count != float_batch::capacity(); ++p)
|
||||
{
|
||||
// a token of the source: 16 or 17 digits, at least 8 after the
|
||||
// point, no exponent, 32 bytes to read
|
||||
// a token of the source: 16 or 17 digits, 8 to 16 after the
|
||||
// point, no exponent, 32 bytes to read (17 after the point, with
|
||||
// no digit before it, only in a damaged image loaded with
|
||||
// image_check::bounds: beyond the range of locate_token())
|
||||
const unsigned frac_digits = p->extra >> 8u;
|
||||
const unsigned int_digits = p->extra & 0xFFu;
|
||||
const unsigned count = int_digits + frac_digits;
|
||||
if (p->kind == node_kind::number_float && (p->flags & node_flags::storage) == 0
|
||||
&& count - 16u <= 1u && frac_digits >= 8 && src_end - (src + p->off) >= 33)
|
||||
&& count - 16u <= 1u && frac_digits - 8u <= 8u && src_end - (src + p->off) >= 33)
|
||||
{
|
||||
const auto* const token = reinterpret_cast<const unsigned char*>(src + p->off); // NOLINT(cppcoreguidelines-pro-type-reinterpret-cast)
|
||||
const unsigned sign = token[0] == '-' ? 1 : 0;
|
||||
|
||||
@@ -1165,6 +1165,44 @@ TEST_CASE("json_view images: check")
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("float tokens with 17 digits after the point and none before it")
|
||||
{
|
||||
// No JSON number has this layout. dump() batches tokens of 16 or 17
|
||||
// digits, but only those with 8 to 16 fraction digits are in its
|
||||
// range: 17 read past the table of powers of five (caught by a
|
||||
// bounds-checked std::array), and on x86-64 wrote 18 digits
|
||||
// (the assertion of write_decimal()).
|
||||
const std::vector<std::uint8_t> img = json_document::parse("[1.2345678901234567]" + std::string(40, ' ')).save();
|
||||
node n = node_at(img, 1);
|
||||
REQUIRE(n.len == 18);
|
||||
n.extra = 17u << 8u;
|
||||
std::vector<std::uint8_t> b = img;
|
||||
set_node(b, 1, n);
|
||||
std::memcpy(b.data() + text_at(img) + n.off, ".99999999999999999", 18);
|
||||
const json_document d = json_document::load(b, image_check::bounds);
|
||||
const std::string dumped = d.root().dump();
|
||||
CAPTURE(dumped)
|
||||
CHECK(json::parse(dumped)[0].is_number());
|
||||
CHECK(dumped == d.root().materialize().dump());
|
||||
|
||||
// (the input of OSS-Fuzz issue 583898810: the token is " " and
|
||||
// 15 nines, then "1{")
|
||||
const std::vector<std::uint8_t> fuzzed =
|
||||
{
|
||||
0x4E, 0x4A, 0x56, 0x49, 0x01, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
||||
0x07, 0x10, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39, 0x39,
|
||||
0x31, 0x7B, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x00, 0x00
|
||||
};
|
||||
CHECK(load_result(fuzzed, image_check::full) == check_failed);
|
||||
const json_document f = json_document::load(fuzzed, image_check::bounds);
|
||||
CHECK_NOTHROW(f.root().dump());
|
||||
}
|
||||
|
||||
SECTION("nodes that share a range")
|
||||
{
|
||||
// [big string, then n strings made to point to the big string]: every
|
||||
|
||||
Reference in new issue
Block a user