Skip to content

Commit 73ea2e2

Browse files
authored
Merge pull request #2438 from mgreter/bugfix/nesting-stack-size-guard
Implement nesting guard to avoid "out of stack space"
2 parents 4869c0f + 7664114 commit 73ea2e2

File tree

5 files changed

+43
-2
lines changed

5 files changed

+43
-2
lines changed

src/ast_def_macros.hpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ class LocalOption {
2929
};
3030

3131
#define LOCAL_FLAG(name,opt) LocalOption<bool> flag_##name(name, opt)
32+
#define LOCAL_COUNT(name,opt) LocalOption<size_t> cnt_##name(name, opt)
33+
34+
#define NESTING_GUARD(name) \
35+
LocalOption<size_t> cnt_##name(name, name + 1); \
36+
if (nestings > MAX_NESTING) throw Exception::NestingLimitError(pstate); \
3237

3338
#define ATTACH_OPERATIONS()\
3439
virtual void perform(Operation<void>* op) { (*op)(this); }\

src/error_handling.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ namespace Sass {
5959
: Base(pstate, msg, import_stack)
6060
{ }
6161

62+
NestingLimitError::NestingLimitError(ParserState pstate, std::string msg, std::vector<Sass_Import_Entry>* import_stack)
63+
: Base(pstate, msg, import_stack)
64+
{ }
65+
6266
UndefinedOperation::UndefinedOperation(Expression_Ptr_Const lhs, Expression_Ptr_Const rhs, const std::string& op)
6367
: lhs(lhs), rhs(rhs), op(op)
6468
{

src/error_handling.hpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ namespace Sass {
1717
const std::string def_msg = "Invalid sass detected";
1818
const std::string def_op_msg = "Undefined operation";
1919
const std::string def_op_null_msg = "Invalid null operation";
20+
const std::string def_nesting_limit = "Code too deeply neested";
2021

2122
class Base : public std::runtime_error {
2223
protected:
@@ -83,6 +84,12 @@ namespace Sass {
8384
virtual ~InvalidSyntax() throw() {};
8485
};
8586

87+
class NestingLimitError : public Base {
88+
public:
89+
NestingLimitError(ParserState pstate, std::string msg = def_nesting_limit, std::vector<Sass_Import_Entry>* import_stack = 0);
90+
virtual ~NestingLimitError() throw() {};
91+
};
92+
8693
/* common virtual base class (has no pstate) */
8794
class OperationError : public std::runtime_error {
8895
protected:

src/parser.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,7 @@ namespace Sass {
503503
// a ruleset connects a selector and a block
504504
Ruleset_Obj Parser::parse_ruleset(Lookahead lookahead)
505505
{
506+
NESTING_GUARD(nestings);
506507
// inherit is_root from parent block
507508
Block_Obj parent = block_stack.back();
508509
bool is_root = parent && parent->is_root();
@@ -535,6 +536,7 @@ namespace Sass {
535536
// in the eval stage we will be re-parse it into an actual selector
536537
Selector_Schema_Obj Parser::parse_selector_schema(const char* end_of_selector, bool chroot)
537538
{
539+
NESTING_GUARD(nestings);
538540
// move up to the start
539541
lex< optional_spaces >();
540542
const char* i = position;
@@ -646,6 +648,7 @@ namespace Sass {
646648
{
647649
bool reloop;
648650
bool had_linefeed = false;
651+
NESTING_GUARD(nestings);
649652
Complex_Selector_Obj sel;
650653
Selector_List_Obj group = SASS_MEMORY_NEW(Selector_List, pstate);
651654
group->media_block(last_media_block);
@@ -700,6 +703,7 @@ namespace Sass {
700703
Complex_Selector_Obj Parser::parse_complex_selector(bool chroot)
701704
{
702705

706+
NESTING_GUARD(nestings);
703707
String_Obj reference = 0;
704708
lex < block_comment >();
705709
advanceToNextToken();
@@ -1055,6 +1059,7 @@ namespace Sass {
10551059

10561060
Expression_Obj Parser::parse_map()
10571061
{
1062+
NESTING_GUARD(nestings);
10581063
Expression_Obj key = parse_list();
10591064
List_Obj map = SASS_MEMORY_NEW(List, pstate, 0, SASS_HASH);
10601065

@@ -1098,6 +1103,7 @@ namespace Sass {
10981103

10991104
Expression_Obj Parser::parse_bracket_list()
11001105
{
1106+
NESTING_GUARD(nestings);
11011107
// check if we have an empty list
11021108
// return the empty list as such
11031109
if (peek_css< list_terminator >(position))
@@ -1144,12 +1150,14 @@ namespace Sass {
11441150
// so to speak: we unwrap items from lists if possible here!
11451151
Expression_Obj Parser::parse_list(bool delayed)
11461152
{
1153+
NESTING_GUARD(nestings);
11471154
return parse_comma_list(delayed);
11481155
}
11491156

11501157
// will return singletons unwrapped
11511158
Expression_Obj Parser::parse_comma_list(bool delayed)
11521159
{
1160+
NESTING_GUARD(nestings);
11531161
// check if we have an empty list
11541162
// return the empty list as such
11551163
if (peek_css< list_terminator >(position))
@@ -1189,6 +1197,7 @@ namespace Sass {
11891197
// will return singletons unwrapped
11901198
Expression_Obj Parser::parse_space_list()
11911199
{
1200+
NESTING_GUARD(nestings);
11921201
Expression_Obj disj1 = parse_disjunction();
11931202
// if it's a singleton, return it (don't wrap it)
11941203
if (peek_css< space_list_terminator >(position)
@@ -1213,6 +1222,7 @@ namespace Sass {
12131222
// parse logical OR operation
12141223
Expression_Obj Parser::parse_disjunction()
12151224
{
1225+
NESTING_GUARD(nestings);
12161226
advanceToNextToken();
12171227
ParserState state(pstate);
12181228
// parse the left hand side conjunction
@@ -1234,6 +1244,7 @@ namespace Sass {
12341244
// parse logical AND operation
12351245
Expression_Obj Parser::parse_conjunction()
12361246
{
1247+
NESTING_GUARD(nestings);
12371248
advanceToNextToken();
12381249
ParserState state(pstate);
12391250
// parse the left hand side relation
@@ -1256,6 +1267,7 @@ namespace Sass {
12561267
// parse comparison operations
12571268
Expression_Obj Parser::parse_relation()
12581269
{
1270+
NESTING_GUARD(nestings);
12591271
advanceToNextToken();
12601272
ParserState state(pstate);
12611273
// parse the left hand side expression
@@ -1308,6 +1320,7 @@ namespace Sass {
13081320
// parse addition and subtraction operations
13091321
Expression_Obj Parser::parse_expression()
13101322
{
1323+
NESTING_GUARD(nestings);
13111324
advanceToNextToken();
13121325
ParserState state(pstate);
13131326
// parses multiple add and subtract operations
@@ -1351,6 +1364,7 @@ namespace Sass {
13511364
// parse addition and subtraction operations
13521365
Expression_Obj Parser::parse_operators()
13531366
{
1367+
NESTING_GUARD(nestings);
13541368
advanceToNextToken();
13551369
ParserState state(pstate);
13561370
Expression_Obj factor = parse_factor();
@@ -1383,6 +1397,7 @@ namespace Sass {
13831397
// called from parse_value_schema
13841398
Expression_Obj Parser::parse_factor()
13851399
{
1400+
NESTING_GUARD(nestings);
13861401
lex < css_comments >(false);
13871402
if (lex_css< exactly<'('> >()) {
13881403
// parse_map may return a list

src/parser.hpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,15 @@
1010
#include "position.hpp"
1111
#include "prelexer.hpp"
1212

13+
#ifndef MAX_NESTING
14+
// Note that this limit is not an exact science
15+
// it depends on various factors, which some are
16+
// not under our control (compile time or even OS
17+
// dependent settings on the available stack size)
18+
// It should fix most common segfault cases though.
19+
#define MAX_NESTING 512
20+
#endif
21+
1322
struct Lookahead {
1423
const char* found;
1524
const char* error;
@@ -35,13 +44,14 @@ namespace Sass {
3544
Position before_token;
3645
Position after_token;
3746
ParserState pstate;
38-
int indentation;
47+
size_t indentation;
48+
size_t nestings;
3949

4050
Token lexed;
4151

4252
Parser(Context& ctx, const ParserState& pstate)
4353
: ParserState(pstate), ctx(ctx), block_stack(), stack(0), last_media_block(),
44-
source(0), position(0), end(0), before_token(pstate), after_token(pstate), pstate(pstate), indentation(0)
54+
source(0), position(0), end(0), before_token(pstate), after_token(pstate), pstate(pstate), indentation(0), nestings(0)
4555
{ stack.push_back(Scope::Root); }
4656

4757
// static Parser from_string(const std::string& src, Context& ctx, ParserState pstate = ParserState("[STRING]"));

0 commit comments

Comments
 (0)