Commit 41660160 authored by Dominik Charousset's avatar Dominik Charousset

Fix stack overflow when parsing deeply nested JSON

(cherry picked from commit 0904018c)
parent 44db1160
......@@ -10,6 +10,8 @@ is based on [Keep a Changelog](https://keepachangelog.com).
- The JSON parser no longer chokes when encountering `null` as last value before
the closing parenthesis.
- The JSON reader now automatically widens integers to doubles as necessary.
- Parsing deeply nested JSON inputs no longer produces a stack overflow.
Instead, the parser rejects any JSON with too many nesting levels.
## [0.18.6]
......
......@@ -66,6 +66,8 @@ enum class pec : uint8_t {
/// Stopped after running into an invalid parser state. Should never happen
/// and most likely indicates a bug in the implementation.
invalid_state,
/// Parser stopped after exceeding its maximum supported level of nesting.
nested_too_deeply,
};
/// @relates pec
......
......@@ -19,6 +19,12 @@ CAF_PUSH_UNUSED_LABEL_WARNING
#include "caf/detail/parser/fsm.hpp"
namespace {
constexpr size_t max_nesting_level = 128;
} // namespace
namespace caf::detail::parser {
struct obj_consumer;
......@@ -93,7 +99,8 @@ obj_consumer val_consumer::begin_object() {
return {&obj};
}
void read_value(string_parser_state& ps, val_consumer consumer);
void read_value(string_parser_state& ps, size_t nesting_level,
val_consumer consumer);
template <class Consumer>
void read_json_null_or_nan(string_parser_state& ps, Consumer consumer) {
......@@ -159,7 +166,8 @@ void read_json_string(string_parser_state& ps, Consumer consumer) {
// clang-format on
}
void read_member(string_parser_state& ps, member_consumer consumer) {
void read_member(string_parser_state& ps, size_t nesting_level,
member_consumer consumer) {
// clang-format off
start();
state(init) {
......@@ -168,7 +176,8 @@ void read_member(string_parser_state& ps, member_consumer consumer) {
}
state(after_key) {
transition(after_key, " \t\n")
fsm_transition(read_value(ps, consumer.begin_val()), done, ':')
fsm_transition(read_value(ps, nesting_level, consumer.begin_val()),
done, ':')
}
term_state(done) {
transition(done, " \t\n")
......@@ -177,7 +186,12 @@ void read_member(string_parser_state& ps, member_consumer consumer) {
// clang-format on
}
void read_json_object(string_parser_state& ps, obj_consumer consumer) {
void read_json_object(string_parser_state& ps, size_t nesting_level,
obj_consumer consumer) {
if (nesting_level >= max_nesting_level) {
ps.code = pec::nested_too_deeply;
return;
}
// clang-format off
start();
state(init) {
......@@ -186,7 +200,8 @@ void read_json_object(string_parser_state& ps, obj_consumer consumer) {
}
state(has_open_brace) {
transition(has_open_brace, " \t\n")
fsm_epsilon(read_member(ps, consumer.begin_member()), after_member, '"')
fsm_epsilon(read_member(ps, nesting_level + 1, consumer.begin_member()),
after_member, '"')
transition(done, '}')
}
state(after_member) {
......@@ -196,7 +211,8 @@ void read_json_object(string_parser_state& ps, obj_consumer consumer) {
}
state(after_comma) {
transition(after_comma, " \t\n")
fsm_epsilon(read_member(ps, consumer.begin_member()), after_member, '"')
fsm_epsilon(read_member(ps, nesting_level + 1, consumer.begin_member()),
after_member, '"')
}
term_state(done) {
transition(done, " \t\n")
......@@ -205,7 +221,12 @@ void read_json_object(string_parser_state& ps, obj_consumer consumer) {
// clang-format on
}
void read_json_array(string_parser_state& ps, arr_consumer consumer) {
void read_json_array(string_parser_state& ps, size_t nesting_level,
arr_consumer consumer) {
if (nesting_level >= max_nesting_level) {
ps.code = pec::nested_too_deeply;
return;
}
// clang-format off
start();
state(init) {
......@@ -215,7 +236,8 @@ void read_json_array(string_parser_state& ps, arr_consumer consumer) {
state(has_open_brace) {
transition(has_open_brace, " \t\n")
transition(done, ']')
fsm_epsilon(read_value(ps, consumer.begin_value()), after_value)
fsm_epsilon(read_value(ps, nesting_level + 1, consumer.begin_value()),
after_value)
}
state(after_value) {
transition(after_value, " \t\n")
......@@ -224,7 +246,8 @@ void read_json_array(string_parser_state& ps, arr_consumer consumer) {
}
state(after_comma) {
transition(after_comma, " \t\n")
fsm_epsilon(read_value(ps, consumer.begin_value()), after_value)
fsm_epsilon(read_value(ps, nesting_level + 1, consumer.begin_value()),
after_value)
}
term_state(done) {
transition(done, " \t\n")
......@@ -233,7 +256,8 @@ void read_json_array(string_parser_state& ps, arr_consumer consumer) {
// clang-format on
}
void read_value(string_parser_state& ps, val_consumer consumer) {
void read_value(string_parser_state& ps, size_t nesting_level,
val_consumer consumer) {
// clang-format off
start();
state(init) {
......@@ -242,8 +266,10 @@ void read_value(string_parser_state& ps, val_consumer consumer) {
fsm_epsilon(read_bool(ps, consumer), done, "ft")
fsm_epsilon(read_json_null_or_nan(ps, consumer), done, "n")
fsm_epsilon(read_number(ps, consumer), done, "+-.0123456789")
fsm_epsilon(read_json_object(ps, consumer.begin_object()), done, '{')
fsm_epsilon(read_json_array(ps, consumer.begin_array()), done, '[')
fsm_epsilon(read_json_object(ps, nesting_level, consumer.begin_object()),
done, '{')
fsm_epsilon(read_json_array(ps, nesting_level, consumer.begin_array()),
done, '[')
}
term_state(done) {
transition(done, " \t\n")
......@@ -298,7 +324,7 @@ object* make_object(monotonic_buffer_resource* storage) {
value* parse(string_parser_state& ps, monotonic_buffer_resource* storage) {
monotonic_buffer_resource::allocator<value> alloc{storage};
auto result = new (alloc.allocate(1)) value();
parser::read_value(ps, {storage, result});
parser::read_value(ps, 0, {storage, result});
return result;
}
......
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