Bound JSON-RPC input by size and nesting depth

Enforce the nesting bound in one parse, capping allocation by depth, not input size.
This commit is contained in:
xirvik
2026-09-19 02:11:20 +00:00
committed by Jari Sundell
parent 83b03c2c1e
commit 439d23ce62
5 changed files with 130 additions and 1 deletions
+39 -1
View File
@@ -2,8 +2,10 @@
#include "rpc/jsonrpc.h"
#include <cstddef>
#include <cstdint>
#include <string>
#include <utility>
#include <torrent/common.h>
#include <torrent/torrent.h>
#include <torrent/utils/string_manip.h>
@@ -223,13 +225,49 @@ handle_notification(const json& request) noexcept {
}
}
namespace {
using json_input_adapter = decltype(nlohmann::detail::input_adapter(std::declval<const char*>(), std::declval<const char*>()));
using json_dom_parser = nlohmann::detail::json_sax_dom_parser<json, json_input_adapter>;
class json_depth_limited_parser : public json_dom_parser {
public:
explicit json_depth_limited_parser(json& root) : json_dom_parser(root) {}
bool start_object(std::size_t length) { return enter() && json_dom_parser::start_object(length); }
bool start_array(std::size_t length) { return enter() && json_dom_parser::start_array(length); }
bool end_object() { m_depth--; return json_dom_parser::end_object(); }
bool end_array() { m_depth--; return json_dom_parser::end_array(); }
private:
bool enter() { return ++m_depth <= max_json_depth; }
uint32_t m_depth{0};
};
} // namespace
bool
JsonRpc::process(const char* in_buffer, uint32_t length, slot_write callback) {
json response;
json body;
if (length > m_size_limit) {
auto err_str = json_error(JSONRPC_INVALID_REQUEST_ERROR, "content size exceeds maximum RPC limit", nullptr).dump();
return callback(err_str.c_str(), err_str.size());
}
try {
body = json::parse(in_buffer, in_buffer + length);
json_depth_limited_parser handler(body);
if (!json::sax_parse(in_buffer, in_buffer + length, &handler)) {
auto err_str = json_error(JSONRPC_INVALID_REQUEST_ERROR, "maximum nesting depth exceeded", nullptr).dump();
return callback(err_str.c_str(), err_str.size());
}
switch (body.type()) {
case json::value_t::object: {
if (!body.contains("id")) {