diff --git a/TODO b/TODO index 16665916a2222612612d802b82b07b2c608167f5..2581d0b0e957e75f55a8390530eb90f83d60c48f 100644 --- a/TODO +++ b/TODO @@ -12,8 +12,6 @@ Next (in order of descending priority/precedence): * Code cleanup * Full security review. Enforce limits on number of created objects, script size, execution time, etc... - - Limit the depth of the AST, i.e. command block and test list - nesting. - Make sure error messages don't print large erroneous values. - Limit the maximum number of included scripts - Make (configurable) limit on the number of redirects diff --git a/src/lib-sieve/sieve-parser.c b/src/lib-sieve/sieve-parser.c index 83612b91a5f4d930403485fe70c355437f73be55..dca725d4bfe985cfe1a11d39cf21d3d8aa832f47 100644 --- a/src/lib-sieve/sieve-parser.c +++ b/src/lib-sieve/sieve-parser.c @@ -151,7 +151,7 @@ inline static void sieve_parser_warning * test = identifier arguments */ static int sieve_parse_arguments -(struct sieve_parser *parser, struct sieve_ast_node *node) +(struct sieve_parser *parser, struct sieve_ast_node *node, unsigned int depth) { struct sieve_lexer *lexer = parser->lexer; struct sieve_ast_node *test = NULL; @@ -159,7 +159,7 @@ static int sieve_parse_arguments bool arg_present = TRUE; int result = TRUE; /* Indicates whether the parser is in a defined, not necessarily error-free state */ - + /* Parse arguments */ while ( arg_present && result > 0 && (parser->valid || sieve_errors_more_allowed(parser->ehandler)) ) { @@ -292,6 +292,13 @@ static int sieve_parse_arguments /* Single test */ case STT_IDENTIFIER: + if ( depth+1 > SIEVE_MAX_TEST_NESTING ) { + sieve_parser_error(parser, + "cannot nest tests deeper than %u levels", + SIEVE_MAX_TEST_NESTING); + return FALSE; + } + test = sieve_ast_test_create (node, sieve_lexer_token_ident(lexer), sieve_lexer_current_line(parser->lexer)); @@ -301,7 +308,7 @@ static int sieve_parse_arguments if ( test == NULL ) break; /* Parse test arguments, which may include more tests (recurse) */ - if ( !sieve_parse_arguments(parser, test) ) { + if ( !sieve_parse_arguments(parser, test, depth+1) ) { return FALSE; /* Defer recovery to caller */ } @@ -310,7 +317,17 @@ static int sieve_parse_arguments /* Test list */ case STT_LBRACKET: sieve_lexer_skip_token(lexer); - + + if ( depth+1 > SIEVE_MAX_TEST_NESTING ) { + sieve_parser_error(parser, + "cannot nest tests deeper than %u levels", + SIEVE_MAX_TEST_NESTING); + result = sieve_parser_recover(parser, STT_RBRACKET); + + if ( result ) sieve_lexer_skip_token(lexer); + return result; + } + node->test_list = TRUE; /* Test starts with identifier */ @@ -323,7 +340,7 @@ static int sieve_parse_arguments if ( test == NULL ) break; /* Parse test arguments, which may include more tests (recurse) */ - if ( (result=sieve_parse_arguments(parser, test)) > 0 ) { + if ( (result=sieve_parse_arguments(parser, test, depth+1)) > 0 ) { /* More tests ? */ while ( sieve_lexer_current_token(lexer) == STT_COMMA && @@ -340,7 +357,7 @@ static int sieve_parse_arguments if ( test == NULL ) break; /* Parse test arguments, which may include more tests (recurse) */ - if ( (result=sieve_parse_arguments(parser, test)) <= 0 ) { + if ( (result=sieve_parse_arguments(parser, test, depth+1)) <= 0 ) { if ( result < 0 ) return result; result = sieve_parser_recover(parser, STT_RBRACKET); @@ -411,12 +428,13 @@ static int sieve_parse_arguments * block = "{" commands "}" */ static int sieve_parse_commands -(struct sieve_parser *parser, struct sieve_ast_node *block) +(struct sieve_parser *parser, struct sieve_ast_node *block, unsigned int depth) { struct sieve_lexer *lexer = parser->lexer; int result = TRUE; - while ( sieve_lexer_current_token(lexer) == STT_IDENTIFIER && + while ( result > 0 && + sieve_lexer_current_token(lexer) == STT_IDENTIFIER && (parser->valid || sieve_errors_more_allowed(parser->ehandler)) ) { struct sieve_ast_node *command = sieve_ast_command_create @@ -425,7 +443,7 @@ static int sieve_parse_commands if ( command == NULL ) { sieve_parser_error(parser, - "failed to accept more commands inside block of command '%s'", + "failed to accept more commands inside the block of command '%s'", block->identifier); return -1; } @@ -435,8 +453,8 @@ static int sieve_parse_commands sieve_lexer_skip_token(lexer); - result = sieve_parse_arguments(parser, command); - + result = sieve_parse_arguments(parser, command, 1); + /* Check whether the command is properly terminated * (i.e. with ; or a new block) */ @@ -455,7 +473,7 @@ static int sieve_parse_commands if ( result == 0 ) { result = sieve_parser_recover(parser, STT_SEMICOLON); } - + /* Don't bother to continue if we are not in a defined state */ if ( result <= 0 ) return result; @@ -465,13 +483,26 @@ static int sieve_parse_commands case STT_SEMICOLON: sieve_lexer_skip_token(lexer); break; - + + /* Command has a block {...} */ case STT_LCURLY: sieve_lexer_skip_token(lexer); + /* Check current depth first */ + if ( depth+1 > SIEVE_MAX_BLOCK_NESTING ) { + sieve_parser_error(parser, + "cannot nest command blocks deeper than %u levels", + SIEVE_MAX_BLOCK_NESTING); + result = sieve_parser_recover(parser, STT_RCURLY); + + if ( result > 0 ) + sieve_lexer_skip_token(lexer); + break; + } + command->block = TRUE; - if ( (result=sieve_parse_commands(parser, command)) > 0 ) { + if ( (result=sieve_parse_commands(parser, command, depth+1)) > 0 ) { if ( sieve_lexer_current_token(lexer) != STT_RCURLY ) { sieve_parser_error(parser, @@ -494,7 +525,7 @@ static int sieve_parse_commands i_unreached(); } } - + return result; } @@ -516,7 +547,7 @@ bool sieve_parser_run sieve_lexer_skip_token(parser->lexer); /* Parse */ - if ( sieve_parse_commands(parser, sieve_ast_root(parser->ast)) > 0 && + if ( sieve_parse_commands(parser, sieve_ast_root(parser->ast), 1) > 0 && parser->valid ) { /* Parsed right to EOF ? */ @@ -597,14 +628,14 @@ static int sieve_parser_recover __get_token_priority(sieve_lexer_current_token(lexer)) <= end_priority ) { if ( sieve_lexer_current_token(lexer) == begin_tokens[end_priority] ) { - nesting++; + nesting++; sieve_lexer_skip_token(lexer); continue; } if ( sieve_lexer_current_token(lexer) == end_tokens[end_priority] ) { nesting--; - + if ( nesting == 0 ) { /* Next character is the end */ return TRUE; diff --git a/tests/compile/errors.svtest b/tests/compile/errors.svtest index 7681a13d18b6a4dbc01a423442756308a646cd62..bcc5a44bf25edc28402e4948e8b7342213abd7aa 100644 --- a/tests/compile/errors.svtest +++ b/tests/compile/errors.svtest @@ -31,7 +31,7 @@ test "Parser errors (FIXME: count only)" { test_fail "compile should have failed."; } - if not test_error :count "eq" :comparator "i;ascii-numeric" "4" { + if not test_error :count "eq" :comparator "i;ascii-numeric" "9" { test_fail "wrong number of errors reported"; } } diff --git a/tests/compile/errors/parser.sieve b/tests/compile/errors/parser.sieve index 916940ab283c364fb6bb323b972ac22763d24903..b5300b12b3de140facd7371e600dcf12d59b4d3d 100644 --- a/tests/compile/errors/parser.sieve +++ b/tests/compile/errors/parser.sieve @@ -1,15 +1,78 @@ /* * Parser errors + * + * Total errors: 8 (+1 = 9) */ -# Too many arguments +# Too many arguments (1) frop :this "is" "a" 2 :long "argument" "list" :and :it :should "fail" :during "parsing" :but "it" "should" "be" "recoverable" "." :this "is" "a" 2 :long "argument" "list" :and :it :should "fail" :during "parsing" :but "it" "should" "be" "recoverable" { stop; } -# Garbage argument +# Garbage argument (2) friep $$$; +# Deep block nesting (1) +if true { if true { if true { if true { if true { if true { if true { if true { + if true { if true { if true { if true { if true { if true { if true { if true { + if true { if true { if true { if true { if true { if true { if true { if true { + if true { if true { if true { if true { if true { if true { if true { if true { + if true { if true { if true { if true { if true { if true { if true { if true { + stop; + } } } } } } } } + } } } } } } } } + } } } } } } } } + } } } } } } } } +} } } } } } } } + +# Deepest block and too deep test (list) nesting (1) +if true { if true { if true { if true { if true { if true { if true { if true { + if true { if true { if true { if true { if true { if true { if true { if true { + if true { if true { if true { if true { if true { if true { if true { if true { + if true { if true { if true { if true { if true { if true { + if + anyof ( anyof ( anyof ( anyof ( anyof ( anyof ( anyof ( anyof ( + anyof ( anyof ( anyof ( anyof ( anyof ( anyof ( anyof ( anyof ( + anyof ( anyof ( anyof ( anyof ( anyof ( anyof ( anyof ( anyof ( + anyof ( anyof ( anyof ( anyof ( anyof ( anyof ( anyof ( anyof ( + anyof ( anyof ( anyof ( anyof ( anyof ( anyof ( anyof ( anyof ( + true + )))))))) + )))))))) + )))))))) + )))))))) + )))))))) + { + stop; + } + } } } } } } + } } } } } } } } + } } } } } } } } +} } } } } } } } + +# Deepest block and too deep test nesting (1) +if true { if true { if true { if true { if true { if true { if true { if true { + if true { if true { if true { if true { if true { if true { if true { if true { + if true { if true { if true { if true { if true { if true { if true { if true { + if true { if true { if true { if true { if true { if true { + if + not not not not not not not not + not not not not not not not not + not not not not not not not not + not not not not not not not not + not not not not not not not not false + { + stop; + } + } } } } } } + } } } } } } } } + } } } } } } } } +} } } } } } } } + + +# Garbage command; test wether previous errors were resolved (2) +frop $$$$; +