From e8bdbaa1e8e75a9d91b45284610d454f07a2108d Mon Sep 17 00:00:00 2001 From: Alex Kremer Date: Wed, 13 May 2026 13:42:05 +0100 Subject: [PATCH] refactor: Limit JSON array size (#7112) --- include/xrpl/protocol/STParsedJSON.h | 7 ++++ src/libxrpl/protocol/STParsedJSON.cpp | 56 ++++++++++++++++++++------- 2 files changed, 50 insertions(+), 13 deletions(-) diff --git a/include/xrpl/protocol/STParsedJSON.h b/include/xrpl/protocol/STParsedJSON.h index d5b4f33be7..04ffc624fb 100644 --- a/include/xrpl/protocol/STParsedJSON.h +++ b/include/xrpl/protocol/STParsedJSON.h @@ -6,6 +6,13 @@ namespace xrpl { +/** Maximum JSON object nesting depth permitted during parsing. */ +inline constexpr std::size_t kMAX_PARSED_JSON_DEPTH = 64; + +/** Maximum number of elements permitted in any JSON array field during parsing. + Requests exceeding this limit are rejected with an invalidParams error. */ +inline constexpr std::size_t kMAX_PARSED_JSON_ARRAY_SIZE = 512; + /** Holds the serialized result of parsing an input JSON object. This does validation and checking on the provided JSON. */ diff --git a/src/libxrpl/protocol/STParsedJSON.cpp b/src/libxrpl/protocol/STParsedJSON.cpp index 52d7b4d63e..c6d1d0e805 100644 --- a/src/libxrpl/protocol/STParsedJSON.cpp +++ b/src/libxrpl/protocol/STParsedJSON.cpp @@ -136,6 +136,15 @@ arrayExpected(std::string const& object, std::string const& field) RpcInvalidParams, "Field '" + makeName(object, field) + "' must be a JSON array."); } +static inline json::Value +arrayTooBig(std::string const& object, std::string const& field) +{ + return RPC::makeError( + RpcInvalidParams, + "Field '" + makeName(object, field) + "' exceeds allowed JSON array size of " + + std::to_string(kMAX_PARSED_JSON_ARRAY_SIZE) + " elements per field."); +} + static inline json::Value stringExpected(std::string const& object, std::string const& field) { @@ -681,12 +690,18 @@ parseLeaf( break; case STI_VECTOR256: - if (!value.isArrayOrNull()) + if (not value.isArrayOrNull()) { error = arrayExpected(jsonName, fieldName); return ret; } + if (not value.isNull() and value.size() > kMAX_PARSED_JSON_ARRAY_SIZE) + { + error = arrayTooBig(jsonName, fieldName); + return ret; + } + try { STVector256 tail(field); @@ -708,12 +723,18 @@ parseLeaf( break; case STI_PATHSET: - if (!value.isArrayOrNull()) + if (not value.isArrayOrNull()) { error = arrayExpected(jsonName, fieldName); return ret; } + if (not value.isNull() and value.size() > kMAX_PARSED_JSON_ARRAY_SIZE) + { + error = arrayTooBig(jsonName, fieldName); + return ret; + } + try { STPathSet tail(field); @@ -722,7 +743,7 @@ parseLeaf( { STPath p; - if (!value[i].isArrayOrNull()) + if (not value[i].isArrayOrNull()) { std::stringstream ss; ss << fieldName << "[" << i << "]"; @@ -730,6 +751,14 @@ parseLeaf( return ret; } + if (not value[i].isNull() and value[i].size() > kMAX_PARSED_JSON_ARRAY_SIZE) + { + std::stringstream ss; + ss << fieldName << "[" << i << "]"; + error = arrayTooBig(jsonName, ss.str()); + return ret; + } + for (json::UInt j = 0; value[i].isValidIndex(j); ++j) { std::stringstream ss; @@ -946,8 +975,6 @@ parseLeaf( return ret; } -static int const kMAX_DEPTH = 64; - // Forward declaration since parseObject() and parseArray() call each other. static std::optional parseArray( @@ -965,13 +992,13 @@ parseObject( int depth, json::Value& error) { - if (!json.isObjectOrNull()) + if (not json.isObjectOrNull()) { error = notAnObject(jsonName); return std::nullopt; } - if (depth > kMAX_DEPTH) + if (depth > kMAX_PARSED_JSON_DEPTH) { error = tooDeep(jsonName); return std::nullopt; @@ -984,7 +1011,6 @@ parseObject( for (auto const& fieldName : json.getMemberNames()) { json::Value const& value = json[fieldName]; - auto const& field = SField::getField(fieldName); if (field == kSF_INVALID) @@ -1079,18 +1105,24 @@ parseArray( int depth, json::Value& error) { - if (!json.isArrayOrNull()) + if (not json.isArrayOrNull()) { error = notAnArray(jsonName); return std::nullopt; } - if (depth > kMAX_DEPTH) + if (depth > kMAX_PARSED_JSON_DEPTH) { error = tooDeep(jsonName); return std::nullopt; } + if (not json.isNull() and json.size() > kMAX_PARSED_JSON_ARRAY_SIZE) + { + error = arrayTooBig(jsonName, ""); + return std::nullopt; + } + try { STArray tail(inName); @@ -1108,10 +1140,8 @@ parseArray( } // TODO: There doesn't seem to be a nice way to get just the - // first/only key in an object without copying all keys into - // a vector + // first/only key in an object without copying all keys into a vector std::string const memberName(json[i].getMemberNames()[0]); - ; auto const& nameField(SField::getField(memberName)); if (nameField == kSF_INVALID)