| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481 |
- // Copyright (c) 2019 Google LLC
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- #include <cassert>
- #include <cerrno>
- #include <cstring>
- #include <fstream>
- #include <functional>
- #include <sstream>
- #include <string>
- #include "source/fuzz/fuzzer.h"
- #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
- #include "source/fuzz/replayer.h"
- #include "source/fuzz/shrinker.h"
- #include "source/opt/build_module.h"
- #include "source/opt/ir_context.h"
- #include "source/opt/log.h"
- #include "source/spirv_fuzzer_options.h"
- #include "source/util/string_utils.h"
- #include "tools/io.h"
- #include "tools/util/cli_consumer.h"
- namespace {
- // Check that the std::system function can actually be used.
- bool CheckExecuteCommand() {
- int res = std::system(nullptr);
- return res != 0;
- }
- // Execute a command using the shell.
- // Returns true if and only if the command's exit status was 0.
- bool ExecuteCommand(const std::string& command) {
- errno = 0;
- int status = std::system(command.c_str());
- assert(errno == 0 && "failed to execute command");
- // The result returned by 'system' is implementation-defined, but is
- // usually the case that the returned value is 0 when the command's exit
- // code was 0. We are assuming that here, and that's all we depend on.
- return status == 0;
- }
- // Status and actions to perform after parsing command-line arguments.
- enum class FuzzActions {
- FUZZ, // Run the fuzzer to apply transformations in a randomized fashion.
- REPLAY, // Replay an existing sequence of transformations.
- SHRINK, // Shrink an existing sequence of transformations with respect to an
- // interestingness function.
- STOP // Do nothing.
- };
- struct FuzzStatus {
- FuzzActions action;
- int code;
- };
- void PrintUsage(const char* program) {
- // NOTE: Please maintain flags in lexicographical order.
- printf(
- R"(%s - Fuzzes an equivalent SPIR-V binary based on a given binary.
- USAGE: %s [options] <input.spv> -o <output.spv>
- The SPIR-V binary is read from <input.spv>, which must have extension .spv. If
- <input.facts> is also present, facts about the SPIR-V binary are read from this
- file.
- The transformed SPIR-V binary is written to <output.spv>. Human-readable and
- binary representations of the transformations that were applied are written to
- <output.transformations_json> and <output.transformations>, respectively.
- NOTE: The fuzzer is a work in progress.
- Options (in lexicographical order):
- -h, --help
- Print this help.
- --replay
- File from which to read a sequence of transformations to replay
- (instead of fuzzing)
- --seed
- Unsigned 32-bit integer seed to control random number
- generation.
- --shrink
- File from which to read a sequence of transformations to shrink
- (instead of fuzzing)
- --shrinker-step-limit
- Unsigned 32-bit integer specifying maximum number of steps the
- shrinker will take before giving up. Ignored unless --shrink
- is used.
- --interestingness
- Path to an interestingness function to guide shrinking: a script
- that returns 0 if and only if a given binary is interesting.
- Required if --shrink is provided; disallowed otherwise.
- --version
- Display fuzzer version information.
- )",
- program, program);
- }
- // Message consumer for this tool. Used to emit diagnostics during
- // initialization and setup. Note that |source| and |position| are irrelevant
- // here because we are still not processing a SPIR-V input file.
- void FuzzDiagnostic(spv_message_level_t level, const char* /*source*/,
- const spv_position_t& /*position*/, const char* message) {
- if (level == SPV_MSG_ERROR) {
- fprintf(stderr, "error: ");
- }
- fprintf(stderr, "%s\n", message);
- }
- bool EndsWithSpv(const std::string& filename) {
- std::string dot_spv = ".spv";
- return filename.length() >= dot_spv.length() &&
- 0 == filename.compare(filename.length() - dot_spv.length(),
- filename.length(), dot_spv);
- }
- FuzzStatus ParseFlags(int argc, const char** argv, std::string* in_binary_file,
- std::string* out_binary_file,
- std::string* replay_transformations_file,
- std::string* interestingness_function_file,
- std::string* shrink_transformations_file,
- spvtools::FuzzerOptions* fuzzer_options) {
- uint32_t positional_arg_index = 0;
- for (int argi = 1; argi < argc; ++argi) {
- const char* cur_arg = argv[argi];
- if ('-' == cur_arg[0]) {
- if (0 == strcmp(cur_arg, "--version")) {
- spvtools::Logf(FuzzDiagnostic, SPV_MSG_INFO, nullptr, {}, "%s\n",
- spvSoftwareVersionDetailsString());
- return {FuzzActions::STOP, 0};
- } else if (0 == strcmp(cur_arg, "--help") || 0 == strcmp(cur_arg, "-h")) {
- PrintUsage(argv[0]);
- return {FuzzActions::STOP, 0};
- } else if (0 == strcmp(cur_arg, "-o")) {
- if (out_binary_file->empty() && argi + 1 < argc) {
- *out_binary_file = std::string(argv[++argi]);
- } else {
- PrintUsage(argv[0]);
- return {FuzzActions::STOP, 1};
- }
- } else if (0 == strncmp(cur_arg, "--replay=", sizeof("--replay=") - 1)) {
- const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
- *replay_transformations_file = std::string(split_flag.second);
- } else if (0 == strncmp(cur_arg, "--interestingness=",
- sizeof("--interestingness=") - 1)) {
- const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
- *interestingness_function_file = std::string(split_flag.second);
- } else if (0 == strncmp(cur_arg, "--shrink=", sizeof("--shrink=") - 1)) {
- const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
- *shrink_transformations_file = std::string(split_flag.second);
- } else if (0 == strncmp(cur_arg, "--seed=", sizeof("--seed=") - 1)) {
- const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
- char* end = nullptr;
- errno = 0;
- const auto seed =
- static_cast<uint32_t>(strtol(split_flag.second.c_str(), &end, 10));
- assert(end != split_flag.second.c_str() && errno == 0);
- fuzzer_options->set_random_seed(seed);
- } else if (0 == strncmp(cur_arg, "--shrinker-step-limit=",
- sizeof("--shrinker-step-limit=") - 1)) {
- const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
- char* end = nullptr;
- errno = 0;
- const auto step_limit =
- static_cast<uint32_t>(strtol(split_flag.second.c_str(), &end, 10));
- assert(end != split_flag.second.c_str() && errno == 0);
- fuzzer_options->set_shrinker_step_limit(step_limit);
- } else if ('\0' == cur_arg[1]) {
- // We do not support fuzzing from standard input. We could support
- // this if there was a compelling use case.
- PrintUsage(argv[0]);
- return {FuzzActions::STOP, 0};
- }
- } else if (positional_arg_index == 0) {
- // Binary input file name
- assert(in_binary_file->empty());
- *in_binary_file = std::string(cur_arg);
- positional_arg_index++;
- } else {
- spvtools::Error(FuzzDiagnostic, nullptr, {},
- "Too many positional arguments specified");
- return {FuzzActions::STOP, 1};
- }
- }
- if (in_binary_file->empty()) {
- spvtools::Error(FuzzDiagnostic, nullptr, {}, "No input file specified");
- return {FuzzActions::STOP, 1};
- }
- if (!EndsWithSpv(*in_binary_file)) {
- spvtools::Error(FuzzDiagnostic, nullptr, {},
- "Input filename must have extension .spv");
- return {FuzzActions::STOP, 1};
- }
- if (out_binary_file->empty()) {
- spvtools::Error(FuzzDiagnostic, nullptr, {}, "-o required");
- return {FuzzActions::STOP, 1};
- }
- if (!EndsWithSpv(*out_binary_file)) {
- spvtools::Error(FuzzDiagnostic, nullptr, {},
- "Output filename must have extension .spv");
- return {FuzzActions::STOP, 1};
- }
- if (!replay_transformations_file->empty()) {
- // A replay transformations file was given, thus the tool is being invoked
- // in replay mode.
- if (!shrink_transformations_file->empty()) {
- spvtools::Error(
- FuzzDiagnostic, nullptr, {},
- "The --replay and --shrink arguments are mutually exclusive.");
- return {FuzzActions::STOP, 1};
- }
- if (!interestingness_function_file->empty()) {
- spvtools::Error(FuzzDiagnostic, nullptr, {},
- "The --replay and --interestingness arguments are "
- "mutually exclusive.");
- return {FuzzActions::STOP, 1};
- }
- return {FuzzActions::REPLAY, 0};
- }
- if (!shrink_transformations_file->empty() ^
- !interestingness_function_file->empty()) {
- spvtools::Error(FuzzDiagnostic, nullptr, {},
- "Both or neither of the --shrink and --interestingness "
- "arguments must be provided.");
- return {FuzzActions::STOP, 1};
- }
- if (!shrink_transformations_file->empty()) {
- // The tool is being invoked in shrink mode.
- assert(!interestingness_function_file->empty() &&
- "An error should have been raised if --shrink but not --interesting "
- "was provided.");
- return {FuzzActions::SHRINK, 0};
- }
- return {FuzzActions::FUZZ, 0};
- }
- bool ParseTransformations(
- const std::string& transformations_file,
- spvtools::fuzz::protobufs::TransformationSequence* transformations) {
- std::ifstream transformations_stream;
- transformations_stream.open(transformations_file,
- std::ios::in | std::ios::binary);
- auto parse_success =
- transformations->ParseFromIstream(&transformations_stream);
- transformations_stream.close();
- if (!parse_success) {
- spvtools::Error(FuzzDiagnostic, nullptr, {},
- ("Error reading transformations from file '" +
- transformations_file + "'")
- .c_str());
- return false;
- }
- return true;
- }
- bool Replay(const spv_target_env& target_env,
- const std::vector<uint32_t>& binary_in,
- const spvtools::fuzz::protobufs::FactSequence& initial_facts,
- const std::string& replay_transformations_file,
- std::vector<uint32_t>* binary_out,
- spvtools::fuzz::protobufs::TransformationSequence*
- transformations_applied) {
- spvtools::fuzz::protobufs::TransformationSequence transformation_sequence;
- if (!ParseTransformations(replay_transformations_file,
- &transformation_sequence)) {
- return false;
- }
- spvtools::fuzz::Replayer replayer(target_env);
- replayer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
- auto replay_result_status =
- replayer.Run(binary_in, initial_facts, transformation_sequence,
- binary_out, transformations_applied);
- return !(replay_result_status !=
- spvtools::fuzz::Replayer::ReplayerResultStatus::kComplete);
- }
- bool Shrink(const spv_target_env& target_env,
- spv_const_fuzzer_options fuzzer_options,
- const std::vector<uint32_t>& binary_in,
- const spvtools::fuzz::protobufs::FactSequence& initial_facts,
- const std::string& shrink_transformations_file,
- const std::string& interestingness_function_file,
- std::vector<uint32_t>* binary_out,
- spvtools::fuzz::protobufs::TransformationSequence*
- transformations_applied) {
- spvtools::fuzz::protobufs::TransformationSequence transformation_sequence;
- if (!ParseTransformations(shrink_transformations_file,
- &transformation_sequence)) {
- return false;
- }
- spvtools::fuzz::Shrinker shrinker(target_env,
- fuzzer_options->shrinker_step_limit);
- shrinker.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
- spvtools::fuzz::Shrinker::InterestingnessFunction interestingness_function =
- [interestingness_function_file](std::vector<uint32_t> binary,
- uint32_t reductions_applied) -> bool {
- std::stringstream ss;
- ss << "temp_" << std::setw(4) << std::setfill('0') << reductions_applied
- << ".spv";
- const auto spv_file = ss.str();
- const std::string command =
- std::string(interestingness_function_file) + " " + spv_file;
- auto write_file_succeeded =
- WriteFile(spv_file.c_str(), "wb", &binary[0], binary.size());
- (void)(write_file_succeeded);
- assert(write_file_succeeded);
- return ExecuteCommand(command);
- };
- auto shrink_result_status = shrinker.Run(
- binary_in, initial_facts, transformation_sequence,
- interestingness_function, binary_out, transformations_applied);
- return spvtools::fuzz::Shrinker::ShrinkerResultStatus::kComplete ==
- shrink_result_status ||
- spvtools::fuzz::Shrinker::ShrinkerResultStatus::kStepLimitReached ==
- shrink_result_status;
- }
- bool Fuzz(const spv_target_env& target_env,
- const spvtools::FuzzerOptions& fuzzer_options,
- const std::vector<uint32_t>& binary_in,
- const spvtools::fuzz::protobufs::FactSequence& initial_facts,
- std::vector<uint32_t>* binary_out,
- spvtools::fuzz::protobufs::TransformationSequence*
- transformations_applied) {
- spvtools::fuzz::Fuzzer fuzzer(target_env);
- fuzzer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
- auto fuzz_result_status = fuzzer.Run(binary_in, initial_facts, fuzzer_options,
- binary_out, transformations_applied);
- if (fuzz_result_status !=
- spvtools::fuzz::Fuzzer::FuzzerResultStatus::kComplete) {
- spvtools::Error(FuzzDiagnostic, nullptr, {}, "Error running fuzzer");
- return false;
- }
- return true;
- }
- } // namespace
- const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_3;
- int main(int argc, const char** argv) {
- std::string in_binary_file;
- std::string out_binary_file;
- std::string replay_transformations_file;
- std::string interestingness_function_file;
- std::string shrink_transformations_file;
- spvtools::FuzzerOptions fuzzer_options;
- FuzzStatus status =
- ParseFlags(argc, argv, &in_binary_file, &out_binary_file,
- &replay_transformations_file, &interestingness_function_file,
- &shrink_transformations_file, &fuzzer_options);
- if (status.action == FuzzActions::STOP) {
- return status.code;
- }
- std::vector<uint32_t> binary_in;
- if (!ReadFile<uint32_t>(in_binary_file.c_str(), "rb", &binary_in)) {
- return 1;
- }
- spvtools::fuzz::protobufs::FactSequence initial_facts;
- const std::string dot_spv(".spv");
- std::string in_facts_file =
- in_binary_file.substr(0, in_binary_file.length() - dot_spv.length()) +
- ".facts";
- std::ifstream facts_input(in_facts_file);
- if (facts_input) {
- std::string facts_json_string((std::istreambuf_iterator<char>(facts_input)),
- std::istreambuf_iterator<char>());
- facts_input.close();
- if (google::protobuf::util::Status::OK !=
- google::protobuf::util::JsonStringToMessage(facts_json_string,
- &initial_facts)) {
- spvtools::Error(FuzzDiagnostic, nullptr, {}, "Error reading facts data");
- return 1;
- }
- }
- std::vector<uint32_t> binary_out;
- spvtools::fuzz::protobufs::TransformationSequence transformations_applied;
- spv_target_env target_env = kDefaultEnvironment;
- switch (status.action) {
- case FuzzActions::FUZZ:
- if (!Fuzz(target_env, fuzzer_options, binary_in, initial_facts,
- &binary_out, &transformations_applied)) {
- return 1;
- }
- break;
- case FuzzActions::REPLAY:
- if (!Replay(target_env, binary_in, initial_facts,
- replay_transformations_file, &binary_out,
- &transformations_applied)) {
- return 1;
- }
- break;
- case FuzzActions::SHRINK: {
- if (!CheckExecuteCommand()) {
- std::cerr << "could not find shell interpreter for executing a command"
- << std::endl;
- return 1;
- }
- if (!Shrink(target_env, fuzzer_options, binary_in, initial_facts,
- shrink_transformations_file, interestingness_function_file,
- &binary_out, &transformations_applied)) {
- return 1;
- }
- } break;
- default:
- assert(false && "Unknown fuzzer action.");
- break;
- }
- if (!WriteFile<uint32_t>(out_binary_file.c_str(), "wb", binary_out.data(),
- binary_out.size())) {
- spvtools::Error(FuzzDiagnostic, nullptr, {}, "Error writing out binary");
- return 1;
- }
- std::string output_file_prefix =
- out_binary_file.substr(0, out_binary_file.length() - dot_spv.length());
- std::ofstream transformations_file;
- transformations_file.open(output_file_prefix + ".transformations",
- std::ios::out | std::ios::binary);
- bool success =
- transformations_applied.SerializeToOstream(&transformations_file);
- transformations_file.close();
- if (!success) {
- spvtools::Error(FuzzDiagnostic, nullptr, {},
- "Error writing out transformations binary");
- return 1;
- }
- std::string json_string;
- auto json_options = google::protobuf::util::JsonOptions();
- json_options.add_whitespace = true;
- auto json_generation_status = google::protobuf::util::MessageToJsonString(
- transformations_applied, &json_string, json_options);
- if (json_generation_status != google::protobuf::util::Status::OK) {
- spvtools::Error(FuzzDiagnostic, nullptr, {},
- "Error writing out transformations in JSON format");
- return 1;
- }
- std::ofstream transformations_json_file(output_file_prefix +
- ".transformations_json");
- transformations_json_file << json_string;
- transformations_json_file.close();
- return 0;
- }
|