mirror of
https://github.com/SerenityOS/serenity
synced 2026-04-25 17:15:42 +02:00
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:
@@ -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 '['");
|
||||
|
||||
@@ -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 };
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user