From 00946f3ea09321c2db7799897b183ba48a7cc597 Mon Sep 17 00:00:00 2001 From: Bhargava Shastry Date: Mon, 20 Apr 2020 14:06:16 +0200 Subject: [PATCH] ossfuzz: Add sol proto fuzzer skeleton Co-Authored-By: Leonardo --- test/tools/ossfuzz/protoToSol.cpp | 227 ++++++++++++++++++++++++++++++ test/tools/ossfuzz/protoToSol.h | 185 ++++++++++++++++++++++++ test/tools/ossfuzz/solProto.proto | 111 +++++++++++++++ 3 files changed, 523 insertions(+) create mode 100644 test/tools/ossfuzz/protoToSol.cpp create mode 100644 test/tools/ossfuzz/protoToSol.h create mode 100644 test/tools/ossfuzz/solProto.proto diff --git a/test/tools/ossfuzz/protoToSol.cpp b/test/tools/ossfuzz/protoToSol.cpp new file mode 100644 index 000000000..9551ee3ec --- /dev/null +++ b/test/tools/ossfuzz/protoToSol.cpp @@ -0,0 +1,227 @@ +/* + 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 + +using namespace solidity::test::solprotofuzzer; +using namespace solidity::util; +using namespace std; + +string ProtoConverter::protoToSolidity(Program const& _p) +{ + // Create random number generator with fuzzer supplied + // seed. + m_randomGen = make_shared(_p.seed()); + return visit(_p); +} + +pair ProtoConverter::generateTestCase(TestContract const& _testContract) +{ + ostringstream testCode; + string usingLibDecl; + switch (_testContract.type()) + { + case TestContract::LIBRARY: + { + m_libraryTest = true; + auto testTuple = pseudoRandomLibraryTest(); + m_libraryName = get<0>(testTuple); + usingLibDecl = Whiskers(R"( + using for uint;)") + ("libraryName", get<0>(testTuple)) + .render(); + testCode << Whiskers(R"( + uint x; + if (x.() != ) + return 1;)") + ("testFunction", get<1>(testTuple)) + ("expectedOutput", get<2>(testTuple)) + .render(); + break; + } + case TestContract::CONTRACT: + { + unsigned errorCode = 1; + unsigned contractVarIndex = 0; + for (auto const& testTuple: m_contractTests) + { + // Do this to avoid stack too deep errors + // We require uint as a return var, so we + // cannot have more than 16 variables without + // running into stack too deep errors + if (contractVarIndex >= s_maxVars) + break; + string contractName = testTuple.first; + string contractVarName = "tc" + to_string(contractVarIndex); + testCode << Whiskers(R"( + = new ();)") + ("contractName", contractName) + ("contractVarName", contractVarName) + .render(); + for (auto const& t: testTuple.second) + { + testCode << Whiskers(R"( + if (.() != ) + return ;)") + ("contractVarName", contractVarName) + ("testFunction", t.first) + ("expectedOutput", t.second) + ("errorCode", to_string(errorCode)) + .render(); + errorCode++; + } + contractVarIndex++; + } + break; + } + } + // Expected return value when all tests pass + testCode << Whiskers(R"( + return 0;)") + .render(); + return pair(usingLibDecl, testCode.str()); +} + +string ProtoConverter::visit(TestContract const& _testContract) +{ + string testCode; + string usingLibDecl; + m_libraryTest = false; + + // Simply return valid uint (zero) if there are + // no tests. + if (emptyLibraryTests() || emptyContractTests()) + testCode = Whiskers(R"( + return 0;)") + .render(); + else + tie(usingLibDecl, testCode) = generateTestCase(_testContract); + + return Whiskers(R"( +contract C { + function test() public returns (uint) + { + } +} +)") + ("isLibrary", m_libraryTest) + ("usingDecl", usingLibDecl) + ("testCode", testCode) + .render(); +} + +bool ProtoConverter::libraryTest() const +{ + return m_libraryTest; +} + +string ProtoConverter::libraryName() const +{ + return m_libraryName; +} + +string ProtoConverter::visit(Program const& _p) +{ + ostringstream program; + ostringstream contracts; + + for (auto &contract: _p.contracts()) + contracts << visit(contract); + + program << Whiskers(R"( +pragma solidity >=0.0; + + + + +)") + ("contracts", contracts.str()) + ("testContract", visit(_p.test())) + .render(); + return program.str(); +} + +string ProtoConverter::visit(ContractType const& _contractType) +{ + switch (_contractType.contract_type_oneof_case()) + { + case ContractType::kC: + return visit(_contractType.c()); + case ContractType::kL: + return visit(_contractType.l()); + case ContractType::kI: + return visit(_contractType.i()); + case ContractType::CONTRACT_TYPE_ONEOF_NOT_SET: + return ""; + } +} + +string ProtoConverter::visit(Contract const& _contract) +{ + openProgramScope(&_contract); + return ""; +} + +string ProtoConverter::visit(Interface const& _interface) +{ + openProgramScope(&_interface); + return ""; +} + +string ProtoConverter::visit(Library const& _library) +{ + openProgramScope(&_library); + return ""; +} + +tuple ProtoConverter::pseudoRandomLibraryTest() +{ + solAssert(m_libraryTests.size() > 0, "Sol proto fuzzer: No library tests found"); + unsigned index = randomNumber() % m_libraryTests.size(); + return m_libraryTests[index]; +} + +void ProtoConverter::openProgramScope(CIL _program) +{ + string programNamePrefix; + if (holds_alternative(_program)) + programNamePrefix = "C"; + else if (holds_alternative(_program)) + programNamePrefix = "I"; + else + programNamePrefix = "L"; + string programName = programNamePrefix + to_string(m_programNumericSuffix++); + m_programNameMap.emplace(_program, programName); +} + +string ProtoConverter::programName(CIL _program) +{ + solAssert(m_programNameMap.count(_program), "Sol proto fuzzer: Unregistered program"); + return m_programNameMap[_program]; +} + +unsigned ProtoConverter::randomNumber() +{ + solAssert(m_randomGen, "Sol proto fuzzer: Uninitialized random number generator"); + return m_randomGen->operator()(); +} diff --git a/test/tools/ossfuzz/protoToSol.h b/test/tools/ossfuzz/protoToSol.h new file mode 100644 index 000000000..6a534d71c --- /dev/null +++ b/test/tools/ossfuzz/protoToSol.h @@ -0,0 +1,185 @@ +/* + 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 . +*/ +#pragma once + +#include + +#include +#include +#include +#include + +namespace solidity::test::solprotofuzzer +{ +/// Random number generator that is seeded with a fuzzer +/// supplied unsigned integer. +struct SolRandomNumGenerator +{ + using RandomEngine = std::minstd_rand; + + explicit SolRandomNumGenerator(unsigned _seed): m_random(RandomEngine(_seed)) {} + + /// @returns a pseudo random unsigned integer + unsigned operator()() + { + return m_random(); + } + + RandomEngine m_random; +}; + +/* There are two types of tests created by the converter: + * - library test + * - contract test + * + * The template for library test is the following: + * + * // Library generated from fuzzer protobuf specification + * library L0 { + * function f0(uint) public view returns (uint) { + * return 31337; + * } + * function f1(uint) public pure returns (uint) { + * return 455; + * } + * } + * library L1 { + * function f0(uint) external view returns (uint) { + * return 607; + * } + * } + * + * // Test entry point + * contract C { + * // Uses a single pseudo randomly chosen library + * // and calls a pseudo randomly chosen function + * // returning a non-zero error code on failure or + * // a zero uint when test passes. + * using L0 for uint; + * function test() public pure returns (uint) { + * uint x; + * if (x.f1() != 455) + * return 1; + * return 0; + * } + * } + * + * The template for contract test is the following + * // Contracts generated from fuzzer protobuf specification + * contract C0B { + * function f0() public pure virtual returns (uint) + * { + * return 42; + * } + * } + * contract C0 is C0B { + * function f0() public pure override returns (uint) + * { + * return 1337; + * } + * } + * + * // Test entry point + * contract C { + * // Invokes one or more contract functions returning + * // a non-zero error code for failure, a zero uint + * // when all tests pass + * function test() public pure returns (uint) + * { + * C0 tc0 = new C0(); + * if (tc0.f0() != 1337) + * return 1; + * C0B tc1 = new C0B(); + * if (tc1.f0() != 42) + * return 2; + * // Expected return value if all tests pass + * return 0; + * } + * } + */ +class ProtoConverter +{ +public: + ProtoConverter() {} + ProtoConverter(ProtoConverter const&) = delete; + ProtoConverter(ProtoConverter&&) = delete; + std::string protoToSolidity(Program const&); + /// @returns true if test calls a library function, false + /// otherwise + bool libraryTest() const; + /// @returns name of the library under test + std::string libraryName() const; +private: + /// Variant type that points to one of contract, interface, library protobuf messages + using CIL = std::variant; + /// Protobuf message visitors that accept a const reference to a protobuf message + /// type and return its solidity translation. + std::string visit(Program const&); + std::string visit(TestContract const&); + std::string visit(ContractType const&); + std::string visit(Interface const& _interface); + std::string visit(Library const& _library); + std::string visit(Contract const& _contract); + /// @returns a string pair containing a library declaration (relevant for library + /// tests only) and a solidity test case + std::pair generateTestCase(TestContract const& _testContract); + /// @returns name of a program i.e., contract, library or interface + std::string programName(CIL _program); + /// @returns a tuple containing the names of the library and function under + /// test, and its expected output. + std::tuple pseudoRandomLibraryTest(); + /// Performs bookkeeping for a fuzzer-supplied program + void openProgramScope(CIL _program); + /// @returns a deterministic pseudo random unsigned integer + unsigned randomNumber(); + /// @returns true if fuzzer supplied Library protobuf message + /// contains zero functions, false otherwise. + static bool emptyLibrary(Library const& _library) + { + return _library.funcdef_size() == 0; + } + /// @returns true if there are no valid library test cases, false + /// otherwise. + bool emptyLibraryTests() + { + return m_libraryTests.size() == 0; + } + /// @returns true if there are no valid contract test cases, false + /// otherwise. + bool emptyContractTests() + { + return m_contractTests.size() == 0; + } + /// Numeric suffix that is part of program names e.g., "0" in "C0" + unsigned m_programNumericSuffix = 0; + /// Flag that states whether library call is tested (true) or not (false). + bool m_libraryTest = false; + /// A smart pointer to fuzzer driven random number generator + std::shared_ptr m_randomGen; + /// Maps protobuf program to its string name + std::map m_programNameMap; + /// List of tuples containing library name, function and its expected output + std::vector> m_libraryTests; + /// Maps contract name to a map of function names and their expected output + std::map> m_contractTests; + /// Name of the library under test, relevant if m_libraryTest is set + std::string m_libraryName; + /// Maximum number of local variables in test function to avoid stack too deep + /// errors + static unsigned constexpr s_maxVars = 15; +}; +} diff --git a/test/tools/ossfuzz/solProto.proto b/test/tools/ossfuzz/solProto.proto new file mode 100644 index 000000000..f0444e0d7 --- /dev/null +++ b/test/tools/ossfuzz/solProto.proto @@ -0,0 +1,111 @@ +/* + 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 InterfaceFunction { + enum StateMutability { + PURE = 0; + VIEW = 1; + PAYABLE = 2; + NONPAYABLE = 3; + } + required StateMutability mut = 1; +} + +message LibraryFunction { + // Library functions cannot be payable + enum StateMutability { + PURE = 0; + VIEW = 1; + NONPAYABLE = 2; + } + enum Visibility { + PUBLIC = 0; + EXTERNAL = 1; + INTERNAL = 2; + PRIVATE = 3; + } + required Visibility vis = 1; + required StateMutability mut = 2; +} + +message ContractFunction { + enum StateMutability { + PURE = 0; + VIEW = 1; + PAYABLE = 2; + NONPAYABLE = 3; + } + enum Visibility { + PUBLIC = 0; + EXTERNAL = 1; + INTERNAL = 2; + PRIVATE = 3; + } + required Visibility vis = 1; + required StateMutability mut = 2; + required bool virtualfunc = 3; +} + +message Library { + repeated LibraryFunction funcdef = 1; +} + +message Interface { + repeated InterfaceFunction funcdef = 1; + repeated Interface bases = 2; +} + +message Contract { + repeated ContractFunction funcdef = 1; + required bool abstract = 2; + repeated ContractOrInterface bases = 3; +} + +message ContractOrInterface { + oneof contract_or_interface_oneof { + Contract c = 1; + Interface i = 2; + } +} + +message ContractType { + oneof contract_type_oneof { + Contract c = 1; + Library l = 2; + Interface i = 3; + } +} + +message TestContract { + enum Type { + LIBRARY = 0; + CONTRACT = 1; + } + required Type type = 1; +} + +message Program { + repeated ContractType contracts = 1; + required TestContract test = 2; + // Seed is an unsigned integer that initializes + // a pseudo random number generator. + required uint64 seed = 3; +} + +package solidity.test.solprotofuzzer; \ No newline at end of file