Skip to content

Commit 7664114

Browse files
committed
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.
1 parent 0787ad0 commit 7664114

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)