ソースを参照

Shell: Prevent exponential explosion around '$(('

When parse_expression looks at '$((', there are two ways it can end up
in parse_expression again, three consumed characters later. All these
ways fail, so what happened was that the parser tried all possible
combinations, hence taking potentially an exponential amount of time.

1. parse_evaluate swallows the '$(', a new invocation of
   parse_expression swallows the other '(', and through
   parse_list_expression we're at another parse_expression.
2. parse_evaluate swallows the '$(', but returns a SyntaxError.
   parse_expression used to not recognize the error, and treated it as a
   regular AST node, calling into read_concat, then a new invocation of
   parse_expression swallows the other '(', and through
   parse_list_expression we're at another parse_expression.

Fixes #10561.

Found by OSS Fuzz, long-standing issue
https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=28113
Ben Wiederhake 3 年 前
コミット
48e4fb239a
2 ファイル変更4 行追加2 行削除
  1. 3 1
      Userland/Shell/Parser.cpp
  2. 1 1
      Userland/Shell/Parser.h

+ 3 - 1
Userland/Shell/Parser.cpp

@@ -1166,8 +1166,10 @@ RefPtr<AST::Node> Parser::parse_expression()
         if (auto immediate = parse_immediate_expression())
         if (auto immediate = parse_immediate_expression())
             return read_concat(immediate.release_nonnull());
             return read_concat(immediate.release_nonnull());
 
 
-        if (auto inline_exec = parse_evaluate())
+        auto inline_exec = parse_evaluate();
+        if (inline_exec && !inline_exec->is_syntax_error())
             return read_concat(inline_exec.release_nonnull());
             return read_concat(inline_exec.release_nonnull());
+        return inline_exec;
     }
     }
 
 
     if (starting_char == '#')
     if (starting_char == '#')

+ 1 - 1
Userland/Shell/Parser.h

@@ -251,7 +251,7 @@ expression :: evaluate expression?
             | '(' list_expression ')' expression?
             | '(' list_expression ')' expression?
 
 
 evaluate :: '$' '(' pipe_sequence ')'
 evaluate :: '$' '(' pipe_sequence ')'
-          | '$' expression          {eval / dynamic resolve}
+          | '$' [lookahead != '('] expression          {eval / dynamic resolve}
 
 
 string_composite :: string string_composite?
 string_composite :: string string_composite?
                   | variable string_composite?
                   | variable string_composite?