From 597ce09c7a1d8b89e9473faae900321ef2d4181d Mon Sep 17 00:00:00 2001 From: Erik Verbruggen Date: Thu, 11 Oct 2018 13:33:08 +0200 Subject: JS: Limit expression and statement nesting level This is to prevent extremely deeply nested expressions and statements make the code-generator run out of (native) stack space. Task-number: QTBUG-71087 Change-Id: I8e1a20a361bff3e49101e535754546475a63ca18 Reviewed-by: Simon Hausmann --- src/qml/compiler/qv4codegen.cpp | 8 ++++++-- src/qml/compiler/qv4codegen_p.h | 25 +++++++++++++++++++++++++ src/qml/compiler/qv4compilerscanfunctions.cpp | 19 +++++++++++++++++++ src/qml/compiler/qv4compilerscanfunctions_p.h | 5 +++++ src/qml/parser/qqmljs.g | 10 +++++++++- 5 files changed, 64 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/qml/compiler/qv4codegen.cpp b/src/qml/compiler/qv4codegen.cpp index bf05c5c538..8ec730a33d 100644 --- a/src/qml/compiler/qv4codegen.cpp +++ b/src/qml/compiler/qv4codegen.cpp @@ -315,6 +315,7 @@ void Codegen::accept(Node *node) void Codegen::statement(Statement *ast) { + RecursionDepthCheck depthCheck(this, ast->lastSourceLocation()); RegisterScope scope(this); bytecodeGenerator->setLocation(ast->firstSourceLocation()); @@ -327,11 +328,12 @@ void Codegen::statement(Statement *ast) void Codegen::statement(ExpressionNode *ast) { - RegisterScope scope(this); - if (! ast) { return; } else { + RecursionDepthCheck depthCheck(this, ast->lastSourceLocation()); + RegisterScope scope(this); + Result r(nx); qSwap(_expr, r); VolatileMemoryLocations vLocs = scanVolatileMemoryLocations(ast); @@ -358,6 +360,7 @@ void Codegen::condition(ExpressionNode *ast, const BytecodeGenerator::Label *ift if (!ast) return; + RecursionDepthCheck depthCheck(this, ast->lastSourceLocation()); Result r(iftrue, iffalse, trueBlockFollowsCondition); qSwap(_expr, r); accept(ast); @@ -381,6 +384,7 @@ void Codegen::condition(ExpressionNode *ast, const BytecodeGenerator::Label *ift Codegen::Reference Codegen::expression(ExpressionNode *ast) { + RecursionDepthCheck depthCheck(this, ast->lastSourceLocation()); Result r; if (ast) { qSwap(_expr, r); diff --git a/src/qml/compiler/qv4codegen_p.h b/src/qml/compiler/qv4codegen_p.h index 0bc04750f7..289728f505 100644 --- a/src/qml/compiler/qv4codegen_p.h +++ b/src/qml/compiler/qv4codegen_p.h @@ -761,6 +761,31 @@ protected: bool _onoff; }; + class RecursionDepthCheck { + public: + RecursionDepthCheck(Codegen *cg, const AST::SourceLocation &loc) + : _cg(cg) + { +#ifdef QT_NO_DEBUG + const int depthLimit = 4000; // limit to ~1000 deep +#else + const int depthLimit = 1000; // limit to ~250 deep +#endif // QT_NO_DEBUG + + ++_cg->_recursionDepth; + if (_cg->_recursionDepth > depthLimit) + _cg->throwSyntaxError(loc, QStringLiteral("Maximum statement or expression depth exceeded")); + } + + ~RecursionDepthCheck() + { --_cg->_recursionDepth; } + + private: + Codegen *_cg; + }; + int _recursionDepth = 0; + friend class RecursionDepthCheck; + private: VolatileMemoryLocations scanVolatileMemoryLocations(AST::Node *ast) const; void handleConstruct(const Reference &base, AST::ArgumentList *args); diff --git a/src/qml/compiler/qv4compilerscanfunctions.cpp b/src/qml/compiler/qv4compilerscanfunctions.cpp index 2026e64929..fc3ac769ae 100644 --- a/src/qml/compiler/qv4compilerscanfunctions.cpp +++ b/src/qml/compiler/qv4compilerscanfunctions.cpp @@ -96,6 +96,25 @@ void ScanFunctions::leaveEnvironment() _context = _contextStack.isEmpty() ? nullptr : _contextStack.top(); } +bool ScanFunctions::preVisit(Node *ast) +{ + if (_cg->hasError) + return false; + ++_recursionDepth; + + if (_recursionDepth > 1000) { + _cg->throwSyntaxError(ast->lastSourceLocation(), QStringLiteral("Maximum statement or expression depth exceeded")); + return false; + } + + return true; +} + +void ScanFunctions::postVisit(Node *) +{ + --_recursionDepth; +} + void ScanFunctions::checkDirectivePrologue(StatementList *ast) { for (StatementList *it = ast; it; it = it->next) { diff --git a/src/qml/compiler/qv4compilerscanfunctions_p.h b/src/qml/compiler/qv4compilerscanfunctions_p.h index bb07540ec9..4463a4f4f3 100644 --- a/src/qml/compiler/qv4compilerscanfunctions_p.h +++ b/src/qml/compiler/qv4compilerscanfunctions_p.h @@ -96,6 +96,9 @@ protected: using Visitor::visit; using Visitor::endVisit; + bool preVisit(AST::Node *ast) override; + void postVisit(AST::Node *) override; + void checkDirectivePrologue(AST::StatementList *ast); void checkName(const QStringRef &name, const AST::SourceLocation &loc); @@ -169,6 +172,8 @@ protected: bool _allowFuncDecls; ContextType defaultProgramType; + unsigned _recursionDepth = 0; + private: static constexpr AST::Node *astNodeForGlobalEnvironment = nullptr; }; diff --git a/src/qml/parser/qqmljs.g b/src/qml/parser/qqmljs.g index 6549e5bfa3..860a4e999e 100644 --- a/src/qml/parser/qqmljs.g +++ b/src/qml/parser/qqmljs.g @@ -614,8 +614,16 @@ bool Parser::parse(int startToken) program = 0; do { - if (++tos == stack_size) + if (++tos == stack_size) { reallocateStack(); + if (stack_size > 10000) { + // We're now in some serious right-recursive stuff, which will probably result in + // an AST that's so deep that recursively visiting it will run out of stack space. + const QString msg = QCoreApplication::translate("QQmlParser", "Maximum statement or expression depth exceeded"); + diagnostic_messages.append(DiagnosticMessage(DiagnosticMessage::Error, token_buffer[0].loc, msg)); + return false; + } + } state_stack[tos] = action; -- cgit v1.2.3