Skip to content

Commit 6c8bbdc

Browse files
mgreterxzyfer
authored andcommitted
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. # Conflicts: # src/parser.cpp
1 parent b3262c1 commit 6c8bbdc

File tree

5 files changed

+42
-2
lines changed

5 files changed

+42
-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: 14 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

@@ -1101,12 +1106,14 @@ namespace Sass {
11011106
// so to speak: we unwrap items from lists if possible here!
11021107
Expression_Obj Parser::parse_list(bool delayed)
11031108
{
1109+
NESTING_GUARD(nestings);
11041110
return parse_comma_list(delayed);
11051111
}
11061112

11071113
// will return singletons unwrapped
11081114
Expression_Obj Parser::parse_comma_list(bool delayed)
11091115
{
1116+
NESTING_GUARD(nestings);
11101117
// check if we have an empty list
11111118
// return the empty list as such
11121119
if (peek_css< alternatives <
@@ -1167,6 +1174,7 @@ namespace Sass {
11671174
// will return singletons unwrapped
11681175
Expression_Obj Parser::parse_space_list()
11691176
{
1177+
NESTING_GUARD(nestings);
11701178
Expression_Obj disj1 = parse_disjunction();
11711179
// if it's a singleton, return it (don't wrap it)
11721180
if (peek_css< alternatives <
@@ -1212,6 +1220,7 @@ namespace Sass {
12121220
// parse logical OR operation
12131221
Expression_Obj Parser::parse_disjunction()
12141222
{
1223+
NESTING_GUARD(nestings);
12151224
advanceToNextToken();
12161225
ParserState state(pstate);
12171226
// parse the left hand side conjunction
@@ -1233,6 +1242,7 @@ namespace Sass {
12331242
// parse logical AND operation
12341243
Expression_Obj Parser::parse_conjunction()
12351244
{
1245+
NESTING_GUARD(nestings);
12361246
advanceToNextToken();
12371247
ParserState state(pstate);
12381248
// parse the left hand side relation
@@ -1255,6 +1265,7 @@ namespace Sass {
12551265
// parse comparison operations
12561266
Expression_Obj Parser::parse_relation()
12571267
{
1268+
NESTING_GUARD(nestings);
12581269
advanceToNextToken();
12591270
ParserState state(pstate);
12601271
// parse the left hand side expression
@@ -1307,6 +1318,7 @@ namespace Sass {
13071318
// parse addition and subtraction operations
13081319
Expression_Obj Parser::parse_expression()
13091320
{
1321+
NESTING_GUARD(nestings);
13101322
advanceToNextToken();
13111323
ParserState state(pstate);
13121324
// parses multiple add and subtract operations
@@ -1350,6 +1362,7 @@ namespace Sass {
13501362
// parse addition and subtraction operations
13511363
Expression_Obj Parser::parse_operators()
13521364
{
1365+
NESTING_GUARD(nestings);
13531366
advanceToNextToken();
13541367
ParserState state(pstate);
13551368
Expression_Obj factor = parse_factor();
@@ -1382,6 +1395,7 @@ namespace Sass {
13821395
// called from parse_value_schema
13831396
Expression_Obj Parser::parse_factor()
13841397
{
1398+
NESTING_GUARD(nestings);
13851399
lex < css_comments >(false);
13861400
if (lex_css< exactly<'('> >()) {
13871401
// 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,14 +44,15 @@ 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

4151
Token lexed;
4252

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

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

0 commit comments

Comments
 (0)