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.
This commit is contained in:
RubenKelevra
2026-03-01 10:19:07 +01:00
committed by Sam Atkins
parent 4c8188d6de
commit a3b1af96e0
3 changed files with 43 additions and 0 deletions

View File

@@ -9,6 +9,7 @@
#include <AK/JsonArray.h>
#include <AK/JsonObject.h>
#include <AK/JsonParser.h>
#include <AK/ScopeGuard.h>
#include <math.h>
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<ByteString> JsonParser::consume_and_unescape_string()
ErrorOr<JsonValue> 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<JsonValue> JsonParser::parse_object()
ErrorOr<JsonValue> 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 '['");

View File

@@ -31,6 +31,10 @@ private:
ErrorOr<JsonValue> parse_false();
ErrorOr<JsonValue> parse_true();
ErrorOr<JsonValue> 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 };
};
}

View File

@@ -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 {
};