diff --git a/test/tools/ossfuzz/CMakeLists.txt b/test/tools/ossfuzz/CMakeLists.txt index 265533397..8293cb57b 100644 --- a/test/tools/ossfuzz/CMakeLists.txt +++ b/test/tools/ossfuzz/CMakeLists.txt @@ -16,6 +16,8 @@ if (OSSFUZZ) yul_proto_diff_ossfuzz yul_proto_diff_custom_mutate_ossfuzz stack_reuse_codegen_ossfuzz + yul_evm_diff_ossfuzz + yul_evm_mutator_diff_ossfuzz ) add_custom_target(ossfuzz_abiv2) @@ -133,6 +135,69 @@ if (OSSFUZZ) -Wno-suggest-destructor-override ) + add_executable(yul_evm_diff_ossfuzz + ../../libyul/YulOptimizerTestCommon.cpp + YulToEvmDifferentialFuzzer.cpp + protoToYul.cpp + yulProto.pb.cc + ../../EVMHost.cpp + YulEvmoneInterface.cpp + yulFuzzerCommon.cpp + ) + target_include_directories(yul_evm_diff_ossfuzz PRIVATE + /usr/include/libprotobuf-mutator + ) + target_link_libraries(yul_evm_diff_ossfuzz PRIVATE + yul + yulInterpreter + evmc + evmone-standalone + protobuf-mutator-libfuzzer.a + protobuf-mutator.a + protobuf.a + ) + set_target_properties(yul_evm_diff_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE}) + target_compile_options(yul_evm_diff_ossfuzz PUBLIC + ${COMPILE_OPTIONS} + -Wno-sign-conversion + -Wno-inconsistent-missing-destructor-override + -Wno-unused-parameter + -Wno-zero-length-array + -Wno-suggest-destructor-override + ) + + add_executable(yul_evm_mutator_diff_ossfuzz + ../../libyul/YulOptimizerTestCommon.cpp + YulToEvmDifferentialFuzzer.cpp + protoToYul.cpp + yulProto.pb.cc + ../../EVMHost.cpp + YulEvmoneInterface.cpp + yulFuzzerCommon.cpp + protomutators/YulProtoMutator.cpp + ) + target_include_directories(yul_evm_mutator_diff_ossfuzz PRIVATE + /usr/include/libprotobuf-mutator + ) + target_link_libraries(yul_evm_mutator_diff_ossfuzz PRIVATE + yul + yulInterpreter + evmc + evmone-standalone + protobuf-mutator-libfuzzer.a + protobuf-mutator.a + protobuf.a + ) + set_target_properties(yul_evm_mutator_diff_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE}) + target_compile_options(yul_evm_mutator_diff_ossfuzz PUBLIC + ${COMPILE_OPTIONS} + -Wno-sign-conversion + -Wno-inconsistent-missing-destructor-override + -Wno-unused-parameter + -Wno-zero-length-array + -Wno-suggest-destructor-override + ) + add_executable(abiv2_proto_ossfuzz ../../EVMHost.cpp abiV2ProtoFuzzer.cpp diff --git a/test/tools/ossfuzz/YulToEvmDifferentialFuzzer.cpp b/test/tools/ossfuzz/YulToEvmDifferentialFuzzer.cpp new file mode 100644 index 000000000..8f195f120 --- /dev/null +++ b/test/tools/ossfuzz/YulToEvmDifferentialFuzzer.cpp @@ -0,0 +1,246 @@ +/* + This file is part of solidity. + + solidity is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + solidity is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with solidity. If not, see . +*/ + +#include +#include +#include + +#include + +#include + +#include + +#include + +#include +#include + +#include +#include + +#include + +#include + +#include + +#include + +#include + +#include + +using namespace solidity; +using namespace solidity::test; +using namespace solidity::test::fuzzer; +using namespace solidity::yul; +using namespace solidity::yul::test; +using namespace solidity::yul::test::yul_fuzzer; +using namespace solidity::langutil; +using namespace solidity::util; +using namespace std; + +namespace +{ +#if 0 +/// @returns true if there are no recursive functions, false otherwise. +bool recursiveFunctionHasUnreachableVariables(Dialect const& _dialect, yul::Object& _object) +{ + auto recursiveFunctions = CallGraphGenerator::callGraph(*_object.code).recursiveFunctions(); + for(auto&& [function, variables]: CompilabilityChecker{ + _dialect, + _object, + true + }.unreachableVariables + ) + if(recursiveFunctions.count(function)) + return true; + return false; +} +#endif + +string optimiseYulSubObject( + shared_ptr _subObject, + Dialect const& _dialect, + bool _forceOldPipeline +) +{ + YulOptimizerTestCommon optimizerTest( + _subObject, + _dialect, + _forceOldPipeline + ); + // Run circular references pruner and then stack limit evader. + string step = "fullSuite"; + optimizerTest.setStep(step); + shared_ptr astBlock = optimizerTest.run(); + string optimisedSubObject = AsmPrinter{}(*astBlock); + string optimisedProgram = Whiskers(R"( + object "main" { + code { + codecopy(0, dataoffset("deployed"), datasize("deployed")) + return(0, datasize("deployed")) + } + object "deployed" { + code { + + } + } + })") + ("fuzzerInput", optimisedSubObject) + .render(); + return optimisedProgram; +} + +void runOnce( + EVMHost& _host, + bytes& _deployCode, + ostringstream& _state, + string _yulSubObject +) +{ + // Reset host before running optimised code. + _host.reset(); + evmc::result deployResultOpt = YulEvmoneUtility{}.deployCode(_deployCode, _host); + solAssert( + deployResultOpt.status_code == EVMC_SUCCESS, + "Evmone: Contract creation failed" + ); + auto callMessageOpt = YulEvmoneUtility{}.callMessage(deployResultOpt.create_address); + evmc::result callResultOpt = _host.call(callMessageOpt); + // Bail out if we ran out of gas. + if (callResultOpt.status_code == EVMC_OUT_OF_GAS) + return; + bool noRevertInSource = _yulSubObject.find("revert") == string::npos; + bool noInvalidInSource = _yulSubObject.find("invalid") == string::npos; + if (noRevertInSource) + solAssert( + callResultOpt.status_code != EVMC_REVERT, + "SolidityEvmoneInterface: EVM One reverted" + ); + if (noInvalidInSource) + solAssert( + callResultOpt.status_code != EVMC_INVALID_INSTRUCTION, + "Invalid instruction." + ); + solAssert( + (callResultOpt.status_code == EVMC_SUCCESS || + (!noRevertInSource && callResultOpt.status_code == EVMC_REVERT) || + (!noInvalidInSource && callResultOpt.status_code == EVMC_INVALID_INSTRUCTION)), + "Call failed." + ); + _state << EVMHostPrinter{_host, deployResultOpt.create_address}.state(); +} +} + +static evmc::VM evmone = evmc::VM{evmc_create_evmone()}; + +DEFINE_PROTO_FUZZER(Program const& _input) +{ + // Solidity creates an invalid instruction for subobjects, so we simply + // ignore them in this fuzzer. + if (_input.has_obj()) + return; + ProtoConverter converter; + string fuzzerInput = converter.programToString(_input); + langutil::EVMVersion version; + EVMHost hostContext(version, evmone); + + YulStringRepository::reset(); + + + if (const char* dump_path = getenv("PROTO_FUZZER_DUMP_PATH")) + { + // Package test case into a sub-object + Whiskers yulObjectFormat(R"( + object "main" { + code { + codecopy(0, dataoffset("deployed"), datasize("deployed")) + return(0, datasize("deployed")) + } + object "deployed" { + code { + + } + } + })"); + string yul_source = yulObjectFormat("fuzzerInput", fuzzerInput).render(); + ofstream of(dump_path); + of.write(yul_source.data(), static_cast(yul_source.size())); + } + + solidity::frontend::OptimiserSettings settings = solidity::frontend::OptimiserSettings::none(); + AssemblyStack yulStack(version, AssemblyStack::Language::StrictAssembly, settings); + solAssert( + yulStack.parseAndAnalyze("source", fuzzerInput), + "Parsing fuzzer generated input failed." + ); + std::shared_ptr fuzzedYulObject = yulStack.parserResult(); + Dialect const& dialect = EVMDialect::strictAssemblyForEVMObjects(version); + // Run Yul optimizer with old pipeline + string controlOptYul = optimiseYulSubObject(fuzzedYulObject, dialect, true); + bytes controlByteCode; + settings.optimizeStackAllocation = true; + settings.forceOldPipeline = true; + bool controlThrowsStackTooDeep = false; + try + { + controlByteCode = YulAssembler{version, settings, controlOptYul}.assemble(); + } + catch (yul::StackTooDeepError const&) + { + controlThrowsStackTooDeep = true; + } + + ostringstream controlState; + runOnce(hostContext, controlByteCode, controlState, fuzzerInput); + // Run Yul optimizer with new pipeline + string experimentOptYul = optimiseYulSubObject(fuzzedYulObject, dialect, false); + settings.forceOldPipeline = false; + bytes experimentByteCode; +#if 0 + bool recursiveFunction = recursiveFunctionHasUnreachableVariables(dialect, *fuzzedYulObject); + if (recursiveFunction) + cout << "Fuzzer print: Recursive function" << endl; +#endif + experimentByteCode = YulAssembler{version, settings, experimentOptYul}.assemble(); +#if 0 + catch (yul::StackTooDeepError const&) + { + // If there are recursive functions, stack too deep errors are expected + // even post stack evasion optimisation and hence ignored. Otherwise, + // they are rethrown for further investigation. + if (recursiveFunction) + return; + throw; + } +#endif + + ostringstream experimentState; + runOnce(hostContext, experimentByteCode, experimentState, fuzzerInput); + + if ( + !controlThrowsStackTooDeep && + (controlState.str() != experimentState.str()) + ) + { + cout << controlState.str() << endl; + cout << experimentState.str() << endl; + solAssert(false, "State of unoptimised and optimised code do not match."); + } +} diff --git a/test/tools/ossfuzz/protomutators/YulProtoMutator.cpp b/test/tools/ossfuzz/protomutators/YulProtoMutator.cpp index f1f3fbc93..444977d88 100644 --- a/test/tools/ossfuzz/protomutators/YulProtoMutator.cpp +++ b/test/tools/ossfuzz/protomutators/YulProtoMutator.cpp @@ -46,6 +46,18 @@ static LPMPostProcessor addStoreToZero( } ); +static LPMPostProcessor addStackVariables( + [](Block* _message, unsigned _seed) + { + if (_seed % YPM::s_highIP == 0) + { + MutationInfo m{_message, "Added variable to block."}; + _message->add_statements()->set_allocated_multidecl(new MultiVarDecl()); + _message->add_statements()->set_allocated_decl(new VarDecl()); + _message->add_statements()->set_allocated_assignment(new AssignmentStatement()); + } + } +); namespace {