| //===- FuzzerFork.cpp - run fuzzing in separate subprocesses --------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| // Spawn and orchestrate separate fuzzing processes. |
| //===----------------------------------------------------------------------===// |
| |
| #include "FuzzerCommand.h" |
| #include "FuzzerFork.h" |
| #include "FuzzerIO.h" |
| #include "FuzzerMerge.h" |
| #include "FuzzerSHA1.h" |
| #include "FuzzerUtil.h" |
| |
| namespace fuzzer { |
| |
| struct FuzzJob { |
| // Inputs. |
| Command Cmd; |
| Vector<std::string> Files; |
| std::string CorpusDir; |
| std::string LogPath; |
| std::string CFPath; |
| int MaxTotalTimeSec; |
| |
| // Fuzzing Outputs. |
| int ExitCode; |
| }; |
| |
| struct GlobalEnv { |
| const Vector<std::string> *Args; |
| std::string MainCorpusDir; |
| Set<uint32_t> Features; |
| Vector<std::string> Files; |
| }; |
| |
| void RunOneFuzzingJob(FuzzJob *Job) { |
| Command &Cmd = Job->Cmd; |
| if (!Job->Files.empty()) { |
| std::string Seeds; |
| for (const auto &File : Job->Files) |
| Seeds += (Seeds.empty() ? "" : ",") + File; |
| Cmd.addFlag("seed_inputs", Seeds); |
| } |
| Cmd.addFlag("max_total_time", std::to_string(Job->MaxTotalTimeSec)); |
| Cmd.setOutputFile(Job->LogPath); |
| Cmd.combineOutAndErr(); |
| Cmd.addArgument(Job->CorpusDir); |
| RmDirRecursive(Job->CorpusDir); |
| MkDir(Job->CorpusDir); |
| Job->ExitCode = ExecuteCommand(Cmd); |
| } |
| |
| void RunOneMergeJob(GlobalEnv *Env, FuzzJob *Job) { |
| Vector<SizedFile> TempFiles; |
| GetSizedFilesFromDir(Job->CorpusDir, &TempFiles); |
| |
| Vector<std::string>FilesToAdd; |
| Set<uint32_t> NewFeatures; |
| CrashResistantMerge(*Env->Args, {}, TempFiles, &FilesToAdd, Env->Features, |
| &NewFeatures, Job->CFPath, false); |
| RemoveFile(Job->CFPath); |
| for (auto &Path : FilesToAdd) { |
| auto U = FileToVector(Path); |
| auto NewPath = DirPlusFile(Env->MainCorpusDir, Hash(U)); |
| WriteToFile(U, NewPath); |
| Env->Files.push_back(NewPath); |
| } |
| RmDirRecursive(Job->CorpusDir); |
| Env->Features.insert(NewFeatures.begin(), NewFeatures.end()); |
| Printf("INFO: temp_files: %zd files_added: %zd newft: %zd ft: %zd\n", |
| TempFiles.size(), FilesToAdd.size(), NewFeatures.size(), |
| Env->Features.size()); |
| } |
| |
| // This is just a skeleton of an experimental -fork=1 feature. |
| void FuzzWithFork(Random &Rand, const FuzzingOptions &Options, |
| const Vector<std::string> &Args, |
| const Vector<std::string> &CorpusDirs) { |
| Printf("INFO: -fork=1: doing fuzzing in a separate process in order to " |
| "be more resistant to crashes, timeouts, and OOMs\n"); |
| |
| GlobalEnv Env; |
| Env.Args = &Args; |
| |
| Vector<SizedFile> SeedFiles; |
| for (auto &Dir : CorpusDirs) |
| GetSizedFilesFromDir(Dir, &SeedFiles); |
| std::sort(SeedFiles.begin(), SeedFiles.end()); |
| auto TempDir = TempPath(".dir"); |
| RmDirRecursive(TempDir); // just in case there is a leftover from an old run. |
| MkDir(TempDir); |
| |
| auto CFPath = DirPlusFile(TempDir, "merge.txt"); |
| auto LogPath = DirPlusFile(TempDir, "sub.log"); |
| |
| if (CorpusDirs.empty()) |
| MkDir(Env.MainCorpusDir = DirPlusFile(TempDir, "C")); |
| else |
| Env.MainCorpusDir = CorpusDirs[0]; |
| |
| auto TempCorpusDir = DirPlusFile(TempDir, "C0"); |
| |
| CrashResistantMerge(*Env.Args, {}, SeedFiles, &Env.Files, {}, &Env.Features, |
| CFPath, false); |
| RemoveFile(CFPath); |
| Printf("INFO: -fork=1: %zd seeds, starting to fuzz; scratch: %s\n", |
| Env.Files.size(), TempDir.c_str()); |
| |
| Command BaseCmd(*Env.Args); |
| BaseCmd.removeFlag("fork"); |
| for (auto &C : CorpusDirs) // Remove all corpora from the args. |
| BaseCmd.removeArgument(C); |
| BaseCmd.addFlag("reload", "0"); // working in an isolated dir, no reload. |
| int ExitCode = 0; |
| |
| |
| for (size_t i = 1; i < 1000000; i++) { |
| // TODO: take new files from disk e.g. those generated by another process. |
| |
| FuzzJob Job; |
| Job.Cmd = BaseCmd; |
| if (size_t CorpusSubsetSize = std::min(Env.Files.size(), (size_t)100)) |
| for (size_t i = 0; i < CorpusSubsetSize; i++) |
| Job.Files.push_back(Env.Files[Rand.SkewTowardsLast(Env.Files.size())]); |
| Job.CorpusDir = TempCorpusDir; |
| Job.LogPath = LogPath; |
| Job.CFPath = CFPath; |
| // Start from very short runs and gradually increase them. |
| Job.MaxTotalTimeSec = std::min(300, (int)i); |
| RunOneFuzzingJob(&Job); |
| |
| if (Options.Verbosity >= 2) |
| Printf("done [%d] %s\n", Job.ExitCode, Job.Cmd.toString().c_str()); |
| if (Job.ExitCode == Options.InterruptExitCode) |
| break; |
| |
| RunOneMergeJob(&Env, &Job); |
| |
| // Continue if our crash is one of the ignorred ones. |
| if (Options.IgnoreTimeouts && Job.ExitCode == Options.TimeoutExitCode) |
| continue; |
| if (Options.IgnoreOOMs && Job.ExitCode == Options.OOMExitCode) |
| continue; |
| // And exit if we don't ignore this crash. |
| if (Job.ExitCode != 0) { |
| Printf("INFO: log from the inner process:\n%s", |
| FileToString(LogPath).c_str()); |
| ExitCode = Job.ExitCode; |
| break; |
| } |
| } |
| |
| RmDirRecursive(TempDir); |
| |
| // Use the exit code from the last child process. |
| Printf("Fork: exiting: %d\n", ExitCode); |
| exit(ExitCode); |
| } |
| |
| } // namespace fuzzer |
| |