diff --git a/.gitignore b/.gitignore index 8257f570..637273f1 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ /libtool CMakeCache.txt CMakeFiles/ +DartConfiguration.tcl GNUmakefile Makefile Makefile.in @@ -40,6 +41,7 @@ stamp-h1 /tests/example-reformatter /tests/example-reformatter-alt /tests/run-test-suite +/tests/test-nesting /tests/test-reader /tests/test-version /dist/ diff --git a/src/parser.c b/src/parser.c index 855f1eaa..10770936 100644 --- a/src/parser.c +++ b/src/parser.c @@ -82,10 +82,6 @@ yaml_parser_set_parser_error_context(yaml_parser_t *parser, const char *context, yaml_mark_t context_mark, const char *problem, yaml_mark_t problem_mark); -static int -yaml_maximum_level_reached(yaml_parser_t *parser, - yaml_mark_t context_mark, yaml_mark_t problem_mark); - /* * State functions. */ @@ -229,15 +225,6 @@ yaml_parser_set_parser_error_context(yaml_parser_t *parser, return 0; } -static int -yaml_maximum_level_reached(yaml_parser_t *parser, - yaml_mark_t context_mark, yaml_mark_t problem_mark) -{ - yaml_parser_set_parser_error_context(parser, - "while parsing", context_mark, "Maximum nesting level reached, set with yaml_set_max_nest_level())", problem_mark); - return 0; -} - /* * State dispatcher. */ @@ -677,10 +664,6 @@ yaml_parser_parse_node(yaml_parser_t *parser, yaml_event_t *event, return 1; } else if (token->type == YAML_FLOW_SEQUENCE_START_TOKEN) { - if (!STACK_LIMIT(parser, parser->indents, MAX_NESTING_LEVEL - parser->flow_level)) { - yaml_maximum_level_reached(parser, start_mark, token->start_mark); - goto error; - } end_mark = token->end_mark; parser->state = YAML_PARSE_FLOW_SEQUENCE_FIRST_ENTRY_STATE; SEQUENCE_START_EVENT_INIT(*event, anchor, tag, implicit, @@ -688,10 +671,6 @@ yaml_parser_parse_node(yaml_parser_t *parser, yaml_event_t *event, return 1; } else if (token->type == YAML_FLOW_MAPPING_START_TOKEN) { - if (!STACK_LIMIT(parser, parser->indents, MAX_NESTING_LEVEL - parser->flow_level)) { - yaml_maximum_level_reached(parser, start_mark, token->start_mark); - goto error; - } end_mark = token->end_mark; parser->state = YAML_PARSE_FLOW_MAPPING_FIRST_KEY_STATE; MAPPING_START_EVENT_INIT(*event, anchor, tag, implicit, @@ -699,10 +678,6 @@ yaml_parser_parse_node(yaml_parser_t *parser, yaml_event_t *event, return 1; } else if (block && token->type == YAML_BLOCK_SEQUENCE_START_TOKEN) { - if (!STACK_LIMIT(parser, parser->indents, MAX_NESTING_LEVEL - parser->flow_level)) { - yaml_maximum_level_reached(parser, start_mark, token->start_mark); - goto error; - } end_mark = token->end_mark; parser->state = YAML_PARSE_BLOCK_SEQUENCE_FIRST_ENTRY_STATE; SEQUENCE_START_EVENT_INIT(*event, anchor, tag, implicit, @@ -710,10 +685,6 @@ yaml_parser_parse_node(yaml_parser_t *parser, yaml_event_t *event, return 1; } else if (block && token->type == YAML_BLOCK_MAPPING_START_TOKEN) { - if (!STACK_LIMIT(parser, parser->indents, MAX_NESTING_LEVEL - parser->flow_level)) { - yaml_maximum_level_reached(parser, start_mark, token->start_mark); - goto error; - } end_mark = token->end_mark; parser->state = YAML_PARSE_BLOCK_MAPPING_FIRST_KEY_STATE; MAPPING_START_EVENT_INIT(*event, anchor, tag, implicit, diff --git a/src/scanner.c b/src/scanner.c index 428e1686..e7ffd83f 100644 --- a/src/scanner.c +++ b/src/scanner.c @@ -478,6 +478,12 @@ #include "yaml_private.h" +/* + * Maximum nesting level (defined in parser.c). + */ + +extern int MAX_NESTING_LEVEL; + /* * Ensure that the buffer contains the required number of characters. * 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) return 0; } + if (!STACK_LIMIT(parser, parser->indents, MAX_NESTING_LEVEL - parser->flow_level)) { + return yaml_parser_set_scanner_error(parser, + "while increasing flow level", parser->mark, + "exceeded maximum nesting depth"); + } + parser->flow_level++; return 1; @@ -1223,6 +1235,12 @@ yaml_parser_roll_indent(yaml_parser_t *parser, ptrdiff_t column, if (!PUSH(parser, parser->indents, parser->indent)) return 0; + if (!STACK_LIMIT(parser, parser->indents, MAX_NESTING_LEVEL - parser->flow_level)) { + return yaml_parser_set_scanner_error(parser, + "while increasing block level", parser->mark, + "exceeded maximum nesting depth"); + } + if (column > INT_MAX) { parser->error = YAML_MEMORY_ERROR; return 0; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index be2ce399..17a9c671 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -16,6 +16,7 @@ foreach(name IN ITEMS run-parser run-parser-test-suite run-scanner + test-nesting test-reader test-version ) @@ -24,4 +25,5 @@ endforeach() add_test(NAME version COMMAND test-version) add_test(NAME reader COMMAND test-reader) +add_test(NAME nesting COMMAND test-nesting) diff --git a/tests/Makefile.am b/tests/Makefile.am index 9597b7fe..8b8a456f 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -1,8 +1,8 @@ AM_CPPFLAGS = -I$(top_srcdir)/include -Wall #AM_CFLAGS = -Wno-pointer-sign LDADD = $(top_builddir)/src/libyaml.la -TESTS = test-version test-reader -check_PROGRAMS = test-version test-reader +TESTS = test-version test-reader test-nesting +check_PROGRAMS = test-version test-reader test-nesting noinst_PROGRAMS = run-scanner run-parser run-loader run-emitter run-dumper \ example-reformatter example-reformatter-alt \ example-deconstructor example-deconstructor-alt \ diff --git a/tests/test-nesting.c b/tests/test-nesting.c new file mode 100644 index 00000000..a318f859 --- /dev/null +++ b/tests/test-nesting.c @@ -0,0 +1,115 @@ +#include + +#include +#include +#include + +#ifdef NDEBUG +#undef NDEBUG +#endif +#include + +/* + * Test that the scanner enforces the maximum nesting depth. + */ + +static int +scan_string(const char *input, size_t length) +{ + yaml_parser_t parser; + yaml_token_t token; + int done = 0; + int error = 0; + + assert(yaml_parser_initialize(&parser)); + yaml_parser_set_input_string(&parser, + (const unsigned char *)input, length); + + while (!done) { + if (!yaml_parser_scan(&parser, &token)) { + error = 1; + break; + } + done = (token.type == YAML_STREAM_END_TOKEN); + yaml_token_delete(&token); + } + + yaml_parser_delete(&parser); + return error; +} + +int +main(void) +{ + char *input; + int i; + + /* Test 1: nesting beyond the default limit (1000) must fail. */ + { + int depth = 2000; + input = (char *)malloc(depth + 1); + assert(input); + memset(input, '[', depth); + input[depth] = '\0'; + + printf("Test 1: %d nested '[' (exceeds limit) ... ", depth); + fflush(stdout); + assert(scan_string(input, depth) == 1); + printf("OK (rejected)\n"); + free(input); + } + + /* Test 2: block nesting beyond the limit must fail. */ + { + /* + * Build a YAML string with 2000 levels of block mapping nesting: + * "a:\n b:\n c:\n d:\n..." — each level indented one more space. + */ + int depth = 2000; + int len = 0; + int pos = 0; + + /* Each level: indent spaces + "X:\n" */ + for (i = 0; i < depth; i++) + len += i + 2 + 1; /* i spaces + "X:" + newline */ + input = (char *)malloc(len + 1); + assert(input); + + for (i = 0; i < depth; i++) { + int j; + for (j = 0; j < i; j++) + input[pos++] = ' '; + input[pos++] = 'a' + (i % 26); + input[pos++] = ':'; + input[pos++] = '\n'; + } + input[pos] = '\0'; + + printf("Test 2: %d levels block nesting (exceeds limit) ... ", depth); + fflush(stdout); + assert(scan_string(input, pos) == 1); + printf("OK (rejected)\n"); + free(input); + } + + /* Test 3: flow nesting within the limit must succeed. */ + { + int depth = 500; + int len = depth * 2; + input = (char *)malloc(len + 1); + assert(input); + for (i = 0; i < depth; i++) + input[i] = '['; + for (i = 0; i < depth; i++) + input[depth + i] = ']'; + input[len] = '\0'; + + printf("Test 2: %d nested '[]' (within limit) ... ", depth); + fflush(stdout); + assert(scan_string(input, len) == 0); + printf("OK (accepted)\n"); + free(input); + } + + return 0; +}