diff --git a/src/test_lib_json/fuzz.cpp b/src/test_lib_json/fuzz.cpp index 3679a95ec..d79f80209 100644 --- a/src/test_lib_json/fuzz.cpp +++ b/src/test_lib_json/fuzz.cpp @@ -6,6 +6,7 @@ #include "fuzz.h" #include +#include #include #include #include @@ -25,17 +26,29 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { data += sizeof(uint32_t); size -= sizeof(uint32_t); - builder.settings_["failIfExtra"] = hash_settings & (1 << 0); - builder.settings_["allowComments_"] = hash_settings & (1 << 1); - builder.settings_["strictRoot_"] = hash_settings & (1 << 2); - builder.settings_["allowDroppedNullPlaceholders_"] = hash_settings & (1 << 3); - builder.settings_["allowNumericKeys_"] = hash_settings & (1 << 4); - builder.settings_["allowSingleQuotes_"] = hash_settings & (1 << 5); - builder.settings_["failIfExtra_"] = hash_settings & (1 << 6); - builder.settings_["rejectDupKeys_"] = hash_settings & (1 << 7); - builder.settings_["allowSpecialFloats_"] = hash_settings & (1 << 8); - builder.settings_["collectComments"] = hash_settings & (1 << 9); - builder.settings_["allowTrailingCommas_"] = hash_settings & (1 << 10); + builder.settings_["failIfExtra"] = (hash_settings & (1 << 0)) != 0; + builder.settings_["allowComments"] = (hash_settings & (1 << 1)) != 0; + builder.settings_["strictRoot"] = (hash_settings & (1 << 2)) != 0; + builder.settings_["allowDroppedNullPlaceholders"] = + (hash_settings & (1 << 3)) != 0; + builder.settings_["allowNumericKeys"] = (hash_settings & (1 << 4)) != 0; + builder.settings_["allowSingleQuotes"] = (hash_settings & (1 << 5)) != 0; + builder.settings_["skipBom"] = (hash_settings & (1 << 6)) != 0; + builder.settings_["rejectDupKeys"] = (hash_settings & (1 << 7)) != 0; + builder.settings_["allowSpecialFloats"] = (hash_settings & (1 << 8)) != 0; + builder.settings_["collectComments"] = (hash_settings & (1 << 9)) != 0; + builder.settings_["allowTrailingCommas"] = (hash_settings & (1 << 10)) != 0; + // Bit 11 selects a small stack limit taken from the upper 16 bits, so that + // the depth-limit error paths are reachable without huge inputs. + if (hash_settings & (1 << 11)) { + builder.settings_["stackLimit"] = (hash_settings >> 16) & 0x3FF; + } + + // An unknown key is silently ignored by newCharReader(), so a typo here + // would quietly stop the fuzzer from exercising that setting. + if (!builder.validate(nullptr)) { + std::abort(); + } std::unique_ptr reader(builder.newCharReader()); diff --git a/src/test_lib_json/main.cpp b/src/test_lib_json/main.cpp index e09b87b84..b1c884aa0 100644 --- a/src/test_lib_json/main.cpp +++ b/src/test_lib_json/main.cpp @@ -4324,11 +4324,28 @@ struct FuzzTest : JsonTest::TestCase {}; // Build and run the fuzz test without any fuzzer, so that it's guaranteed not // go out of date, even if it's never run as an actual fuzz test. JSONTEST_FIXTURE_LOCAL(FuzzTest, fuzzDoesntCrash) { - const std::string example = "{}"; - JSONTEST_ASSERT_EQUAL( - 0, - LLVMFuzzerTestOneInput(reinterpret_cast(example.c_str()), - example.size())); + // The first four bytes select the reader settings (little-endian). + const auto withSettings = [](uint32_t settings, const std::string& doc) { + std::string input; + for (int i = 0; i < 4; ++i) { + input += static_cast((settings >> (8 * i)) & 0xFF); + } + return input + doc; + }; + const std::string examples[] = { + "{}", + withSettings(0, "{\"a\":[1,2]}"), + // Every boolean setting on, with a stack limit of 16. + withSettings(0x0010FFFF, "{\"a\":[1,2,],} // comment"), + // A stack limit of 1, so the nested arrays exceed it. + withSettings(0x00010800, "[[[[1]]]]"), + }; + for (const auto& example : examples) { + JSONTEST_ASSERT_EQUAL( + 0, + LLVMFuzzerTestOneInput(reinterpret_cast(example.data()), + example.size())); + } } struct ParseWithStructuredErrorsTest : JsonTest::TestCase {