Skip to content

Commit 5bacd3c

Browse files
committed
cap recursion depth in yaml and toml generic readers
1 parent 8391961 commit 5bacd3c

6 files changed

Lines changed: 88 additions & 0 deletions

File tree

include/glaze/core/context.hpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,32 @@ namespace glz
132132
{ ctx.depth } -> std::same_as<uint32_t&>;
133133
};
134134

135+
// RAII guard for recursive descent: bumps ctx.depth on entry and restores it on exit,
136+
// erroring out before the nesting reaches max_recursive_depth_limit so adversarial deeply
137+
// nested input can't overflow the stack. Mirrors the per-reader guards already used by the
138+
// BSON and JSONB binary readers; placed here so the text-format readers can share one copy.
139+
template <class Ctx>
140+
struct depth_guard
141+
{
142+
Ctx& ctx;
143+
bool entered = false;
144+
145+
depth_guard(Ctx& c) noexcept : ctx(c)
146+
{
147+
if (ctx.depth >= max_recursive_depth_limit) [[unlikely]] {
148+
ctx.error = error_code::exceeded_max_recursive_depth;
149+
return;
150+
}
151+
++ctx.depth;
152+
entered = true;
153+
}
154+
~depth_guard()
155+
{
156+
if (entered) --ctx.depth;
157+
}
158+
explicit operator bool() const noexcept { return entered; }
159+
};
160+
135161
// Runtime constraint concepts
136162
// These detect if a user-defined context has runtime constraint fields.
137163
// Users can inherit from glz::context and add these fields for runtime limits:

include/glaze/toml/read.hpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2689,6 +2689,13 @@ namespace glz
26892689
return;
26902690
}
26912691

2692+
// Nested arrays and inline tables recurse back through this reader, so cap the depth
2693+
// here; a deeply nested value (e.g. "a=[[[[...") otherwise overflows the stack.
2694+
depth_guard guard{ctx};
2695+
if (!guard) [[unlikely]] {
2696+
return;
2697+
}
2698+
26922699
skip_ws_and_comments(it, end);
26932700

26942701
if (it == end) [[unlikely]] {

include/glaze/yaml/common.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,9 @@ namespace glz::yaml
118118
c.allow_indentless_sequence = allow_indentless_sequence;
119119
c.stream_begin = stream_begin;
120120
c.secondary_tag_handle_overridden = secondary_tag_handle_overridden;
121+
// Carry the recursion depth so a speculative type-probe shares the parent's budget
122+
// and can't reset the stack-overflow guard partway down a deeply nested value.
123+
c.depth = depth;
121124
return c;
122125
}
123126
};

include/glaze/yaml/read.hpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5208,6 +5208,13 @@ namespace glz
52085208
if (bool(ctx.error)) [[unlikely]]
52095209
return;
52105210

5211+
// Every nested generic/variant value routes back through this reader, so bounding
5212+
// depth here caps flow-collection and flow-embedded mapping recursion that the
5213+
// indent stack does not cover.
5214+
depth_guard guard{ctx};
5215+
if (!guard) [[unlikely]]
5216+
return;
5217+
52115218
// At root level (indent == -1), skip leading whitespace, newlines, and comments
52125219
// For nested values, only skip inline whitespace to preserve block structure
52135220
if (ctx.current_indent() < 0) {

tests/toml_test/toml_test.cpp

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5787,4 +5787,23 @@ suite issue_2595_transparent_wrappers = [] {
57875787
};
57885788
};
57895789

5790+
suite recursion_depth_tests = [] {
5791+
"deeply nested array is bounded"_test = [] {
5792+
const std::string toml = "a = " + std::string(100000, '[');
5793+
glz::generic value{};
5794+
auto ec = glz::read_toml(value, toml);
5795+
expect(ec.ec == glz::error_code::exceeded_max_recursive_depth);
5796+
};
5797+
5798+
"nesting within the limit still parses"_test = [] {
5799+
const std::string toml = "a = [1, [2, [3]]]";
5800+
glz::generic value{};
5801+
auto ec = glz::read_toml(value, toml);
5802+
expect(!ec) << glz::format_error(ec, toml);
5803+
std::string json{};
5804+
expect(!glz::write_json(value, json));
5805+
expect(json == R"({"a":[1,[2,[3]]]})") << json;
5806+
};
5807+
};
5808+
57905809
int main() { return 0; }

tests/yaml_test/yaml_test.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10029,4 +10029,30 @@ suite issue_2595_transparent_wrappers = [] {
1002910029
};
1003010030
};
1003110031

10032+
suite recursion_depth_tests = [] {
10033+
"deeply nested flow sequence is bounded"_test = [] {
10034+
const std::string yaml(100000, '[');
10035+
glz::generic value{};
10036+
auto ec = glz::read_yaml(value, yaml);
10037+
expect(ec.ec == glz::error_code::exceeded_max_recursive_depth);
10038+
};
10039+
10040+
"deeply nested flow mapping is bounded"_test = [] {
10041+
const std::string yaml(100000, '{');
10042+
glz::generic value{};
10043+
auto ec = glz::read_yaml(value, yaml);
10044+
expect(ec.ec == glz::error_code::exceeded_max_recursive_depth);
10045+
};
10046+
10047+
"nesting within the limit still parses"_test = [] {
10048+
const std::string yaml = "[1, 2, [3, [4, 5]]]";
10049+
glz::generic value{};
10050+
auto ec = glz::read_yaml(value, yaml);
10051+
expect(!ec) << glz::format_error(ec, yaml);
10052+
std::string json{};
10053+
expect(!glz::write_json(value, json));
10054+
expect(json == "[1,2,[3,[4,5]]]") << json;
10055+
};
10056+
};
10057+
1003210058
int main() { return 0; }

0 commit comments

Comments
 (0)