Commit 42fa4609 authored by Kamal Marhubi's avatar Kamal Marhubi

Add maximum nesting depth on JsonCodec

parent 8a9a8ab2
......@@ -380,6 +380,45 @@ KJ_TEST("basic json decoding") {
}
}
KJ_TEST("maximum nesting depth") {
JsonCodec json;
auto input = kj::str(R"({"foo": "a", "bar": ["b", { "baz": [-5.5e11] }, [ [ 1 ], { "z": 2 }]]})");
// `input` has a maximum nesting depth of 4, reached 3 times.
{
MallocMessageBuilder message;
auto root = message.initRoot<JsonValue>();
json.decodeRaw(input, root);
}
{
json.setMaxNestingDepth(0);
MallocMessageBuilder message;
auto root = message.initRoot<JsonValue>();
KJ_EXPECT_THROW_MESSAGE("nest",
json.decodeRaw(input, root));
}
{
json.setMaxNestingDepth(3);
MallocMessageBuilder message;
auto root = message.initRoot<JsonValue>();
KJ_EXPECT_THROW_MESSAGE("nest",
json.decodeRaw(input, root));
}
{
json.setMaxNestingDepth(4);
MallocMessageBuilder message;
auto root = message.initRoot<JsonValue>();
json.decodeRaw(input, root);
}
}
class TestHandler: public JsonCodec::Handler<Text> {
public:
void encode(const JsonCodec& codec, Text::Reader input,
......
......@@ -47,6 +47,7 @@ struct FieldHash {
struct JsonCodec::Impl {
bool prettyPrint = false;
size_t maxNestingDepth = 64;
std::unordered_map<Type, HandlerBase*, TypeHash> typeHandlers;
std::unordered_map<StructSchema::Field, HandlerBase*, FieldHash> fieldHandlers;
......@@ -188,6 +189,10 @@ JsonCodec::~JsonCodec() noexcept(false) {}
void JsonCodec::setPrettyPrint(bool enabled) { impl->prettyPrint = enabled; }
void JsonCodec::setMaxNestingDepth(size_t maxNestingDepth) {
impl->maxNestingDepth = maxNestingDepth;
}
kj::String JsonCodec::encode(DynamicValue::Reader value, Type type) const {
MallocMessageBuilder message;
auto json = message.getRoot<JsonValue>();
......@@ -376,7 +381,9 @@ namespace {
class Parser {
public:
Parser(kj::ArrayPtr<const char> input) : input_(input), remaining_(input_) {}
Parser(size_t maxNestingDepth, kj::ArrayPtr<const char> input) :
maxNestingDepth_(maxNestingDepth), input_(input), remaining_(input_), nestingDepth_(0) {}
void parseValue(JsonValue::Builder& output) {
consumeWhitespace();
KJ_REQUIRE(remaining_.size() > 0, "JSON message ends prematurely.");
......@@ -415,6 +422,9 @@ public:
auto orphanage = Orphanage::getForMessageContaining(output);
consume('[');
KJ_REQUIRE(++nestingDepth_ <= maxNestingDepth_, "JSON message nested too deeply.", nestingDepth_);
KJ_DEFER(--nestingDepth_);
while (consumeWhitespace(), nextChar() != ']') {
auto orphan = orphanage.newOrphan<JsonValue>();
auto builder = orphan.get();
......@@ -442,6 +452,9 @@ public:
auto orphanage = Orphanage::getForMessageContaining(output);
consume('{');
KJ_REQUIRE(++nestingDepth_ <= maxNestingDepth_, "JSON message nested too deeply.", nestingDepth_);
KJ_DEFER(--nestingDepth_);
while (consumeWhitespace(), nextChar() != '}') {
auto orphan = orphanage.newOrphan<JsonValue::Field>();
auto builder = orphan.get();
......@@ -598,8 +611,10 @@ private:
static const kj::ArrayPtr<const char> FALSE;
static const kj::ArrayPtr<const char> TRUE;
const size_t maxNestingDepth_;
const kj::ArrayPtr<const char> input_;
kj::ArrayPtr<const char> remaining_;
size_t nestingDepth_;
}; // class Parser
......@@ -613,7 +628,7 @@ const kj::ArrayPtr<const char> Parser::TRUE = kj::ArrayPtr<const char>({'t','r',
void JsonCodec::decodeRaw(kj::ArrayPtr<const char> input, JsonValue::Builder output) const {
// TODO(security): should we check there are no non-whitespace characters left in input?
Parser parser(input);
Parser parser(impl->maxNestingDepth, input);
parser.parseValue(output);
}
......
......@@ -69,6 +69,10 @@ public:
// Enable to insert newlines, indentation, and other extra spacing into the output. The default
// is to use minimal whitespace.
void setMaxNestingDepth(size_t maxNestingDepth);
// Set maximum nesting depth when decoding JSON to prevent highly nested input from overflowing
// the call stack. The default is 64.
template <typename T>
kj::String encode(T&& value);
// Encode any Cap'n Proto value to JSON, including primitives and
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment