refactor: Limit JSON array size (#7112)

This commit is contained in:
Alex Kremer
2026-05-13 13:42:05 +01:00
committed by GitHub
parent 6340c986c9
commit e8bdbaa1e8
2 changed files with 50 additions and 13 deletions

View File

@@ -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.
*/

View File

@@ -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<detail::STVar>
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)