diff --git a/test/tools/ossfuzz/CMakeLists.txt b/test/tools/ossfuzz/CMakeLists.txt index 83543b9f0..516bf9974 100644 --- a/test/tools/ossfuzz/CMakeLists.txt +++ b/test/tools/ossfuzz/CMakeLists.txt @@ -10,7 +10,7 @@ add_dependencies(ossfuzz if (OSSFUZZ) add_custom_target(ossfuzz_proto) - add_dependencies(ossfuzz_proto yul_proto_ossfuzz yul_proto_diff_ossfuzz) + add_dependencies(ossfuzz_proto yul_proto_ossfuzz yul_proto_diff_ossfuzz sol_arith_proto_ossfuzz) add_custom_target(ossfuzz_abiv2) add_dependencies(ossfuzz_abiv2 abiv2_proto_ossfuzz) @@ -78,6 +78,48 @@ if (OSSFUZZ) protobuf.a ) set_target_properties(abiv2_proto_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE}) + + add_executable(sol_arith_proto_ossfuzz + solArithProtoFuzzer.cpp + solarithprotoToSol.cpp + solArith.pb.cc + abiV2FuzzerCommon.cpp + ../../EVMHost.cpp + ) + target_include_directories(sol_arith_proto_ossfuzz PRIVATE + /usr/include/libprotobuf-mutator + ) + target_link_libraries(sol_arith_proto_ossfuzz PRIVATE solidity libsolc + evmc + evmone-standalone + protobuf-mutator-libfuzzer.a + protobuf-mutator.a + protobuf.a + ) + set_target_properties(sol_arith_proto_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE}) + +# add_executable(sol_proto_ossfuzz +# solProtoFuzzer.cpp +# protoToSol.cpp +# solProto.pb.cc +# abiV2FuzzerCommon.cpp +# ../../EVMHost.cpp +# SolProtoAdaptor.cpp +# ) +# target_include_directories(sol_proto_ossfuzz PRIVATE +# /usr/include/libprotobuf-mutator +# /home/bhargava/work/github/solidity/deps/LPM/external.protobuf/include +# /home/bhargava/work/github/solidity/deps/evmone/include +# /home/bhargava/work/github/solidity/deps/libprotobuf-mutator +# ) +# target_link_libraries(sol_proto_ossfuzz PRIVATE solidity libsolc +# evmc +# evmone-standalone +# protobuf-mutator-libfuzzer.a +# protobuf-mutator.a +# protobuf.a +# ) +# set_target_properties(sol_proto_ossfuzz PROPERTIES LINK_FLAGS ${LIB_FUZZING_ENGINE}) else() add_library(solc_opt_ossfuzz solc_opt_ossfuzz.cpp diff --git a/test/tools/ossfuzz/solArith.proto b/test/tools/ossfuzz/solArith.proto new file mode 100644 index 000000000..a64ec982f --- /dev/null +++ b/test/tools/ossfuzz/solArith.proto @@ -0,0 +1,116 @@ +/* + 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 . +*/ + +syntax = "proto2"; + +message Type { + enum Sign { + SIGNED = 0; + UNSIGNED = 1; + } + required Sign s = 1; + required uint32 bytewidth = 2; +} + +message UnaryOpStmt { + enum Op { + POSTINC = 1; + POSTDEC = 2; + PREINC = 3; + PREDEC = 4; + } + required Op op = 1; + required VarRef v = 2; +} + +message BinaryOpStmt { + enum Op { + ADDSELF = 0; + SUBSELF = 1; + MULSELF = 2; + DIVSELF = 3; + MODSELF = 4; + SHLSELF = 5; + SHRSELF = 6; + } + required Op op = 1; + required VarRef left = 2; + required Expression right = 3; +} + +message BinaryOp { + enum Op { + ADD = 0; + SUB = 1; + MUL = 2; + DIV = 3; + MOD = 4; + EXP = 10; + SHL = 11; + SHR = 12; + } + required Op op = 1; + required Expression left = 2; + required Expression right = 3; +} + +message VarDecl { + required Type t = 1; + required Expression value = 2; +} + +message Assignment { + required VarRef id = 1; + required Expression value = 2; +} + +message VarRef { + required uint32 id = 1; +} + +message Expression { + oneof expr_oneof { + VarRef v = 1; + BinaryOp bop = 2; + } +} + +message Return { + required Expression e = 1; +} + +message Statement { + oneof stmt_oneof { + VarDecl vd = 1; + Assignment a = 2; + UnaryOpStmt u = 3; + } +} + +message Block { + required VarDecl v = 1; + repeated Statement s = 2; + required Return r = 3; +} + +message Program { + required Block b = 1; + required Type.Sign s = 2; + required uint64 seed = 3; +} + +package solidity.test.solarithfuzzer; \ No newline at end of file diff --git a/test/tools/ossfuzz/solArithProtoFuzzer.cpp b/test/tools/ossfuzz/solArithProtoFuzzer.cpp new file mode 100644 index 000000000..d71cec4e9 --- /dev/null +++ b/test/tools/ossfuzz/solArithProtoFuzzer.cpp @@ -0,0 +1,255 @@ +/* + 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 + +static evmc::VM evmone = evmc::VM{evmc_create_evmone()}; + +using namespace solidity::test::abiv2fuzzer; +using namespace solidity::test::solarithfuzzer; +using namespace solidity; +using namespace solidity::test; +using namespace solidity::util; +using namespace std; + +namespace +{ +/// Test function returns a uint256 value +//static size_t const expectedOutputLength = 32; +///// Expected output value is decimal 0 +//static uint8_t const expectedOutput[expectedOutputLength] = { +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, +// 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 +//}; + +/// Compares the contents of the memory address pointed to +/// by `_result` of `_length` bytes to the expected output. +/// Returns true if `_result` matches expected output, false +/// otherwise. +//bool isOutputExpected(evmc::result const& _run) +//{ +// if (_run.output_size != expectedOutputLength) +// return false; +// +// return (memcmp(_run.output_data, expectedOutput, expectedOutputLength) == 0); +//} + +bool compareRuns(evmc::result const& _run1, evmc::result const& _run2) +{ + if (_run1.output_size != _run2.output_size) + return false; + return memcmp(_run1.output_data, _run2.output_data, _run1.output_size) == 0; +} + +/// Accepts a reference to a user-specified input and returns an +/// evmc_message with all of its fields zero initialized except +/// gas and input fields. +/// The gas field is set to the maximum permissible value so that we +/// don't run into out of gas errors. The input field is copied from +/// user input. +evmc_message initializeMessage(bytes const& _input) +{ + // Zero initialize all message fields + evmc_message msg = {}; + // Gas available (value of type int64_t) is set to its maximum + // value. + msg.gas = std::numeric_limits::max(); + msg.input_data = _input.data(); + msg.input_size = _input.size(); + return msg; +} + +/// Accepts host context implementation, and keccak256 hash of the function +/// to be called at a specified address in the simulated blockchain as +/// input and returns the result of the execution of the called function. +evmc::result executeContract( + EVMHost& _hostContext, + bytes const& _functionHash, + evmc_address _deployedAddress +) +{ + evmc_message message = initializeMessage(_functionHash); + message.destination = _deployedAddress; + message.kind = EVMC_CALL; + return _hostContext.call(message); +} + +/// Accepts a reference to host context implementation and byte code +/// as input and deploys it on the simulated blockchain. Returns the +/// result of deployment. +evmc::result deployContract(EVMHost& _hostContext, bytes const& _code) +{ + evmc_message message = initializeMessage(_code); + message.kind = EVMC_CREATE; + return _hostContext.call(message); +} + +std::pair compileContract( + std::string _sourceCode, + std::string _contractName, + std::map const& _libraryAddresses = {}, + frontend::OptimiserSettings _optimization = frontend::OptimiserSettings::minimal() +) +{ + try + { + // Compile contract generated by the proto fuzzer + SolidityCompilationFramework solCompilationFramework; + return std::make_pair( + solCompilationFramework.compileContract(_sourceCode, _contractName, _libraryAddresses, _optimization), + solCompilationFramework.getMethodIdentifiers() + ); + } + // Ignore stack too deep errors during compilation + catch (evmasm::StackTooDeepException const&) + { + return std::make_pair(bytes{}, Json::Value(0)); + } +} + +evmc::result deployAndExecute(EVMHost& _hostContext, bytes _byteCode, std::string _hexEncodedInput) +{ + // Deploy contract and signal failure if deploy failed + evmc::result createResult = deployContract(_hostContext, _byteCode); + solAssert( + createResult.status_code == EVMC_SUCCESS, + "Proto solc fuzzer: Contract creation failed" + ); + + // Execute test function and signal failure if EVM reverted or + // did not return expected output on successful execution. + evmc::result callResult = executeContract( + _hostContext, + fromHex(_hexEncodedInput), + createResult.create_address + ); + + // We don't care about EVM One failures other than EVMC_REVERT + solAssert(callResult.status_code != EVMC_REVERT, "Proto solc fuzzer: EVM One reverted"); + return callResult; +} + +evmc::result compileDeployAndExecute( + std::string _sourceCode, + std::string _contractName, + std::string _methodName, + frontend::OptimiserSettings _optimization, + std::string _libraryName = {} +) +{ + bytes libraryBytecode; + Json::Value libIds; + // We target the default EVM which is the latest + langutil::EVMVersion version = {}; + EVMHost hostContext(version, evmone); + std::map _libraryAddressMap; + + // First deploy library + if (!_libraryName.empty()) + { + tie(libraryBytecode, libIds) = compileContract( + _sourceCode, + _libraryName, + {}, + _optimization + ); + // Deploy contract and signal failure if deploy failed + evmc::result createResult = deployContract(hostContext, libraryBytecode); + solAssert( + createResult.status_code == EVMC_SUCCESS, + "Proto solc fuzzer: Library deployment failed" + ); + _libraryAddressMap[_libraryName] = EVMHost::convertFromEVMC(createResult.create_address); + } + + auto [bytecode, ids] = compileContract( + _sourceCode, + _contractName, + _libraryAddressMap, + _optimization + ); + + return deployAndExecute( + hostContext, + bytecode, + ids[_methodName].asString() + ); +} +} + +DEFINE_PROTO_FUZZER(Program const& _input) +{ + ProtoConverter converter; + string sol_source = converter.programToString(_input); + + if (const char* dump_path = getenv("PROTO_FUZZER_DUMP_PATH")) + { + // With libFuzzer binary run this to generate a YUL source file x.yul: + // PROTO_FUZZER_DUMP_PATH=x.yul ./a.out proto-input + ofstream of(dump_path); + of.write(sol_source.data(), sol_source.size()); + } + + if (const char* dump_path = getenv("SOL_DEBUG_FILE")) + { + sol_source.clear(); + // With libFuzzer binary run this to generate a YUL source file x.yul: + // PROTO_FUZZER_LOAD_PATH=x.yul ./a.out proto-input + ifstream ifstr(dump_path); + sol_source = { + std::istreambuf_iterator(ifstr), + std::istreambuf_iterator() + }; + std::cout << sol_source << std::endl; + } + + auto minimalResult = compileDeployAndExecute( + sol_source, + ":C", + "test()", + frontend::OptimiserSettings::minimal(), + "" + ); + bool successState = minimalResult.status_code == EVMC_SUCCESS; + + auto optResult = compileDeployAndExecute( + sol_source, + ":C", + "test()", + frontend::OptimiserSettings::standard(), + "" + ); + if (successState) + { + solAssert( + optResult.status_code == EVMC_SUCCESS, + "Sol arith fuzzer: Optimal code failed" + ); + solAssert( + compareRuns(minimalResult, optResult), + "Sol arith fuzzer: Runs produced different result" + ); + } +} \ No newline at end of file diff --git a/test/tools/ossfuzz/solarithprotoToSol.cpp b/test/tools/ossfuzz/solarithprotoToSol.cpp new file mode 100644 index 000000000..23b5e2d9a --- /dev/null +++ b/test/tools/ossfuzz/solarithprotoToSol.cpp @@ -0,0 +1,199 @@ +#include + +#include + +#include + +using namespace solidity::test::solarithfuzzer; +using namespace solidity; +using namespace solidity::util; +using namespace std; + +string ProtoConverter::programToString(Program const& _program) +{ + m_rand = make_unique( + SolRandomNumGenerator(_program.seed()) + ); + return visit(_program); +} + +string ProtoConverter::visit(Program const& _program) +{ + Whiskers p(R"(pragma solidity >= 0.0.0;)"); + Whiskers c(R"(contract C {)"); + Whiskers t(R"(function test() public returns ())"); + m_returnType = signString(_program.s()); + t("type", m_returnType); + t("body", visit(_program.b())); + return p.render() + + '\n' + + c.render() + + '\n' + + '\t' + + t.render() + + '\n' + + '}'; +} + +string ProtoConverter::visit(Return const& _ret) +{ + Whiskers r(R"(return ();)"); + r("expr", visit(_ret.e())); + r("type", m_returnType); + return "\t\t" + r.render() + '\n'; +} + +string ProtoConverter::visit(Block const& _block) +{ + ostringstream blockStr; + blockStr << '\n' + << '\t' + << '{' + << '\n'; + blockStr << visit(_block.v()); + for (auto const& s: _block.s()) + blockStr << visit(s); + blockStr << visit(_block.r()); + blockStr << '\n' + << '\t' + << '}'; + return blockStr.str(); +} + +string ProtoConverter::visit(Statement const& _stmt) +{ + switch (_stmt.stmt_oneof_case()) + { + case Statement::kVd: + return visit(_stmt.vd()); + case Statement::kA: + return visit(_stmt.a()); + case Statement::kU: + return visit(_stmt.u()); + case Statement::STMT_ONEOF_NOT_SET: + return ""; + } +} + +string ProtoConverter::visit(VarDecl const& _vardecl) +{ + bool varExists = varAvailable(); + Whiskers v(R"( = ();)"); + string type = visit(_vardecl.t()); + string varName = newVarName(); + m_varTypeMap.emplace(varName, type); + v("type", type); + v("varName", varName); + v("value", varExists ? visit(_vardecl.value()) : to_string((*m_rand)())); + incrementVarCounter(); + return "\t\t" + v.render() + '\n'; +} + +string ProtoConverter::visit(Type const& _type) +{ + return signString(_type.s()) + widthString(_type.bytewidth()); +} + +string ProtoConverter::visit(UnaryOpStmt const& _uop) +{ + switch (_uop.op()) + { + case UnaryOpStmt_Op_POSTINC: + return "\t\t" + visit(_uop.v()) + "++;\n"; + case UnaryOpStmt_Op_POSTDEC: + return "\t\t" + visit(_uop.v()) + "--;\n"; + case UnaryOpStmt_Op_PREINC: + return "\t\t++" + visit(_uop.v()) + ";\n"; + case UnaryOpStmt_Op_PREDEC: + return "\t\t--" + visit(_uop.v()) + ";\n"; + } +} + +string ProtoConverter::visit(BinaryOp const& _bop) +{ + string op{}; + switch (_bop.op()) + { + case BinaryOp_Op_ADD: + op = " + "; + break; + case BinaryOp_Op_SUB: + op = " - "; + break; + case BinaryOp_Op_MUL: + op = " * "; + break; + case BinaryOp_Op_DIV: + op = " / "; + break; + case BinaryOp_Op_MOD: + op = " % "; + break; +// case BinaryOp_Op_ADDSELF: +// op = " += "; +// break; +// case BinaryOp_Op_SUBSELF: +// op = " -= "; +// break; +// case BinaryOp_Op_MULSELF: +// op = " *= "; +// break; +// case BinaryOp_Op_DIVSELF: +// op = " /= "; +// break; +// case BinaryOp_Op_MODSELF: +// op = " %= "; +// break; + case BinaryOp_Op_EXP: + op = " ** "; + break; + case BinaryOp_Op_SHL: + op = " << "; + break; + case BinaryOp_Op_SHR: + op = " >> "; + break; +// case BinaryOp_Op_SHLSELF: +// op = " <<= "; +// break; +// case BinaryOp_Op_SHRSELF: +// op = " >>= "; +// break; + } + return visit(_bop.left()) + op + visit(_bop.right()); +} + +string ProtoConverter::visit(Expression const& _expr) +{ + switch (_expr.expr_oneof_case()) + { + case Expression::kV: + solAssert(varAvailable(), "Sol arith fuzzer: Varref unavaileble"); + return visit(_expr.v()); + case Expression::kBop: + return visit(_expr.bop()); + case Expression::EXPR_ONEOF_NOT_SET: + return "v0"; + } +} + +string ProtoConverter::visit(VarRef const&) +{ + return randomVarName(); +} + +string ProtoConverter::visit(Assignment const& _assignment) +{ + if (varAvailable()) + { + string varName = visit(_assignment.id()); + solAssert(m_varTypeMap.count(varName), "Sol arith fuzzer: Invalid varname"); + Whiskers a(R"( = ();)"); + a("varName", varName); + a("type", m_varTypeMap[varName]); + a("expr", visit(_assignment.value())); + return "\t\t" + a.render() + '\n'; + } + else + return ""; +} \ No newline at end of file diff --git a/test/tools/ossfuzz/solarithprotoToSol.h b/test/tools/ossfuzz/solarithprotoToSol.h new file mode 100644 index 000000000..4326320a5 --- /dev/null +++ b/test/tools/ossfuzz/solarithprotoToSol.h @@ -0,0 +1,114 @@ +#include + +#include + +namespace solidity::test::solarithfuzzer +{ +/// Random number generator that is seeded with a fuzzer +/// supplied unsigned integer. +struct SolRandomNumGenerator +{ + using RandomEngine = std::mt19937_64; + + explicit SolRandomNumGenerator(unsigned _seed): m_random(RandomEngine(_seed)) {} + + /// @returns a pseudo random unsigned integer + unsigned operator()() + { + return m_random(); + } + + RandomEngine m_random; +}; + +enum class Sign +{ + Signed, + Unsigned +}; + +struct SolVarRef +{ + SolVarRef(VarRef const& _varref); + + std::string str(); + + Sign m_sign; +}; + +struct SolExpression +{ + SolExpression(Expression const& _e); + + std::string str(); + + Sign m_sign; +}; + +struct SolBinop +{ + SolBinop(SolExpression const& _left, SolExpression const& _right); + + std::string str(); + + Sign m_sign; +}; + +class ProtoConverter +{ +public: + ProtoConverter() + { + + } + ProtoConverter(ProtoConverter const&) = delete; + ProtoConverter(ProtoConverter&&) = delete; + std::string programToString(Program const& _input); +private: + std::string visit(Type const& _type); + std::string visit(UnaryOpStmt const& _uop); + std::string visit(BinaryOp const& _bop); + std::string visit(VarDecl const& _decl); + std::string visit(Assignment const& _assignment); + std::string visit(Return const& _return); + std::string visit(VarRef const& _varref); + std::string visit(Expression const& _expr); + std::string visit(Statement const& _stmt); + std::string visit(Block const& _block); + std::string visit(Program const& _program); + + std::string newVarName() + { + return "v" + std::to_string(m_varCounter); + } + void incrementVarCounter() + { + m_varCounter++; + } + std::string randomVarName() + { + return "v" + std::to_string((*m_rand)() % m_varCounter); + } + static std::string signString(Type::Sign _sign) + { + return _sign == Type::Sign::Type_Sign_SIGNED ? "uint" : "uint"; + } + static std::string widthString(unsigned _width) + { + return std::to_string((_width % 32 + 1) * 8); + } + bool varAvailable() + { + return m_varCounter > 0; + } + Sign typeSign(Type const& _ts) + { + return _ts.s() == Type::SIGNED ? Sign::Signed : Sign::Unsigned; + } + + unsigned m_varCounter = 0; + std::shared_ptr m_rand; + std::map m_varTypeMap; + std::string m_returnType; +}; +} \ No newline at end of file