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 c7b402eff5
commit 14a0f00400
Notes: github-actions[bot] 2026-03-27 14:30:48 +00:00
3 changed files with 43 additions and 0 deletions

View File

@@ -8,6 +8,7 @@
#include <AK/JsonArray.h>
#include <AK/JsonObject.h>
#include <AK/JsonParser.h>
#include <AK/ScopeGuard.h>
#include <AK/StringConversions.h>
#include <math.h>
@@ -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;
ErrorOr<JsonValue> JsonParser::parse(StringView input)
{
JsonParser parser(input);
@@ -137,6 +140,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 '{'");
@@ -168,6 +176,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

@@ -32,6 +32,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

@@ -319,6 +319,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 {
};