Skip to content

Commit 5f3eff2

Browse files
committed
Enforce nesting limit in scanner
MAX_NESTING_LEVEL was only checked in parser.c, so yaml_parser_scan() callers had no depth protection. Deep flow nesting caused O(n²) CPU via simple_keys scanning. Add the limit check in the scanner's yaml_parser_increase_flow_level() and a test.
1 parent 840b65c commit 5f3eff2

4 files changed

Lines changed: 98 additions & 2 deletions

File tree

src/scanner.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,12 @@
478478

479479
#include "yaml_private.h"
480480

481+
/*
482+
* Maximum nesting level (defined in parser.c).
483+
*/
484+
485+
extern int MAX_NESTING_LEVEL;
486+
481487
/*
482488
* Ensure that the buffer contains the required number of characters.
483489
* Return 1 on success, 0 on failure (reader error or memory error).
@@ -1175,6 +1181,12 @@ yaml_parser_increase_flow_level(yaml_parser_t *parser)
11751181
return 0;
11761182
}
11771183

1184+
if (parser->flow_level >= MAX_NESTING_LEVEL) {
1185+
return yaml_parser_set_scanner_error(parser,
1186+
"while increasing flow level", parser->mark,
1187+
"exceeded maximum nesting depth");
1188+
}
1189+
11781190
parser->flow_level++;
11791191

11801192
return 1;

tests/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ foreach(name IN ITEMS
1616
run-parser
1717
run-parser-test-suite
1818
run-scanner
19+
test-nesting
1920
test-reader
2021
test-version
2122
)
@@ -24,4 +25,5 @@ endforeach()
2425

2526
add_test(NAME version COMMAND test-version)
2627
add_test(NAME reader COMMAND test-reader)
28+
add_test(NAME nesting COMMAND test-nesting)
2729

tests/Makefile.am

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
AM_CPPFLAGS = -I$(top_srcdir)/include -Wall
22
#AM_CFLAGS = -Wno-pointer-sign
33
LDADD = $(top_builddir)/src/libyaml.la
4-
TESTS = test-version test-reader
5-
check_PROGRAMS = test-version test-reader
4+
TESTS = test-version test-reader test-nesting
5+
check_PROGRAMS = test-version test-reader test-nesting
66
noinst_PROGRAMS = run-scanner run-parser run-loader run-emitter run-dumper \
77
example-reformatter example-reformatter-alt \
88
example-deconstructor example-deconstructor-alt \

tests/test-nesting.c

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
#include <yaml.h>
2+
3+
#include <stdlib.h>
4+
#include <stdio.h>
5+
#include <string.h>
6+
7+
#ifdef NDEBUG
8+
#undef NDEBUG
9+
#endif
10+
#include <assert.h>
11+
12+
/*
13+
* Test that the scanner enforces the maximum nesting depth.
14+
*/
15+
16+
static int
17+
scan_string(const char *input, size_t length)
18+
{
19+
yaml_parser_t parser;
20+
yaml_token_t token;
21+
int done = 0;
22+
int error = 0;
23+
24+
assert(yaml_parser_initialize(&parser));
25+
yaml_parser_set_input_string(&parser,
26+
(const unsigned char *)input, length);
27+
28+
while (!done) {
29+
if (!yaml_parser_scan(&parser, &token)) {
30+
error = 1;
31+
break;
32+
}
33+
done = (token.type == YAML_STREAM_END_TOKEN);
34+
yaml_token_delete(&token);
35+
}
36+
37+
yaml_parser_delete(&parser);
38+
return error;
39+
}
40+
41+
int
42+
main(void)
43+
{
44+
char *input;
45+
int i;
46+
47+
/* Test 1: nesting beyond the default limit (1000) must fail. */
48+
{
49+
int depth = 2000;
50+
input = (char *)malloc(depth + 1);
51+
assert(input);
52+
memset(input, '[', depth);
53+
input[depth] = '\0';
54+
55+
printf("Test 1: %d nested '[' (exceeds limit) ... ", depth);
56+
fflush(stdout);
57+
assert(scan_string(input, depth) == 1);
58+
printf("OK (rejected)\n");
59+
free(input);
60+
}
61+
62+
/* Test 2: nesting within the limit must succeed. */
63+
{
64+
int depth = 500;
65+
int len = depth * 2;
66+
input = (char *)malloc(len + 1);
67+
assert(input);
68+
for (i = 0; i < depth; i++)
69+
input[i] = '[';
70+
for (i = 0; i < depth; i++)
71+
input[depth + i] = ']';
72+
input[len] = '\0';
73+
74+
printf("Test 2: %d nested '[]' (within limit) ... ", depth);
75+
fflush(stdout);
76+
assert(scan_string(input, len) == 0);
77+
printf("OK (accepted)\n");
78+
free(input);
79+
}
80+
81+
return 0;
82+
}

0 commit comments

Comments
 (0)