fuzz.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  1. // Copyright (c) 2019 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. #include <cassert>
  15. #include <cerrno>
  16. #include <cstring>
  17. #include <fstream>
  18. #include <functional>
  19. #include <sstream>
  20. #include <string>
  21. #include "source/fuzz/fuzzer.h"
  22. #include "source/fuzz/protobufs/spirvfuzz_protobufs.h"
  23. #include "source/fuzz/replayer.h"
  24. #include "source/fuzz/shrinker.h"
  25. #include "source/opt/build_module.h"
  26. #include "source/opt/ir_context.h"
  27. #include "source/opt/log.h"
  28. #include "source/spirv_fuzzer_options.h"
  29. #include "source/util/string_utils.h"
  30. #include "tools/io.h"
  31. #include "tools/util/cli_consumer.h"
  32. namespace {
  33. // Check that the std::system function can actually be used.
  34. bool CheckExecuteCommand() {
  35. int res = std::system(nullptr);
  36. return res != 0;
  37. }
  38. // Execute a command using the shell.
  39. // Returns true if and only if the command's exit status was 0.
  40. bool ExecuteCommand(const std::string& command) {
  41. errno = 0;
  42. int status = std::system(command.c_str());
  43. assert(errno == 0 && "failed to execute command");
  44. // The result returned by 'system' is implementation-defined, but is
  45. // usually the case that the returned value is 0 when the command's exit
  46. // code was 0. We are assuming that here, and that's all we depend on.
  47. return status == 0;
  48. }
  49. // Status and actions to perform after parsing command-line arguments.
  50. enum class FuzzActions {
  51. FUZZ, // Run the fuzzer to apply transformations in a randomized fashion.
  52. REPLAY, // Replay an existing sequence of transformations.
  53. SHRINK, // Shrink an existing sequence of transformations with respect to an
  54. // interestingness function.
  55. STOP // Do nothing.
  56. };
  57. struct FuzzStatus {
  58. FuzzActions action;
  59. int code;
  60. };
  61. void PrintUsage(const char* program) {
  62. // NOTE: Please maintain flags in lexicographical order.
  63. printf(
  64. R"(%s - Fuzzes an equivalent SPIR-V binary based on a given binary.
  65. USAGE: %s [options] <input.spv> -o <output.spv>
  66. The SPIR-V binary is read from <input.spv>, which must have extension .spv. If
  67. <input.facts> is also present, facts about the SPIR-V binary are read from this
  68. file.
  69. The transformed SPIR-V binary is written to <output.spv>. Human-readable and
  70. binary representations of the transformations that were applied are written to
  71. <output.transformations_json> and <output.transformations>, respectively.
  72. NOTE: The fuzzer is a work in progress.
  73. Options (in lexicographical order):
  74. -h, --help
  75. Print this help.
  76. --replay
  77. File from which to read a sequence of transformations to replay
  78. (instead of fuzzing)
  79. --seed
  80. Unsigned 32-bit integer seed to control random number
  81. generation.
  82. --shrink
  83. File from which to read a sequence of transformations to shrink
  84. (instead of fuzzing)
  85. --shrinker-step-limit
  86. Unsigned 32-bit integer specifying maximum number of steps the
  87. shrinker will take before giving up. Ignored unless --shrink
  88. is used.
  89. --interestingness
  90. Path to an interestingness function to guide shrinking: a script
  91. that returns 0 if and only if a given binary is interesting.
  92. Required if --shrink is provided; disallowed otherwise.
  93. --version
  94. Display fuzzer version information.
  95. )",
  96. program, program);
  97. }
  98. // Message consumer for this tool. Used to emit diagnostics during
  99. // initialization and setup. Note that |source| and |position| are irrelevant
  100. // here because we are still not processing a SPIR-V input file.
  101. void FuzzDiagnostic(spv_message_level_t level, const char* /*source*/,
  102. const spv_position_t& /*position*/, const char* message) {
  103. if (level == SPV_MSG_ERROR) {
  104. fprintf(stderr, "error: ");
  105. }
  106. fprintf(stderr, "%s\n", message);
  107. }
  108. bool EndsWithSpv(const std::string& filename) {
  109. std::string dot_spv = ".spv";
  110. return filename.length() >= dot_spv.length() &&
  111. 0 == filename.compare(filename.length() - dot_spv.length(),
  112. filename.length(), dot_spv);
  113. }
  114. FuzzStatus ParseFlags(int argc, const char** argv, std::string* in_binary_file,
  115. std::string* out_binary_file,
  116. std::string* replay_transformations_file,
  117. std::string* interestingness_function_file,
  118. std::string* shrink_transformations_file,
  119. spvtools::FuzzerOptions* fuzzer_options) {
  120. uint32_t positional_arg_index = 0;
  121. for (int argi = 1; argi < argc; ++argi) {
  122. const char* cur_arg = argv[argi];
  123. if ('-' == cur_arg[0]) {
  124. if (0 == strcmp(cur_arg, "--version")) {
  125. spvtools::Logf(FuzzDiagnostic, SPV_MSG_INFO, nullptr, {}, "%s\n",
  126. spvSoftwareVersionDetailsString());
  127. return {FuzzActions::STOP, 0};
  128. } else if (0 == strcmp(cur_arg, "--help") || 0 == strcmp(cur_arg, "-h")) {
  129. PrintUsage(argv[0]);
  130. return {FuzzActions::STOP, 0};
  131. } else if (0 == strcmp(cur_arg, "-o")) {
  132. if (out_binary_file->empty() && argi + 1 < argc) {
  133. *out_binary_file = std::string(argv[++argi]);
  134. } else {
  135. PrintUsage(argv[0]);
  136. return {FuzzActions::STOP, 1};
  137. }
  138. } else if (0 == strncmp(cur_arg, "--replay=", sizeof("--replay=") - 1)) {
  139. const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
  140. *replay_transformations_file = std::string(split_flag.second);
  141. } else if (0 == strncmp(cur_arg, "--interestingness=",
  142. sizeof("--interestingness=") - 1)) {
  143. const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
  144. *interestingness_function_file = std::string(split_flag.second);
  145. } else if (0 == strncmp(cur_arg, "--shrink=", sizeof("--shrink=") - 1)) {
  146. const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
  147. *shrink_transformations_file = std::string(split_flag.second);
  148. } else if (0 == strncmp(cur_arg, "--seed=", sizeof("--seed=") - 1)) {
  149. const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
  150. char* end = nullptr;
  151. errno = 0;
  152. const auto seed =
  153. static_cast<uint32_t>(strtol(split_flag.second.c_str(), &end, 10));
  154. assert(end != split_flag.second.c_str() && errno == 0);
  155. fuzzer_options->set_random_seed(seed);
  156. } else if (0 == strncmp(cur_arg, "--shrinker-step-limit=",
  157. sizeof("--shrinker-step-limit=") - 1)) {
  158. const auto split_flag = spvtools::utils::SplitFlagArgs(cur_arg);
  159. char* end = nullptr;
  160. errno = 0;
  161. const auto step_limit =
  162. static_cast<uint32_t>(strtol(split_flag.second.c_str(), &end, 10));
  163. assert(end != split_flag.second.c_str() && errno == 0);
  164. fuzzer_options->set_shrinker_step_limit(step_limit);
  165. } else if ('\0' == cur_arg[1]) {
  166. // We do not support fuzzing from standard input. We could support
  167. // this if there was a compelling use case.
  168. PrintUsage(argv[0]);
  169. return {FuzzActions::STOP, 0};
  170. }
  171. } else if (positional_arg_index == 0) {
  172. // Binary input file name
  173. assert(in_binary_file->empty());
  174. *in_binary_file = std::string(cur_arg);
  175. positional_arg_index++;
  176. } else {
  177. spvtools::Error(FuzzDiagnostic, nullptr, {},
  178. "Too many positional arguments specified");
  179. return {FuzzActions::STOP, 1};
  180. }
  181. }
  182. if (in_binary_file->empty()) {
  183. spvtools::Error(FuzzDiagnostic, nullptr, {}, "No input file specified");
  184. return {FuzzActions::STOP, 1};
  185. }
  186. if (!EndsWithSpv(*in_binary_file)) {
  187. spvtools::Error(FuzzDiagnostic, nullptr, {},
  188. "Input filename must have extension .spv");
  189. return {FuzzActions::STOP, 1};
  190. }
  191. if (out_binary_file->empty()) {
  192. spvtools::Error(FuzzDiagnostic, nullptr, {}, "-o required");
  193. return {FuzzActions::STOP, 1};
  194. }
  195. if (!EndsWithSpv(*out_binary_file)) {
  196. spvtools::Error(FuzzDiagnostic, nullptr, {},
  197. "Output filename must have extension .spv");
  198. return {FuzzActions::STOP, 1};
  199. }
  200. if (!replay_transformations_file->empty()) {
  201. // A replay transformations file was given, thus the tool is being invoked
  202. // in replay mode.
  203. if (!shrink_transformations_file->empty()) {
  204. spvtools::Error(
  205. FuzzDiagnostic, nullptr, {},
  206. "The --replay and --shrink arguments are mutually exclusive.");
  207. return {FuzzActions::STOP, 1};
  208. }
  209. if (!interestingness_function_file->empty()) {
  210. spvtools::Error(FuzzDiagnostic, nullptr, {},
  211. "The --replay and --interestingness arguments are "
  212. "mutually exclusive.");
  213. return {FuzzActions::STOP, 1};
  214. }
  215. return {FuzzActions::REPLAY, 0};
  216. }
  217. if (!shrink_transformations_file->empty() ^
  218. !interestingness_function_file->empty()) {
  219. spvtools::Error(FuzzDiagnostic, nullptr, {},
  220. "Both or neither of the --shrink and --interestingness "
  221. "arguments must be provided.");
  222. return {FuzzActions::STOP, 1};
  223. }
  224. if (!shrink_transformations_file->empty()) {
  225. // The tool is being invoked in shrink mode.
  226. assert(!interestingness_function_file->empty() &&
  227. "An error should have been raised if --shrink but not --interesting "
  228. "was provided.");
  229. return {FuzzActions::SHRINK, 0};
  230. }
  231. return {FuzzActions::FUZZ, 0};
  232. }
  233. bool ParseTransformations(
  234. const std::string& transformations_file,
  235. spvtools::fuzz::protobufs::TransformationSequence* transformations) {
  236. std::ifstream transformations_stream;
  237. transformations_stream.open(transformations_file,
  238. std::ios::in | std::ios::binary);
  239. auto parse_success =
  240. transformations->ParseFromIstream(&transformations_stream);
  241. transformations_stream.close();
  242. if (!parse_success) {
  243. spvtools::Error(FuzzDiagnostic, nullptr, {},
  244. ("Error reading transformations from file '" +
  245. transformations_file + "'")
  246. .c_str());
  247. return false;
  248. }
  249. return true;
  250. }
  251. bool Replay(const spv_target_env& target_env,
  252. const std::vector<uint32_t>& binary_in,
  253. const spvtools::fuzz::protobufs::FactSequence& initial_facts,
  254. const std::string& replay_transformations_file,
  255. std::vector<uint32_t>* binary_out,
  256. spvtools::fuzz::protobufs::TransformationSequence*
  257. transformations_applied) {
  258. spvtools::fuzz::protobufs::TransformationSequence transformation_sequence;
  259. if (!ParseTransformations(replay_transformations_file,
  260. &transformation_sequence)) {
  261. return false;
  262. }
  263. spvtools::fuzz::Replayer replayer(target_env);
  264. replayer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
  265. auto replay_result_status =
  266. replayer.Run(binary_in, initial_facts, transformation_sequence,
  267. binary_out, transformations_applied);
  268. return !(replay_result_status !=
  269. spvtools::fuzz::Replayer::ReplayerResultStatus::kComplete);
  270. }
  271. bool Shrink(const spv_target_env& target_env,
  272. spv_const_fuzzer_options fuzzer_options,
  273. const std::vector<uint32_t>& binary_in,
  274. const spvtools::fuzz::protobufs::FactSequence& initial_facts,
  275. const std::string& shrink_transformations_file,
  276. const std::string& interestingness_function_file,
  277. std::vector<uint32_t>* binary_out,
  278. spvtools::fuzz::protobufs::TransformationSequence*
  279. transformations_applied) {
  280. spvtools::fuzz::protobufs::TransformationSequence transformation_sequence;
  281. if (!ParseTransformations(shrink_transformations_file,
  282. &transformation_sequence)) {
  283. return false;
  284. }
  285. spvtools::fuzz::Shrinker shrinker(target_env,
  286. fuzzer_options->shrinker_step_limit);
  287. shrinker.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
  288. spvtools::fuzz::Shrinker::InterestingnessFunction interestingness_function =
  289. [interestingness_function_file](std::vector<uint32_t> binary,
  290. uint32_t reductions_applied) -> bool {
  291. std::stringstream ss;
  292. ss << "temp_" << std::setw(4) << std::setfill('0') << reductions_applied
  293. << ".spv";
  294. const auto spv_file = ss.str();
  295. const std::string command =
  296. std::string(interestingness_function_file) + " " + spv_file;
  297. auto write_file_succeeded =
  298. WriteFile(spv_file.c_str(), "wb", &binary[0], binary.size());
  299. (void)(write_file_succeeded);
  300. assert(write_file_succeeded);
  301. return ExecuteCommand(command);
  302. };
  303. auto shrink_result_status = shrinker.Run(
  304. binary_in, initial_facts, transformation_sequence,
  305. interestingness_function, binary_out, transformations_applied);
  306. return spvtools::fuzz::Shrinker::ShrinkerResultStatus::kComplete ==
  307. shrink_result_status ||
  308. spvtools::fuzz::Shrinker::ShrinkerResultStatus::kStepLimitReached ==
  309. shrink_result_status;
  310. }
  311. bool Fuzz(const spv_target_env& target_env,
  312. const spvtools::FuzzerOptions& fuzzer_options,
  313. const std::vector<uint32_t>& binary_in,
  314. const spvtools::fuzz::protobufs::FactSequence& initial_facts,
  315. std::vector<uint32_t>* binary_out,
  316. spvtools::fuzz::protobufs::TransformationSequence*
  317. transformations_applied) {
  318. spvtools::fuzz::Fuzzer fuzzer(target_env);
  319. fuzzer.SetMessageConsumer(spvtools::utils::CLIMessageConsumer);
  320. auto fuzz_result_status = fuzzer.Run(binary_in, initial_facts, fuzzer_options,
  321. binary_out, transformations_applied);
  322. if (fuzz_result_status !=
  323. spvtools::fuzz::Fuzzer::FuzzerResultStatus::kComplete) {
  324. spvtools::Error(FuzzDiagnostic, nullptr, {}, "Error running fuzzer");
  325. return false;
  326. }
  327. return true;
  328. }
  329. } // namespace
  330. const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_3;
  331. int main(int argc, const char** argv) {
  332. std::string in_binary_file;
  333. std::string out_binary_file;
  334. std::string replay_transformations_file;
  335. std::string interestingness_function_file;
  336. std::string shrink_transformations_file;
  337. spvtools::FuzzerOptions fuzzer_options;
  338. FuzzStatus status =
  339. ParseFlags(argc, argv, &in_binary_file, &out_binary_file,
  340. &replay_transformations_file, &interestingness_function_file,
  341. &shrink_transformations_file, &fuzzer_options);
  342. if (status.action == FuzzActions::STOP) {
  343. return status.code;
  344. }
  345. std::vector<uint32_t> binary_in;
  346. if (!ReadFile<uint32_t>(in_binary_file.c_str(), "rb", &binary_in)) {
  347. return 1;
  348. }
  349. spvtools::fuzz::protobufs::FactSequence initial_facts;
  350. const std::string dot_spv(".spv");
  351. std::string in_facts_file =
  352. in_binary_file.substr(0, in_binary_file.length() - dot_spv.length()) +
  353. ".facts";
  354. std::ifstream facts_input(in_facts_file);
  355. if (facts_input) {
  356. std::string facts_json_string((std::istreambuf_iterator<char>(facts_input)),
  357. std::istreambuf_iterator<char>());
  358. facts_input.close();
  359. if (google::protobuf::util::Status::OK !=
  360. google::protobuf::util::JsonStringToMessage(facts_json_string,
  361. &initial_facts)) {
  362. spvtools::Error(FuzzDiagnostic, nullptr, {}, "Error reading facts data");
  363. return 1;
  364. }
  365. }
  366. std::vector<uint32_t> binary_out;
  367. spvtools::fuzz::protobufs::TransformationSequence transformations_applied;
  368. spv_target_env target_env = kDefaultEnvironment;
  369. switch (status.action) {
  370. case FuzzActions::FUZZ:
  371. if (!Fuzz(target_env, fuzzer_options, binary_in, initial_facts,
  372. &binary_out, &transformations_applied)) {
  373. return 1;
  374. }
  375. break;
  376. case FuzzActions::REPLAY:
  377. if (!Replay(target_env, binary_in, initial_facts,
  378. replay_transformations_file, &binary_out,
  379. &transformations_applied)) {
  380. return 1;
  381. }
  382. break;
  383. case FuzzActions::SHRINK: {
  384. if (!CheckExecuteCommand()) {
  385. std::cerr << "could not find shell interpreter for executing a command"
  386. << std::endl;
  387. return 1;
  388. }
  389. if (!Shrink(target_env, fuzzer_options, binary_in, initial_facts,
  390. shrink_transformations_file, interestingness_function_file,
  391. &binary_out, &transformations_applied)) {
  392. return 1;
  393. }
  394. } break;
  395. default:
  396. assert(false && "Unknown fuzzer action.");
  397. break;
  398. }
  399. if (!WriteFile<uint32_t>(out_binary_file.c_str(), "wb", binary_out.data(),
  400. binary_out.size())) {
  401. spvtools::Error(FuzzDiagnostic, nullptr, {}, "Error writing out binary");
  402. return 1;
  403. }
  404. std::string output_file_prefix =
  405. out_binary_file.substr(0, out_binary_file.length() - dot_spv.length());
  406. std::ofstream transformations_file;
  407. transformations_file.open(output_file_prefix + ".transformations",
  408. std::ios::out | std::ios::binary);
  409. bool success =
  410. transformations_applied.SerializeToOstream(&transformations_file);
  411. transformations_file.close();
  412. if (!success) {
  413. spvtools::Error(FuzzDiagnostic, nullptr, {},
  414. "Error writing out transformations binary");
  415. return 1;
  416. }
  417. std::string json_string;
  418. auto json_options = google::protobuf::util::JsonOptions();
  419. json_options.add_whitespace = true;
  420. auto json_generation_status = google::protobuf::util::MessageToJsonString(
  421. transformations_applied, &json_string, json_options);
  422. if (json_generation_status != google::protobuf::util::Status::OK) {
  423. spvtools::Error(FuzzDiagnostic, nullptr, {},
  424. "Error writing out transformations in JSON format");
  425. return 1;
  426. }
  427. std::ofstream transformations_json_file(output_file_prefix +
  428. ".transformations_json");
  429. transformations_json_file << json_string;
  430. transformations_json_file.close();
  431. return 0;
  432. }