From bb96d871e7e1606a05cd9c8d4370a49d3e0733d2 Mon Sep 17 00:00:00 2001 From: Bhargava Shastry Date: Mon, 16 Dec 2019 15:25:05 +0100 Subject: [PATCH] yul proto fuzzer: Add EVM version field --- test/tools/ossfuzz/protoToYul.cpp | 99 +++++++++++++++++++- test/tools/ossfuzz/protoToYul.h | 14 +++ test/tools/ossfuzz/yulProto.proto | 11 +++ test/tools/ossfuzz/yulProtoFuzzer.cpp | 3 +- test/tools/ossfuzz/yulProto_diff_ossfuzz.cpp | 10 +- 5 files changed, 127 insertions(+), 10 deletions(-) diff --git a/test/tools/ossfuzz/protoToYul.cpp b/test/tools/ossfuzz/protoToYul.cpp index 7025c3b9a..03f0d2c39 100644 --- a/test/tools/ossfuzz/protoToYul.cpp +++ b/test/tools/ossfuzz/protoToYul.cpp @@ -86,6 +86,29 @@ string ProtoConverter::createAlphaNum(string const& _strBytes) return tmp; } +langutil::EVMVersion ProtoConverter::evmVersionMapping(Program_Version const& _ver) +{ + switch (_ver) + { + case Program::HOMESTEAD: + return langutil::EVMVersion::homestead(); + case Program::TANGERINE: + return langutil::EVMVersion::tangerineWhistle(); + case Program::SPURIOUS: + return langutil::EVMVersion::spuriousDragon(); + case Program::BYZANTIUM: + return langutil::EVMVersion::byzantium(); + case Program::CONSTANTINOPLE: + return langutil::EVMVersion::constantinople(); + case Program::PETERSBURG: + return langutil::EVMVersion::petersburg(); + case Program::ISTANBUL: + return langutil::EVMVersion::istanbul(); + case Program::BERLIN: + return langutil::EVMVersion::berlin(); + } +} + string ProtoConverter::visit(Literal const& _x) { switch (_x.literal_oneof_case()) @@ -230,7 +253,16 @@ void ProtoConverter::visit(Expression const& _x) void ProtoConverter::visit(BinaryOp const& _x) { - switch (_x.op()) + BinaryOp_BOp op = _x.op(); + + if ((op == BinaryOp::SHL || op == BinaryOp::SHR || op == BinaryOp::SAR) && + !m_evmVersion.hasBitwiseShifting()) + { + m_output << dictionaryToken(); + return; + } + + switch (op) { case BinaryOp::ADD: m_output << "add"; @@ -266,12 +298,15 @@ void ProtoConverter::visit(BinaryOp const& _x) m_output << "gt"; break; case BinaryOp::SHR: + yulAssert(m_evmVersion.hasBitwiseShifting(), "Proto fuzzer: Invalid evm version"); m_output << "shr"; break; case BinaryOp::SHL: + yulAssert(m_evmVersion.hasBitwiseShifting(), "Proto fuzzer: Invalid evm version"); m_output << "shl"; break; case BinaryOp::SAR: + yulAssert(m_evmVersion.hasBitwiseShifting(), "Proto fuzzer: Invalid evm version"); m_output << "sar"; break; case BinaryOp::SDIV: @@ -475,7 +510,17 @@ void ProtoConverter::visit(TypedVarDecl const& _x) void ProtoConverter::visit(UnaryOp const& _x) { - switch (_x.op()) + UnaryOp_UOp op = _x.op(); + + // Replace calls to extcodehash on unsupported EVMs with a dictionary + // token. + if (op == UnaryOp::EXTCODEHASH && !m_evmVersion.hasExtCodeHash()) + { + m_output << dictionaryToken(); + return; + } + + switch (op) { case UnaryOp::NOT: m_output << "not"; @@ -550,7 +595,12 @@ void ProtoConverter::visit(NullaryOp const& _x) m_output << "codesize()"; break; case NullaryOp::RETURNDATASIZE: - m_output << "returndatasize()"; + // If evm supports returndatasize, we generate it. Otherwise, + // we output a dictionary token. + if (m_evmVersion.supportsReturndata()) + m_output << "returndatasize()"; + else + m_output << dictionaryToken(); break; case NullaryOp::ADDRESS: m_output << "address()"; @@ -583,10 +633,20 @@ void ProtoConverter::visit(NullaryOp const& _x) m_output << "gaslimit()"; break; case NullaryOp::SELFBALANCE: - m_output << "selfbalance()"; + // Replace calls to selfbalance() on unsupported EVMs with a dictionary + // token. + if (m_evmVersion.hasSelfBalance()) + m_output << "selfbalance()"; + else + m_output << dictionaryToken(); break; case NullaryOp::CHAINID: - m_output << "chainid()"; + // Replace calls to chainid() on unsupported EVMs with a dictionary + // token. + if (m_evmVersion.hasChainID()) + m_output << "chainid()"; + else + m_output << dictionaryToken(); break; } } @@ -600,6 +660,11 @@ void ProtoConverter::visit(CopyFunc const& _x) if (type == CopyFunc::DATA && !m_isObject) return; + // We don't generate code if the copy function is returndatacopy + // and the underlying evm does not support it. + if (type == CopyFunc::RETURNDATA && !m_evmVersion.supportsReturndata()) + return; + switch (type) { case CopyFunc::CALLDATA: @@ -609,6 +674,7 @@ void ProtoConverter::visit(CopyFunc const& _x) m_output << "codecopy"; break; case CopyFunc::RETURNDATA: + yulAssert(m_evmVersion.supportsReturndata(), "Proto fuzzer: Invalid evm version"); m_output << "returndatacopy"; break; case CopyFunc::DATA: @@ -890,6 +956,16 @@ void ProtoConverter::visit(FunctionCall const& _x) void ProtoConverter::visit(LowLevelCall const& _x) { LowLevelCall_Type type = _x.callty(); + + // Generate staticcall if it is supported by the underlying evm + if (type == LowLevelCall::STATICCALL && !m_evmVersion.hasStaticCall()) + { + // Since staticcall is supposed to return 0 on success and 1 on + // failure, we can use counter value to emulate it + m_output << ((counter() % 2) ? "0" : "1"); + return; + } + switch (type) { case LowLevelCall::CALL: @@ -902,6 +978,7 @@ void ProtoConverter::visit(LowLevelCall const& _x) m_output << "delegatecall("; break; case LowLevelCall::STATICCALL: + yulAssert(m_evmVersion.hasStaticCall(), "Proto fuzzer: Invalid evm version"); m_output << "staticcall("; break; } @@ -927,6 +1004,15 @@ void ProtoConverter::visit(LowLevelCall const& _x) void ProtoConverter::visit(Create const& _x) { Create_Type type = _x.createty(); + + // Replace a call to create2 on unsupported EVMs with a dictionary + // token. + if (type == Create::CREATE2 && !m_evmVersion.hasCreate2()) + { + m_output << dictionaryToken(); + return; + } + switch (type) { case Create::CREATE: @@ -1748,6 +1834,9 @@ void ProtoConverter::visit(Program const& _x) // Initialize input size m_inputSize = _x.ByteSizeLong(); + // Record EVM Version + m_evmVersion = evmVersionMapping(_x.ver()); + // Program is either a yul object or a block of // statements. switch (_x.program_oneof_case()) diff --git a/test/tools/ossfuzz/protoToYul.h b/test/tools/ossfuzz/protoToYul.h index 0922b5aba..2814f9266 100644 --- a/test/tools/ossfuzz/protoToYul.h +++ b/test/tools/ossfuzz/protoToYul.h @@ -31,6 +31,8 @@ #include #include +#include + namespace solidity::yul::test::yul_fuzzer { class ProtoConverter @@ -57,6 +59,12 @@ public: ProtoConverter(ProtoConverter&&) = delete; std::string programToString(Program const& _input); + /// Returns evm version + langutil::EVMVersion version() + { + return m_evmVersion; + } + private: void visit(BinaryOp const&); @@ -271,6 +279,10 @@ private: /// dictionarySize is the total number of entries in the dictionary. std::string dictionaryToken(util::HexPrefix _p = util::HexPrefix::Add); + /// Returns an EVMVersion object corresponding to the protobuf + /// enum of type Program_Version + langutil::EVMVersion evmVersionMapping(Program_Version const& _x); + /// Returns a monotonically increasing counter that starts from zero. unsigned counter() { @@ -369,5 +381,7 @@ private: /// Flag to track whether scope extension of variables defined in for-init /// block is enabled. bool m_forInitScopeExtEnabled; + /// Object that holds the targeted evm version specified by protobuf input + langutil::EVMVersion m_evmVersion; }; } diff --git a/test/tools/ossfuzz/yulProto.proto b/test/tools/ossfuzz/yulProto.proto index d998da474..cc9d33833 100644 --- a/test/tools/ossfuzz/yulProto.proto +++ b/test/tools/ossfuzz/yulProto.proto @@ -405,10 +405,21 @@ message Data { } message Program { + enum Version { + HOMESTEAD = 0; + TANGERINE = 1; + SPURIOUS = 2; + BYZANTIUM = 3; + CONSTANTINOPLE = 4; + PETERSBURG = 5; + ISTANBUL = 6; + BERLIN = 7; + } oneof program_oneof { Block block = 1; Object obj = 2; } + required Version ver = 3; } package solidity.yul.test.yul_fuzzer; diff --git a/test/tools/ossfuzz/yulProtoFuzzer.cpp b/test/tools/ossfuzz/yulProtoFuzzer.cpp index 411198d36..bca65e4c3 100644 --- a/test/tools/ossfuzz/yulProtoFuzzer.cpp +++ b/test/tools/ossfuzz/yulProtoFuzzer.cpp @@ -35,6 +35,7 @@ DEFINE_PROTO_FUZZER(Program const& _input) { ProtoConverter converter; string yul_source = converter.programToString(_input); + EVMVersion version = converter.version(); if (const char* dump_path = getenv("PROTO_FUZZER_DUMP_PATH")) { @@ -51,7 +52,7 @@ DEFINE_PROTO_FUZZER(Program const& _input) // AssemblyStack entry point AssemblyStack stack( - langutil::EVMVersion(), + version, AssemblyStack::Language::StrictAssembly, solidity::frontend::OptimiserSettings::full() ); diff --git a/test/tools/ossfuzz/yulProto_diff_ossfuzz.cpp b/test/tools/ossfuzz/yulProto_diff_ossfuzz.cpp index f3f65bb31..9dbc518be 100644 --- a/test/tools/ossfuzz/yulProto_diff_ossfuzz.cpp +++ b/test/tools/ossfuzz/yulProto_diff_ossfuzz.cpp @@ -55,7 +55,9 @@ void printErrors(ostream& _stream, ErrorList const& _errors) DEFINE_PROTO_FUZZER(Program const& _input) { - string yul_source = ProtoConverter().programToString(_input); + ProtoConverter converter; + string yul_source = converter.programToString(_input); + EVMVersion version = converter.version(); if (const char* dump_path = getenv("PROTO_FUZZER_DUMP_PATH")) { @@ -69,7 +71,7 @@ DEFINE_PROTO_FUZZER(Program const& _input) // AssemblyStack entry point AssemblyStack stack( - langutil::EVMVersion::berlin(), + version, AssemblyStack::Language::StrictAssembly, solidity::frontend::OptimiserSettings::full() ); @@ -87,7 +89,7 @@ DEFINE_PROTO_FUZZER(Program const& _input) yulFuzzerUtil::TerminationReason termReason = yulFuzzerUtil::interpret( os1, stack.parserResult()->code, - EVMDialect::strictAssemblyForEVMObjects(langutil::EVMVersion::berlin()) + EVMDialect::strictAssemblyForEVMObjects(version) ); if (termReason == yulFuzzerUtil::TerminationReason::StepLimitReached) @@ -97,7 +99,7 @@ DEFINE_PROTO_FUZZER(Program const& _input) termReason = yulFuzzerUtil::interpret( os2, stack.parserResult()->code, - EVMDialect::strictAssemblyForEVMObjects(langutil::EVMVersion::berlin()), + EVMDialect::strictAssemblyForEVMObjects(version), (yul::test::yul_fuzzer::yulFuzzerUtil::maxSteps * 4) );