From 7664114543757e932f5b1a2ff5295aa9b34f8623 Mon Sep 17 00:00:00 2001 From: Marcel Greter Date: Tue, 11 Jul 2017 20:47:26 +0200 Subject: [PATCH] Implement nesting guard to avoid "out of stack space" Note that this limit is not an exact science it depends on various factors, which some are not under our control (compile time or even OS dependent settings on the available stack size) It should fix most common segfault cases though. --- src/ast_def_macros.hpp | 5 +++++ src/error_handling.cpp | 4 ++++ src/error_handling.hpp | 7 +++++++ src/parser.cpp | 15 +++++++++++++++ src/parser.hpp | 14 ++++++++++++-- 5 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/ast_def_macros.hpp b/src/ast_def_macros.hpp index 78ba1f4ce4..253b05a918 100644 --- a/src/ast_def_macros.hpp +++ b/src/ast_def_macros.hpp @@ -29,6 +29,11 @@ class LocalOption { }; #define LOCAL_FLAG(name,opt) LocalOption flag_##name(name, opt) +#define LOCAL_COUNT(name,opt) LocalOption cnt_##name(name, opt) + +#define NESTING_GUARD(name) \ + LocalOption cnt_##name(name, name + 1); \ + if (nestings > MAX_NESTING) throw Exception::NestingLimitError(pstate); \ #define ATTACH_OPERATIONS()\ virtual void perform(Operation* op) { (*op)(this); }\ diff --git a/src/error_handling.cpp b/src/error_handling.cpp index 7316e6a627..d85f095985 100644 --- a/src/error_handling.cpp +++ b/src/error_handling.cpp @@ -59,6 +59,10 @@ namespace Sass { : Base(pstate, msg, import_stack) { } + NestingLimitError::NestingLimitError(ParserState pstate, std::string msg, std::vector* import_stack) + : Base(pstate, msg, import_stack) + { } + UndefinedOperation::UndefinedOperation(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, const std::string& op) : lhs(lhs), rhs(rhs), op(op) { diff --git a/src/error_handling.hpp b/src/error_handling.hpp index 9818891003..ec00817ebf 100644 --- a/src/error_handling.hpp +++ b/src/error_handling.hpp @@ -17,6 +17,7 @@ namespace Sass { const std::string def_msg = "Invalid sass detected"; const std::string def_op_msg = "Undefined operation"; const std::string def_op_null_msg = "Invalid null operation"; + const std::string def_nesting_limit = "Code too deeply neested"; class Base : public std::runtime_error { protected: @@ -83,6 +84,12 @@ namespace Sass { virtual ~InvalidSyntax() throw() {}; }; + class NestingLimitError : public Base { + public: + NestingLimitError(ParserState pstate, std::string msg = def_nesting_limit, std::vector* import_stack = 0); + virtual ~NestingLimitError() throw() {}; + }; + /* common virtual base class (has no pstate) */ class OperationError : public std::runtime_error { protected: diff --git a/src/parser.cpp b/src/parser.cpp index 644705f586..4cb4a29bec 100644 --- a/src/parser.cpp +++ b/src/parser.cpp @@ -503,6 +503,7 @@ namespace Sass { // a ruleset connects a selector and a block Ruleset_Obj Parser::parse_ruleset(Lookahead lookahead) { + NESTING_GUARD(nestings); // inherit is_root from parent block Block_Obj parent = block_stack.back(); bool is_root = parent && parent->is_root(); @@ -535,6 +536,7 @@ namespace Sass { // in the eval stage we will be re-parse it into an actual selector Selector_Schema_Obj Parser::parse_selector_schema(const char* end_of_selector, bool chroot) { + NESTING_GUARD(nestings); // move up to the start lex< optional_spaces >(); const char* i = position; @@ -646,6 +648,7 @@ namespace Sass { { bool reloop; bool had_linefeed = false; + NESTING_GUARD(nestings); Complex_Selector_Obj sel; Selector_List_Obj group = SASS_MEMORY_NEW(Selector_List, pstate); group->media_block(last_media_block); @@ -700,6 +703,7 @@ namespace Sass { Complex_Selector_Obj Parser::parse_complex_selector(bool chroot) { + NESTING_GUARD(nestings); String_Obj reference = 0; lex < block_comment >(); advanceToNextToken(); @@ -1055,6 +1059,7 @@ namespace Sass { Expression_Obj Parser::parse_map() { + NESTING_GUARD(nestings); Expression_Obj key = parse_list(); List_Obj map = SASS_MEMORY_NEW(List, pstate, 0, SASS_HASH); @@ -1098,6 +1103,7 @@ namespace Sass { Expression_Obj Parser::parse_bracket_list() { + NESTING_GUARD(nestings); // check if we have an empty list // return the empty list as such if (peek_css< list_terminator >(position)) @@ -1144,12 +1150,14 @@ namespace Sass { // so to speak: we unwrap items from lists if possible here! Expression_Obj Parser::parse_list(bool delayed) { + NESTING_GUARD(nestings); return parse_comma_list(delayed); } // will return singletons unwrapped Expression_Obj Parser::parse_comma_list(bool delayed) { + NESTING_GUARD(nestings); // check if we have an empty list // return the empty list as such if (peek_css< list_terminator >(position)) @@ -1189,6 +1197,7 @@ namespace Sass { // will return singletons unwrapped Expression_Obj Parser::parse_space_list() { + NESTING_GUARD(nestings); Expression_Obj disj1 = parse_disjunction(); // if it's a singleton, return it (don't wrap it) if (peek_css< space_list_terminator >(position) @@ -1213,6 +1222,7 @@ namespace Sass { // parse logical OR operation Expression_Obj Parser::parse_disjunction() { + NESTING_GUARD(nestings); advanceToNextToken(); ParserState state(pstate); // parse the left hand side conjunction @@ -1234,6 +1244,7 @@ namespace Sass { // parse logical AND operation Expression_Obj Parser::parse_conjunction() { + NESTING_GUARD(nestings); advanceToNextToken(); ParserState state(pstate); // parse the left hand side relation @@ -1256,6 +1267,7 @@ namespace Sass { // parse comparison operations Expression_Obj Parser::parse_relation() { + NESTING_GUARD(nestings); advanceToNextToken(); ParserState state(pstate); // parse the left hand side expression @@ -1308,6 +1320,7 @@ namespace Sass { // parse addition and subtraction operations Expression_Obj Parser::parse_expression() { + NESTING_GUARD(nestings); advanceToNextToken(); ParserState state(pstate); // parses multiple add and subtract operations @@ -1351,6 +1364,7 @@ namespace Sass { // parse addition and subtraction operations Expression_Obj Parser::parse_operators() { + NESTING_GUARD(nestings); advanceToNextToken(); ParserState state(pstate); Expression_Obj factor = parse_factor(); @@ -1383,6 +1397,7 @@ namespace Sass { // called from parse_value_schema Expression_Obj Parser::parse_factor() { + NESTING_GUARD(nestings); lex < css_comments >(false); if (lex_css< exactly<'('> >()) { // parse_map may return a list diff --git a/src/parser.hpp b/src/parser.hpp index d481e77b0f..3c32c894ab 100644 --- a/src/parser.hpp +++ b/src/parser.hpp @@ -10,6 +10,15 @@ #include "position.hpp" #include "prelexer.hpp" +#ifndef MAX_NESTING +// Note that this limit is not an exact science +// it depends on various factors, which some are +// not under our control (compile time or even OS +// dependent settings on the available stack size) +// It should fix most common segfault cases though. +#define MAX_NESTING 512 +#endif + struct Lookahead { const char* found; const char* error; @@ -35,13 +44,14 @@ namespace Sass { Position before_token; Position after_token; ParserState pstate; - int indentation; + size_t indentation; + size_t nestings; Token lexed; Parser(Context& ctx, const ParserState& pstate) : ParserState(pstate), ctx(ctx), block_stack(), stack(0), last_media_block(), - source(0), position(0), end(0), before_token(pstate), after_token(pstate), pstate(pstate), indentation(0) + source(0), position(0), end(0), before_token(pstate), after_token(pstate), pstate(pstate), indentation(0), nestings(0) { stack.push_back(Scope::Root); } // static Parser from_string(const std::string& src, Context& ctx, ParserState pstate = ParserState("[STRING]"));