From a3b1af96e0afc8790108dcf346cd77571c3fc921 Mon Sep 17 00:00:00 2001 From: RubenKelevra Date: Sun, 1 Mar 2026 10:19:07 +0100 Subject: [PATCH] AK: Guard JSON parser against deep nesting Add a maximum nesting depth check in JsonParser so deeply nested arrays/objects from untrusted input cannot blow the call stack. Add regression tests for excessive nesting rejection and reasonable nesting acceptance. --- AK/JsonParser.cpp | 13 +++++++++++++ AK/JsonParser.h | 4 ++++ Tests/AK/TestJSON.cpp | 26 ++++++++++++++++++++++++++ 3 files changed, 43 insertions(+) diff --git a/AK/JsonParser.cpp b/AK/JsonParser.cpp index 93e068c9123..04d4d1f5699 100644 --- a/AK/JsonParser.cpp +++ b/AK/JsonParser.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include namespace AK { @@ -18,6 +19,8 @@ constexpr bool is_space(int ch) return ch == '\t' || ch == '\n' || ch == '\r' || ch == ' '; } +constexpr StringView exceeded_max_nesting_depth_error = "JsonParser: Exceeded maximum nesting depth"sv; + // ECMA-404 9 String // Boils down to // STRING = "\"" *("[^\"\\]" | "\\" ("[\"\\bfnrt]" | "u[0-9A-Za-z]{4}")) "\"" @@ -131,6 +134,11 @@ ErrorOr JsonParser::consume_and_unescape_string() ErrorOr JsonParser::parse_object() { + if (m_current_nesting_depth >= max_nesting_depth) + return Error::from_string_view(exceeded_max_nesting_depth_error); + ++m_current_nesting_depth; + ScopeGuard nesting_depth_guard { [this] { --m_current_nesting_depth; } }; + JsonObject object; if (!consume_specific('{')) return Error::from_string_literal("JsonParser: Expected '{'"); @@ -162,6 +170,11 @@ ErrorOr JsonParser::parse_object() ErrorOr JsonParser::parse_array() { + if (m_current_nesting_depth >= max_nesting_depth) + return Error::from_string_view(exceeded_max_nesting_depth_error); + ++m_current_nesting_depth; + ScopeGuard nesting_depth_guard { [this] { --m_current_nesting_depth; } }; + JsonArray array; if (!consume_specific('[')) return Error::from_string_literal("JsonParser: Expected '['"); diff --git a/AK/JsonParser.h b/AK/JsonParser.h index 88b373bfaf9..c3cdc7a5828 100644 --- a/AK/JsonParser.h +++ b/AK/JsonParser.h @@ -31,6 +31,10 @@ private: ErrorOr parse_false(); ErrorOr parse_true(); ErrorOr parse_null(); + + // Keep recursive parsing depth bounded so untrusted JSON cannot overflow the call stack. + static constexpr size_t max_nesting_depth { 512 }; + size_t m_current_nesting_depth { 0 }; }; } diff --git a/Tests/AK/TestJSON.cpp b/Tests/AK/TestJSON.cpp index eb1f68ec2cb..30e9187c13a 100644 --- a/Tests/AK/TestJSON.cpp +++ b/Tests/AK/TestJSON.cpp @@ -321,6 +321,32 @@ TEST_CASE(json_parse_fails_on_invalid_number) #undef EXPECT_JSON_PARSE_TO_FAIL } +TEST_CASE(json_parse_rejects_excessive_nesting_depth) +{ + StringBuilder input; + constexpr size_t nesting_depth = 4096; + + input.append_repeated('[', nesting_depth); + input.append('0'); + input.append_repeated(']', nesting_depth); + + auto parsed = JsonValue::from_string(input.string_view()); + EXPECT(parsed.is_error()); +} + +TEST_CASE(json_parse_accepts_reasonable_nesting_depth) +{ + StringBuilder input; + constexpr size_t nesting_depth = 64; + + input.append_repeated('[', nesting_depth); + input.append('0'); + input.append_repeated(']', nesting_depth); + + auto parsed = JsonValue::from_string(input.string_view()); + EXPECT(!parsed.is_error()); +} + struct CustomError { };