diff --git a/Changelog.md b/Changelog.md index ceb1402a8..42e417d4f 100644 --- a/Changelog.md +++ b/Changelog.md @@ -11,6 +11,7 @@ Bugfixes: Build System: + * Add support for continuous fuzzing via Google oss-fuzz ### 0.5.3 (2019-01-22) diff --git a/cmake/EthOptions.cmake b/cmake/EthOptions.cmake index a79e5135a..68d6cb045 100644 --- a/cmake/EthOptions.cmake +++ b/cmake/EthOptions.cmake @@ -3,6 +3,7 @@ macro(configure_project) # features eth_default_option(COVERAGE OFF) + eth_default_option(OSSFUZZ OFF) # components eth_default_option(TESTS ON) diff --git a/libdevcore/CommonData.h b/libdevcore/CommonData.h index 98331936c..adf9d70ce 100644 --- a/libdevcore/CommonData.h +++ b/libdevcore/CommonData.h @@ -334,4 +334,11 @@ bool containerEqual(Container const& _lhs, Container const& _rhs, Compare&& _com return std::equal(std::begin(_lhs), std::end(_lhs), std::begin(_rhs), std::end(_rhs), std::forward(_compare)); } +inline std::string findAnyOf(std::string const& _haystack, std::vector const& _needles) +{ + for (std::string const& needle: _needles) + if (_haystack.find(needle) != std::string::npos) + return needle; + return ""; +} } diff --git a/test/tools/CMakeLists.txt b/test/tools/CMakeLists.txt index da8e0b39c..7e070ebb5 100644 --- a/test/tools/CMakeLists.txt +++ b/test/tools/CMakeLists.txt @@ -1,4 +1,8 @@ -add_executable(solfuzzer fuzzer.cpp) +if (OSSFUZZ) + add_subdirectory(ossfuzz) +endif() + +add_executable(solfuzzer afl_fuzzer.cpp fuzzer_common.cpp) target_link_libraries(solfuzzer PRIVATE libsolc evmasm ${Boost_PROGRAM_OPTIONS_LIBRARIES} ${Boost_SYSTEM_LIBRARIES}) add_executable(yulopti yulopti.cpp) diff --git a/test/tools/afl_fuzzer.cpp b/test/tools/afl_fuzzer.cpp new file mode 100644 index 000000000..9f4ac86cd --- /dev/null +++ b/test/tools/afl_fuzzer.cpp @@ -0,0 +1,101 @@ +/* + 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 . +*/ +/** + * Executable for use with AFL . + */ + +#include +#include + +using namespace std; +using namespace dev; +using namespace dev::eth; + +namespace po = boost::program_options; + +int main(int argc, char** argv) +{ + po::options_description options( + R"(solfuzzer, fuzz-testing binary for use with AFL. +Usage: solfuzzer [Options] < input +Reads a single source from stdin, compiles it and signals a failure for internal errors. + +Allowed options)", + po::options_description::m_default_line_length, + po::options_description::m_default_line_length - 23); + options.add_options() + ("help", "Show this help screen.") + ("quiet", "Only output errors.") + ( + "standard-json", + "Test via the standard-json interface, i.e. " + "input is expected to be JSON-encoded instead of " + "plain source file." + ) + ( + "const-opt", + "Run the constant optimizer instead of compiling. " + "Expects a binary string of up to 32 bytes on stdin." + ) + ( + "input-file", + po::value(), + "input file" + ) + ( + "without-optimizer", + "Run without optimizations. Cannot be used together with standard-json." + ); + + // All positional options should be interpreted as input files + po::positional_options_description filesPositions; + filesPositions.add("input-file", 1); + bool quiet = false; + + po::variables_map arguments; + try + { + po::command_line_parser cmdLineParser(argc, argv); + cmdLineParser.options(options).positional(filesPositions); + po::store(cmdLineParser.run(), arguments); + } + catch (po::error const& _exception) + { + cerr << _exception.what() << endl; + return 1; + } + + string input; + if (arguments.count("input-file")) + input = readFileAsString(arguments["input-file"].as()); + else + input = readStandardInput(); + + if (arguments.count("quiet")) + quiet = true; + + if (arguments.count("help")) + cout << options; + else if (arguments.count("const-opt")) + FuzzerUtil::testConstantOptimizer(input, quiet); + else if (arguments.count("standard-json")) + FuzzerUtil::testStandardCompiler(input, quiet); + else + FuzzerUtil::testCompiler(input, !arguments.count("without-optimizer"), quiet); + + return 0; +} \ No newline at end of file diff --git a/test/tools/fuzzer.cpp b/test/tools/fuzzer.cpp deleted file mode 100644 index 8633454c5..000000000 --- a/test/tools/fuzzer.cpp +++ /dev/null @@ -1,217 +0,0 @@ -/* - 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 . -*/ -/** - * Executable for use with AFL . - */ - -#include -#include -#include -#include - -#include - -#include - -#include -#include -#include - -using namespace std; -using namespace dev; -using namespace dev::eth; -namespace po = boost::program_options; - -namespace -{ - -bool quiet = false; - -string contains(string const& _haystack, vector const& _needles) -{ - for (string const& needle: _needles) - if (_haystack.find(needle) != string::npos) - return needle; - return ""; -} - -void testConstantOptimizer(string const& input) -{ - if (!quiet) - cout << "Testing constant optimizer" << endl; - vector numbers; - stringstream sin(input); - - while (!sin.eof()) - { - h256 data; - sin.read(reinterpret_cast(data.data()), 32); - numbers.push_back(u256(data)); - } - if (!quiet) - cout << "Got " << numbers.size() << " inputs:" << endl; - - Assembly assembly; - for (u256 const& n: numbers) - { - if (!quiet) - cout << n << endl; - assembly.append(n); - } - for (bool isCreation: {false, true}) - { - for (unsigned runs: {1, 2, 3, 20, 40, 100, 200, 400, 1000}) - { - ConstantOptimisationMethod::optimiseConstants( - isCreation, - runs, - EVMVersion{}, - assembly, - const_cast(assembly.items()) - ); - } - } -} - -void runCompiler(string input) -{ - string outputString(solidity_compile(input.c_str(), nullptr)); - Json::Value output; - if (!jsonParseStrict(outputString, output)) - { - cout << "Compiler produced invalid JSON output." << endl; - abort(); - } - if (output.isMember("errors")) - for (auto const& error: output["errors"]) - { - string invalid = contains(error["type"].asString(), vector{ - "Exception", - "InternalCompilerError" - }); - if (!invalid.empty()) - { - cout << "Invalid error: \"" << error["type"].asString() << "\"" << endl; - abort(); - } - } -} - -void testStandardCompiler(string const& input) -{ - if (!quiet) - cout << "Testing compiler via JSON interface." << endl; - - runCompiler(input); -} - -void testCompiler(string const& input, bool optimize) -{ - if (!quiet) - cout << "Testing compiler " << (optimize ? "with" : "without") << " optimizer." << endl; - - Json::Value config = Json::objectValue; - config["language"] = "Solidity"; - config["sources"] = Json::objectValue; - config["sources"][""] = Json::objectValue; - config["sources"][""]["content"] = input; - config["settings"] = Json::objectValue; - config["settings"]["optimizer"] = Json::objectValue; - config["settings"]["optimizer"]["enabled"] = optimize; - config["settings"]["optimizer"]["runs"] = 200; - - // Enable all SourceUnit-level outputs. - config["settings"]["outputSelection"]["*"][""][0] = "*"; - // Enable all Contract-level outputs. - config["settings"]["outputSelection"]["*"]["*"][0] = "*"; - - runCompiler(jsonCompactPrint(config)); -} - -} - -int main(int argc, char** argv) -{ - po::options_description options( - R"(solfuzzer, fuzz-testing binary for use with AFL. -Usage: solfuzzer [Options] < input -Reads a single source from stdin, compiles it and signals a failure for internal errors. - -Allowed options)", - po::options_description::m_default_line_length, - po::options_description::m_default_line_length - 23); - options.add_options() - ("help", "Show this help screen.") - ("quiet", "Only output errors.") - ( - "standard-json", - "Test via the standard-json interface, i.e. " - "input is expected to be JSON-encoded instead of " - "plain source file." - ) - ( - "const-opt", - "Run the constant optimizer instead of compiling. " - "Expects a binary string of up to 32 bytes on stdin." - ) - ( - "input-file", - po::value(), - "input file" - ) - ( - "without-optimizer", - "Run without optimizations. Cannot be used together with standard-json." - ); - - // All positional options should be interpreted as input files - po::positional_options_description filesPositions; - filesPositions.add("input-file", 1); - - po::variables_map arguments; - try - { - po::command_line_parser cmdLineParser(argc, argv); - cmdLineParser.options(options).positional(filesPositions); - po::store(cmdLineParser.run(), arguments); - } - catch (po::error const& _exception) - { - cerr << _exception.what() << endl; - return 1; - } - - string input; - if (arguments.count("input-file")) - input = readFileAsString(arguments["input-file"].as()); - else - input = readStandardInput(); - - if (arguments.count("quiet")) - quiet = true; - - if (arguments.count("help")) - cout << options; - else if (arguments.count("const-opt")) - testConstantOptimizer(input); - else if (arguments.count("standard-json")) - testStandardCompiler(input); - else - testCompiler(input, !arguments.count("without-optimizer")); - - return 0; -} diff --git a/test/tools/fuzzer_common.cpp b/test/tools/fuzzer_common.cpp new file mode 100644 index 000000000..a700c557e --- /dev/null +++ b/test/tools/fuzzer_common.cpp @@ -0,0 +1,114 @@ +/* + 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 + +using namespace std; +using namespace dev; +using namespace dev::eth; + +void FuzzerUtil::runCompiler(string _input) +{ + string outputString(solidity_compile(_input.c_str(), nullptr)); + Json::Value output; + if (!jsonParseStrict(outputString, output)) + { + cout << "Compiler produced invalid JSON output." << endl; + abort(); + } + if (output.isMember("errors")) + for (auto const& error: output["errors"]) + { + string invalid = findAnyOf(error["type"].asString(), vector{ + "Exception", + "InternalCompilerError" + }); + if (!invalid.empty()) + { + cout << "Invalid error: \"" << error["type"].asString() << "\"" << endl; + abort(); + } + } +} + +void FuzzerUtil::testCompiler(string const& _input, bool _optimize, bool _quiet) +{ + if (!_quiet) + cout << "Testing compiler " << (_optimize ? "with" : "without") << " optimizer." << endl; + + Json::Value config = Json::objectValue; + config["language"] = "Solidity"; + config["sources"] = Json::objectValue; + config["sources"][""] = Json::objectValue; + config["sources"][""]["content"] = _input; + config["settings"] = Json::objectValue; + config["settings"]["optimizer"] = Json::objectValue; + config["settings"]["optimizer"]["enabled"] = _optimize; + config["settings"]["optimizer"]["runs"] = 200; + + // Enable all SourceUnit-level outputs. + config["settings"]["outputSelection"]["*"][""][0] = "*"; + // Enable all Contract-level outputs. + config["settings"]["outputSelection"]["*"]["*"][0] = "*"; + + runCompiler(jsonCompactPrint(config)); +} + +void FuzzerUtil::testConstantOptimizer(string const& _input, bool _quiet) +{ + if (!_quiet) + cout << "Testing constant optimizer" << endl; + vector numbers; + stringstream sin(_input); + + while (!sin.eof()) + { + h256 data; + sin.read(reinterpret_cast(data.data()), 32); + numbers.push_back(u256(data)); + } + if (!_quiet) + cout << "Got " << numbers.size() << " inputs:" << endl; + + Assembly assembly; + for (u256 const& n: numbers) + { + if (!_quiet) + cout << n << endl; + assembly.append(n); + } + for (bool isCreation: {false, true}) + for (unsigned runs: {1, 2, 3, 20, 40, 100, 200, 400, 1000}) + { + ConstantOptimisationMethod::optimiseConstants( + isCreation, + runs, + EVMVersion{}, + assembly, + const_cast(assembly.items()) + ); + } +} + +void FuzzerUtil::testStandardCompiler(string const& _input, bool _quiet) +{ + if (!_quiet) + cout << "Testing compiler via JSON interface." << endl; + + runCompiler(_input); +} diff --git a/test/tools/fuzzer_common.h b/test/tools/fuzzer_common.h new file mode 100644 index 000000000..109d5d992 --- /dev/null +++ b/test/tools/fuzzer_common.h @@ -0,0 +1,35 @@ +/* + 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 + +struct FuzzerUtil +{ + static void runCompiler(std::string _input); + static void testCompiler(std::string const& _input, bool _optimize, bool quiet); + static void testConstantOptimizer(std::string const& _input, bool _quiet); + static void testStandardCompiler(std::string const& _input, bool _quiet); +}; \ No newline at end of file diff --git a/test/tools/ossfuzz/CMakeLists.txt b/test/tools/ossfuzz/CMakeLists.txt new file mode 100644 index 000000000..6a32c0eb5 --- /dev/null +++ b/test/tools/ossfuzz/CMakeLists.txt @@ -0,0 +1,12 @@ +add_custom_target(ossfuzz) +add_dependencies(ossfuzz solc_opt_ossfuzz solc_noopt_ossfuzz const_opt_ossfuzz) + +#[[FuzzingEngine.a is provided by oss-fuzz's Dockerized build environment]] +add_executable(solc_opt_ossfuzz solc_opt_ossfuzz.cpp ../fuzzer_common.cpp) +target_link_libraries(solc_opt_ossfuzz PRIVATE libsolc evmasm FuzzingEngine.a) + +add_executable(solc_noopt_ossfuzz solc_noopt_ossfuzz.cpp ../fuzzer_common.cpp) +target_link_libraries(solc_noopt_ossfuzz PRIVATE libsolc evmasm FuzzingEngine.a) + +add_executable(const_opt_ossfuzz const_opt_ossfuzz.cpp ../fuzzer_common.cpp) +target_link_libraries(const_opt_ossfuzz PRIVATE libsolc evmasm FuzzingEngine.a) diff --git a/test/tools/ossfuzz/README.md b/test/tools/ossfuzz/README.md new file mode 100644 index 000000000..eb75f822a --- /dev/null +++ b/test/tools/ossfuzz/README.md @@ -0,0 +1,20 @@ +## Intro + +[oss-fuzz][1] is Google's fuzzing infrastructure that performs continuous fuzzing. What this means is that, each and every upstream commit is automatically fetched by the infrastructure and fuzzed. + +## What does this directory contain? + +To help oss-fuzz do this, we (as project maintainers) need to provide the following: + +- test harnesses: C/C++ tests that define the `LLVMFuzzerTestOneInput` API. This determines what is to be fuzz tested. +- build infrastructure: (c)make targets per fuzzing binary. Fuzzing requires coverage and memory instrumentation of the code to be fuzzed. + +## What is libFuzzingEngine.a? + +`libFuzzingEngine.a` is an oss-fuzz-related dependency. It is present in the Dockerized environment in which Solidity's oss-fuzz code will be built. + +## Is this directory relevant for routine Solidity CI builds? + +No. This is the reason why the `add_subdirectory(ossfuzz)` cmake directive is nested under the `if (OSSFUZZ)` predicate. `OSSFUZZ` is a solidity-wide cmake option that is invoked by the ossfuzz solidity-builder-bot in order to compile solidity fuzzer binaries. + +[1]: https://github.com/google/oss-fuzz diff --git a/test/tools/ossfuzz/const_opt_ossfuzz.cpp b/test/tools/ossfuzz/const_opt_ossfuzz.cpp new file mode 100644 index 000000000..b394d7daf --- /dev/null +++ b/test/tools/ossfuzz/const_opt_ossfuzz.cpp @@ -0,0 +1,27 @@ +/* + 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 + +using namespace std; + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* _data, size_t _size) +{ + string input(reinterpret_cast(_data), _size); + FuzzerUtil::testConstantOptimizer(input, true); + return 0; +} \ No newline at end of file diff --git a/test/tools/ossfuzz/solc_noopt_ossfuzz.cpp b/test/tools/ossfuzz/solc_noopt_ossfuzz.cpp new file mode 100644 index 000000000..7e28c3aca --- /dev/null +++ b/test/tools/ossfuzz/solc_noopt_ossfuzz.cpp @@ -0,0 +1,27 @@ +/* + 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 + +using namespace std; + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* _data, size_t _size) +{ + string input(reinterpret_cast(_data), _size); + FuzzerUtil::testCompiler(input, /*optimize=*/false, /*quiet=*/true); + return 0; +} diff --git a/test/tools/ossfuzz/solc_opt_ossfuzz.cpp b/test/tools/ossfuzz/solc_opt_ossfuzz.cpp new file mode 100644 index 000000000..3ad8e5f74 --- /dev/null +++ b/test/tools/ossfuzz/solc_opt_ossfuzz.cpp @@ -0,0 +1,27 @@ +/* + 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 + +using namespace std; + +extern "C" int LLVMFuzzerTestOneInput(uint8_t const* _data, size_t _size) +{ + string input(reinterpret_cast(_data), _size); + FuzzerUtil::testCompiler(input, /*optimize=*/true, /*quiet=*/true); + return 0; +}