//===- 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 Files; std::string CorpusDir; std::string LogPath; std::string CFPath; int MaxTotalTimeSec; // Fuzzing Outputs. int ExitCode; }; struct GlobalEnv { const Vector *Args; std::string MainCorpusDir; Set Features; Vector 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 TempFiles; GetSizedFilesFromDir(Job->CorpusDir, &TempFiles); VectorFilesToAdd; Set 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 &Args, const Vector &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 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