From 1c9475d68f9c2760c245d64981cddde7567f72ff Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Sun, 6 Sep 2026 20:08:27 +0200 Subject: [PATCH] Fix remaining CI failures in the huge-claimed-length DoS regression tests - Apply the same google-readability-casting fix (std::size_t{N} instead of std::size_t(N)) to the "arrays of various sizes" section in unit-msgpack.cpp, unit-ubjson.cpp, and unit-bjdata.cpp; only unit-cbor.cpp had been fixed previously, since clang-tidy's build didn't get far enough to report the other three in the same pass. - json_sax_dom_parser::start_array()'s max_size() check calls JSON_THROW directly rather than going through sax->parse_error(), so unlike the scanner's own "not enough data" parse_error it is not gated by allow_exceptions=false. On a platform where a header's claimed count exceeds max_size() (e.g. 32-bit, for UBJSON/BJData's 0x7FFFFFFF test value), from_ubjson/from_bjdata(input, true, false) can therefore still throw instead of returning a discarded value. Make that assertion tolerant of either outcome, same as the main exception-catching check above it. - Guard all four "a huge claimed length..." SECTIONs with #if !defined(JSON_NOEXCEPTION), matching this test suite's existing convention for exception-dependent tests: under JSON_NOEXCEPTION, JSON_THROW never produces a catchable C++ exception at all (it aborts the process), so a section that relies on try/catch to distinguish between two acceptable outcomes cannot be expressed under that build configuration regardless of platform. Signed-off-by: Niels Lohmann --- tests/src/unit-bjdata.cpp | 29 +++++++++++++++++++++++++---- tests/src/unit-cbor.cpp | 6 ++++++ tests/src/unit-msgpack.cpp | 12 +++++++++--- tests/src/unit-ubjson.cpp | 29 +++++++++++++++++++++++++---- 4 files changed, 65 insertions(+), 11 deletions(-) diff --git a/tests/src/unit-bjdata.cpp b/tests/src/unit-bjdata.cpp index f94d808ae..629fc9d07 100644 --- a/tests/src/unit-bjdata.cpp +++ b/tests/src/unit-bjdata.cpp @@ -3491,6 +3491,11 @@ TEST_CASE("BJData") TEST_CASE("issue #5405 - array reserve for definite-length BJData arrays") { +#if !defined(JSON_NOEXCEPTION) + // this SECTION relies on catching a thrown exception to distinguish + // which of two acceptable, bounded rejections a hostile header took; + // under JSON_NOEXCEPTION, JSON_THROW never produces a catchable C++ + // exception (it aborts instead), so this cannot be tested that way here SECTION("a huge claimed length with no element data must not over-allocate") { // optimized form [$type#count: type 'i' (int8), count as a four-byte @@ -3529,16 +3534,32 @@ TEST_CASE("issue #5405 - array reserve for definite-length BJData arrays") CHECK(std::string(e.what()).find("excessive array size") != std::string::npos); } CHECK(threw); - CHECK(json::from_bjdata(input, true, false).is_discarded()); + + // json_sax_dom_parser::start_array()'s max_size() check (unlike the + // scanner's own parse_error path) throws unconditionally via + // JSON_THROW rather than going through sax->parse_error(), so it is + // not gated by allow_exceptions=false on a platform where this + // header hits that check (e.g. 32-bit, see above) -- allow either + // a discarded result or the same out_of_range it throws with + // exceptions enabled. + try + { + CHECK(json::from_bjdata(input, true, false).is_discarded()); + } + catch (const json::out_of_range& e) + { + CHECK(e.id == 408); + } } +#endif SECTION("arrays of various sizes decode to the same value as before the reserve optimization") { for (const auto size : { - std::size_t(0), std::size_t(1), std::size_t(5), // small - std::size_t(16384), // exactly at the reserve cap - std::size_t(20000) // above the reserve cap + std::size_t{0}, std::size_t{1}, std::size_t{5}, // small + std::size_t{16384}, // exactly at the reserve cap + std::size_t{20000} // above the reserve cap }) { CAPTURE(size) diff --git a/tests/src/unit-cbor.cpp b/tests/src/unit-cbor.cpp index 3a62fb768..90437ac12 100644 --- a/tests/src/unit-cbor.cpp +++ b/tests/src/unit-cbor.cpp @@ -2037,6 +2037,11 @@ TEST_CASE("CBOR definite length equal to the indefinite-length sentinel") TEST_CASE("issue #5405 - array reserve for definite-length CBOR arrays") { +#if !defined(JSON_NOEXCEPTION) + // this SECTION relies on catching a thrown exception to distinguish + // which of two acceptable, bounded rejections a hostile header took; + // under JSON_NOEXCEPTION, JSON_THROW never produces a catchable C++ + // exception (it aborts instead), so this cannot be tested that way here SECTION("a huge claimed length with no element data must not over-allocate") { // 0x9A: array with a four-byte length; claims 0xFFFFFFFF (4294967295) @@ -2077,6 +2082,7 @@ TEST_CASE("issue #5405 - array reserve for definite-length CBOR arrays") CHECK(threw); CHECK(json::from_cbor(input, true, false).is_discarded()); } +#endif SECTION("arrays of various sizes decode to the same value as before the reserve optimization") { diff --git a/tests/src/unit-msgpack.cpp b/tests/src/unit-msgpack.cpp index 1823a1d7e..c57f29299 100644 --- a/tests/src/unit-msgpack.cpp +++ b/tests/src/unit-msgpack.cpp @@ -1599,6 +1599,11 @@ TEST_CASE("MessagePack") TEST_CASE("issue #5405 - array reserve for definite-length MessagePack arrays") { +#if !defined(JSON_NOEXCEPTION) + // this SECTION relies on catching a thrown exception to distinguish + // which of two acceptable, bounded rejections a hostile header took; + // under JSON_NOEXCEPTION, JSON_THROW never produces a catchable C++ + // exception (it aborts instead), so this cannot be tested that way here SECTION("a huge claimed length with no element data must not over-allocate") { // 0xdd: array 32 (four-byte length); claims 0xFFFFFFFF (4294967295) @@ -1638,14 +1643,15 @@ TEST_CASE("issue #5405 - array reserve for definite-length MessagePack arrays") CHECK(threw); CHECK(json::from_msgpack(input, true, false).is_discarded()); } +#endif SECTION("arrays of various sizes decode to the same value as before the reserve optimization") { for (const auto size : { - std::size_t(0), std::size_t(1), std::size_t(5), // small - std::size_t(16384), // exactly at the reserve cap - std::size_t(20000) // above the reserve cap + std::size_t{0}, std::size_t{1}, std::size_t{5}, // small + std::size_t{16384}, // exactly at the reserve cap + std::size_t{20000} // above the reserve cap }) { CAPTURE(size) diff --git a/tests/src/unit-ubjson.cpp b/tests/src/unit-ubjson.cpp index dee41edf9..a07df4cef 100644 --- a/tests/src/unit-ubjson.cpp +++ b/tests/src/unit-ubjson.cpp @@ -2151,6 +2151,11 @@ TEST_CASE("UBJSON") TEST_CASE("issue #5405 - array reserve for definite-length UBJSON arrays") { +#if !defined(JSON_NOEXCEPTION) + // this SECTION relies on catching a thrown exception to distinguish + // which of two acceptable, bounded rejections a hostile header took; + // under JSON_NOEXCEPTION, JSON_THROW never produces a catchable C++ + // exception (it aborts instead), so this cannot be tested that way here SECTION("a huge claimed length with no element data must not over-allocate") { // optimized form [$type#count: type 'i' (int8), count as a four-byte @@ -2189,16 +2194,32 @@ TEST_CASE("issue #5405 - array reserve for definite-length UBJSON arrays") CHECK(std::string(e.what()).find("excessive array size") != std::string::npos); } CHECK(threw); - CHECK(json::from_ubjson(input, true, false).is_discarded()); + + // json_sax_dom_parser::start_array()'s max_size() check (unlike the + // scanner's own parse_error path) throws unconditionally via + // JSON_THROW rather than going through sax->parse_error(), so it is + // not gated by allow_exceptions=false on a platform where this + // header hits that check (e.g. 32-bit, see above) -- allow either + // a discarded result or the same out_of_range it throws with + // exceptions enabled. + try + { + CHECK(json::from_ubjson(input, true, false).is_discarded()); + } + catch (const json::out_of_range& e) + { + CHECK(e.id == 408); + } } +#endif SECTION("arrays of various sizes decode to the same value as before the reserve optimization") { for (const auto size : { - std::size_t(0), std::size_t(1), std::size_t(5), // small - std::size_t(16384), // exactly at the reserve cap - std::size_t(20000) // above the reserve cap + std::size_t{0}, std::size_t{1}, std::size_t{5}, // small + std::size_t{16384}, // exactly at the reserve cap + std::size_t{20000} // above the reserve cap }) { CAPTURE(size)