diff --git a/test/libyul/EwasmTranslationTest.cpp b/test/libyul/EwasmTranslationTest.cpp index fe3d42969..a8fc1a586 100644 --- a/test/libyul/EwasmTranslationTest.cpp +++ b/test/libyul/EwasmTranslationTest.cpp @@ -102,6 +102,7 @@ string EwasmTranslationTest::interpret() InterpreterState state; state.maxTraceSize = 10000; state.maxSteps = 1000000; + state.maxExprNesting = 64; try { Interpreter::run(state, WasmDialect{}, *m_object->code); diff --git a/test/libyul/YulInterpreterTest.cpp b/test/libyul/YulInterpreterTest.cpp index ec1fa53f1..259e879b3 100644 --- a/test/libyul/YulInterpreterTest.cpp +++ b/test/libyul/YulInterpreterTest.cpp @@ -89,6 +89,7 @@ string YulInterpreterTest::interpret() InterpreterState state; state.maxTraceSize = 32; state.maxSteps = 512; + state.maxExprNesting = 64; try { Interpreter::run(state, EVMDialect::strictAssemblyForEVMObjects(langutil::EVMVersion{}), *m_ast); diff --git a/test/libyul/yulInterpreterTests/expr_nesting_depth_exceeded.yul b/test/libyul/yulInterpreterTests/expr_nesting_depth_exceeded.yul new file mode 100644 index 000000000..c1456b00f --- /dev/null +++ b/test/libyul/yulInterpreterTests/expr_nesting_depth_exceeded.yul @@ -0,0 +1,14 @@ +{ + function f(x) -> y + { + // 32 nested additions are computed in + // exactly 66 expression evaluation steps + y := add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,x)))))))))))))))))))))))))))))))) + } + mstore(0,f(0)) +} +// ---- +// Trace: +// Maximum expression nesting level reached. +// Memory dump: +// Storage dump: diff --git a/test/libyul/yulInterpreterTests/expr_nesting_depth_not_exceeded.yul b/test/libyul/yulInterpreterTests/expr_nesting_depth_not_exceeded.yul new file mode 100644 index 000000000..0a3470571 --- /dev/null +++ b/test/libyul/yulInterpreterTests/expr_nesting_depth_not_exceeded.yul @@ -0,0 +1,14 @@ +{ + function f(x) -> y + { + // 31 nested additions are computed in + // exactly 64 expression evaluation steps + y := add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,add(0x1,x))))))))))))))))))))))))))))))) + } + mstore(0,f(0)) +} +// ---- +// Trace: +// Memory dump: +// 0: 000000000000000000000000000000000000000000000000000000000000001f +// Storage dump: diff --git a/test/tools/ossfuzz/yulFuzzerCommon.cpp b/test/tools/ossfuzz/yulFuzzerCommon.cpp index 86cea8327..d977c7761 100644 --- a/test/tools/ossfuzz/yulFuzzerCommon.cpp +++ b/test/tools/ossfuzz/yulFuzzerCommon.cpp @@ -27,12 +27,14 @@ yulFuzzerUtil::TerminationReason yulFuzzerUtil::interpret( shared_ptr _ast, Dialect const& _dialect, size_t _maxSteps, - size_t _maxTraceSize + size_t _maxTraceSize, + size_t _maxExprNesting ) { InterpreterState state; state.maxTraceSize = _maxTraceSize; state.maxSteps = _maxSteps; + state.maxExprNesting = _maxExprNesting; // Add 64 bytes of pseudo-randomly generated calldata so that // calldata opcodes perform non trivial work. state.calldata = { @@ -59,6 +61,10 @@ yulFuzzerUtil::TerminationReason yulFuzzerUtil::interpret( { reason = TerminationReason::TraceLimitReached; } + catch (ExpressionNestingLimitReached const&) + { + reason = TerminationReason::ExpresionNestingLimitReached; + } catch (ExplicitlyTerminated const&) { reason = TerminationReason::ExplicitlyTerminated; diff --git a/test/tools/ossfuzz/yulFuzzerCommon.h b/test/tools/ossfuzz/yulFuzzerCommon.h index cc20d6163..489cc4532 100644 --- a/test/tools/ossfuzz/yulFuzzerCommon.h +++ b/test/tools/ossfuzz/yulFuzzerCommon.h @@ -28,6 +28,7 @@ struct yulFuzzerUtil ExplicitlyTerminated, StepLimitReached, TraceLimitReached, + ExpresionNestingLimitReached, None }; @@ -36,10 +37,12 @@ struct yulFuzzerUtil std::shared_ptr _ast, Dialect const& _dialect, size_t _maxSteps = maxSteps, - size_t _maxTraceSize = maxTraceSize + size_t _maxTraceSize = maxTraceSize, + size_t _maxExprNesting = maxExprNesting ); static size_t constexpr maxSteps = 100; static size_t constexpr maxTraceSize = 75; + static size_t constexpr maxExprNesting = 64; }; } diff --git a/test/tools/ossfuzz/yulProto_diff_ossfuzz.cpp b/test/tools/ossfuzz/yulProto_diff_ossfuzz.cpp index de9bfe9c7..2ee2b702e 100644 --- a/test/tools/ossfuzz/yulProto_diff_ossfuzz.cpp +++ b/test/tools/ossfuzz/yulProto_diff_ossfuzz.cpp @@ -99,7 +99,8 @@ DEFINE_PROTO_FUZZER(Program const& _input) if ( termReason == yulFuzzerUtil::TerminationReason::StepLimitReached || - termReason == yulFuzzerUtil::TerminationReason::TraceLimitReached + termReason == yulFuzzerUtil::TerminationReason::TraceLimitReached || + termReason == yulFuzzerUtil::TerminationReason::ExpresionNestingLimitReached ) return; @@ -109,10 +110,10 @@ DEFINE_PROTO_FUZZER(Program const& _input) stack.parserResult()->code, EVMDialect::strictAssemblyForEVMObjects(version) ); - if ( termReason == yulFuzzerUtil::TerminationReason::StepLimitReached || - termReason == yulFuzzerUtil::TerminationReason::TraceLimitReached + termReason == yulFuzzerUtil::TerminationReason::TraceLimitReached || + termReason == yulFuzzerUtil::TerminationReason::ExpresionNestingLimitReached ) return; diff --git a/test/tools/yulInterpreter/Interpreter.cpp b/test/tools/yulInterpreter/Interpreter.cpp index 70cd9dc29..df1aaaef3 100644 --- a/test/tools/yulInterpreter/Interpreter.cpp +++ b/test/tools/yulInterpreter/Interpreter.cpp @@ -247,6 +247,7 @@ void Interpreter::incrementStep() void ExpressionEvaluator::operator()(Literal const& _literal) { + incrementStep(); static YulString const trueString("true"); static YulString const falseString("false"); @@ -256,6 +257,7 @@ void ExpressionEvaluator::operator()(Literal const& _literal) void ExpressionEvaluator::operator()(Identifier const& _identifier) { solAssert(m_variables.count(_identifier.name), ""); + incrementStep(); setValue(m_variables.at(_identifier.name)); } @@ -326,6 +328,7 @@ void ExpressionEvaluator::evaluateArgs( vector> const* _literalArguments ) { + incrementStep(); vector values; size_t i = 0; /// Function arguments are evaluated in reverse. @@ -341,3 +344,13 @@ void ExpressionEvaluator::evaluateArgs( m_values = std::move(values); std::reverse(m_values.begin(), m_values.end()); } + +void ExpressionEvaluator::incrementStep() +{ + m_nestingLevel++; + if (m_state.maxExprNesting > 0 && m_nestingLevel > m_state.maxExprNesting) + { + m_state.trace.emplace_back("Maximum expression nesting level reached."); + throw ExpressionNestingLimitReached(); + } +} diff --git a/test/tools/yulInterpreter/Interpreter.h b/test/tools/yulInterpreter/Interpreter.h index 42c8c9138..7af799bbd 100644 --- a/test/tools/yulInterpreter/Interpreter.h +++ b/test/tools/yulInterpreter/Interpreter.h @@ -55,6 +55,10 @@ class TraceLimitReached: public InterpreterTerminatedGeneric { }; +class ExpressionNestingLimitReached: public InterpreterTerminatedGeneric +{ +}; + enum class ControlFlowState { Default, @@ -92,6 +96,7 @@ struct InterpreterState size_t maxTraceSize = 0; size_t maxSteps = 0; size_t numSteps = 0; + size_t maxExprNesting = 0; ControlFlowState controlFlowState = ControlFlowState::Default; void dumpTraceAndState(std::ostream& _out) const; @@ -202,6 +207,11 @@ private: std::vector> const* _literalArguments ); + /// Increment evaluation count, throwing exception if the + /// nesting level is beyond the upper bound configured in + /// the interpreter state. + void incrementStep(); + InterpreterState& m_state; Dialect const& m_dialect; /// Values of variables. @@ -209,6 +219,8 @@ private: Scope& m_scope; /// Current value of the expression std::vector m_values; + /// Current expression nesting level + unsigned m_nestingLevel = 0; }; }