diff --git a/include/xrpl/protocol/STParsedJSON.h b/include/xrpl/protocol/STParsedJSON.h index 9c770fe94d..3ca4881de0 100644 --- a/include/xrpl/protocol/STParsedJSON.h +++ b/include/xrpl/protocol/STParsedJSON.h @@ -26,6 +26,13 @@ namespace ripple { +/** Maximum JSON object nesting depth permitted during parsing. */ +inline constexpr std::size_t maxSTParsedJSONDepth = 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 maxSTParsedJSONArraySize = 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 f99fec6b87..32613d8a8d 100644 --- a/src/libxrpl/protocol/STParsedJSON.cpp +++ b/src/libxrpl/protocol/STParsedJSON.cpp @@ -160,6 +160,16 @@ array_expected(std::string const& object, std::string const& field) "Field '" + make_name(object, field) + "' must be a JSON array."); } +static inline Json::Value +array_too_big(std::string const& object, std::string const& field) +{ + return RPC::make_error( + rpcINVALID_PARAMS, + "Field '" + make_name(object, field) + + "' exceeds allowed JSON array size of " + + std::to_string(maxSTParsedJSONArraySize) + " elements per field."); +} + static inline Json::Value string_expected(std::string const& object, std::string const& field) { @@ -743,6 +753,12 @@ parseLeaf( return ret; } + if (not value.isNull() and value.size() > maxSTParsedJSONArraySize) + { + error = array_too_big(json_name, fieldName); + return ret; + } + try { STVector256 tail(field); @@ -770,6 +786,12 @@ parseLeaf( return ret; } + if (not value.isNull() and value.size() > maxSTParsedJSONArraySize) + { + error = array_too_big(json_name, fieldName); + return ret; + } + try { STPathSet tail(field); @@ -786,6 +808,15 @@ parseLeaf( return ret; } + if (not value[i].isNull() and + value[i].size() > maxSTParsedJSONArraySize) + { + std::stringstream ss; + ss << fieldName << "[" << i << "]"; + error = array_too_big(json_name, ss.str()); + return ret; + } + for (Json::UInt j = 0; value[i].isValidIndex(j); ++j) { std::stringstream ss; @@ -980,8 +1011,6 @@ parseLeaf( return ret; } -static int const maxDepth = 64; - // Forward declaration since parseObject() and parseArray() call each other. static std::optional parseArray( @@ -1005,7 +1034,7 @@ parseObject( return std::nullopt; } - if (depth > maxDepth) + if (depth > maxSTParsedJSONDepth) { error = too_deep(json_name); return std::nullopt; @@ -1018,7 +1047,6 @@ parseObject( for (auto const& fieldName : json.getMemberNames()) { Json::Value const& value = json[fieldName]; - auto const& field = SField::getField(fieldName); if (field == sfInvalid) @@ -1128,12 +1156,18 @@ parseArray( return std::nullopt; } - if (depth > maxDepth) + if (depth > maxSTParsedJSONDepth) { error = too_deep(json_name); return std::nullopt; } + if (not json.isNull() and json.size() > maxSTParsedJSONArraySize) + { + error = array_too_big(json_name, ""); + return std::nullopt; + } + try { STArray tail(inName); @@ -1151,10 +1185,9 @@ 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 objectName(json[i].getMemberNames()[0]); - ; auto const& nameField(SField::getField(objectName)); if (nameField == sfInvalid)