13cab2bb3Spatrick //===- FuzzerFork.cpp - run fuzzing in separate subprocesses --------------===//
23cab2bb3Spatrick //
33cab2bb3Spatrick // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
43cab2bb3Spatrick // See https://llvm.org/LICENSE.txt for license information.
53cab2bb3Spatrick // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
63cab2bb3Spatrick //
73cab2bb3Spatrick //===----------------------------------------------------------------------===//
83cab2bb3Spatrick // Spawn and orchestrate separate fuzzing processes.
93cab2bb3Spatrick //===----------------------------------------------------------------------===//
103cab2bb3Spatrick
113cab2bb3Spatrick #include "FuzzerCommand.h"
123cab2bb3Spatrick #include "FuzzerFork.h"
133cab2bb3Spatrick #include "FuzzerIO.h"
143cab2bb3Spatrick #include "FuzzerInternal.h"
153cab2bb3Spatrick #include "FuzzerMerge.h"
163cab2bb3Spatrick #include "FuzzerSHA1.h"
173cab2bb3Spatrick #include "FuzzerTracePC.h"
183cab2bb3Spatrick #include "FuzzerUtil.h"
193cab2bb3Spatrick
203cab2bb3Spatrick #include <atomic>
213cab2bb3Spatrick #include <chrono>
223cab2bb3Spatrick #include <condition_variable>
233cab2bb3Spatrick #include <fstream>
243cab2bb3Spatrick #include <memory>
253cab2bb3Spatrick #include <mutex>
263cab2bb3Spatrick #include <queue>
273cab2bb3Spatrick #include <sstream>
283cab2bb3Spatrick #include <thread>
293cab2bb3Spatrick
303cab2bb3Spatrick namespace fuzzer {
313cab2bb3Spatrick
323cab2bb3Spatrick struct Stats {
333cab2bb3Spatrick size_t number_of_executed_units = 0;
343cab2bb3Spatrick size_t peak_rss_mb = 0;
353cab2bb3Spatrick size_t average_exec_per_sec = 0;
363cab2bb3Spatrick };
373cab2bb3Spatrick
ParseFinalStatsFromLog(const std::string & LogPath)383cab2bb3Spatrick static Stats ParseFinalStatsFromLog(const std::string &LogPath) {
393cab2bb3Spatrick std::ifstream In(LogPath);
403cab2bb3Spatrick std::string Line;
413cab2bb3Spatrick Stats Res;
423cab2bb3Spatrick struct {
433cab2bb3Spatrick const char *Name;
443cab2bb3Spatrick size_t *Var;
453cab2bb3Spatrick } NameVarPairs[] = {
463cab2bb3Spatrick {"stat::number_of_executed_units:", &Res.number_of_executed_units},
473cab2bb3Spatrick {"stat::peak_rss_mb:", &Res.peak_rss_mb},
483cab2bb3Spatrick {"stat::average_exec_per_sec:", &Res.average_exec_per_sec},
493cab2bb3Spatrick {nullptr, nullptr},
503cab2bb3Spatrick };
513cab2bb3Spatrick while (std::getline(In, Line, '\n')) {
523cab2bb3Spatrick if (Line.find("stat::") != 0) continue;
533cab2bb3Spatrick std::istringstream ISS(Line);
543cab2bb3Spatrick std::string Name;
553cab2bb3Spatrick size_t Val;
563cab2bb3Spatrick ISS >> Name >> Val;
573cab2bb3Spatrick for (size_t i = 0; NameVarPairs[i].Name; i++)
583cab2bb3Spatrick if (Name == NameVarPairs[i].Name)
593cab2bb3Spatrick *NameVarPairs[i].Var = Val;
603cab2bb3Spatrick }
613cab2bb3Spatrick return Res;
623cab2bb3Spatrick }
633cab2bb3Spatrick
643cab2bb3Spatrick struct FuzzJob {
653cab2bb3Spatrick // Inputs.
663cab2bb3Spatrick Command Cmd;
673cab2bb3Spatrick std::string CorpusDir;
683cab2bb3Spatrick std::string FeaturesDir;
693cab2bb3Spatrick std::string LogPath;
703cab2bb3Spatrick std::string SeedListPath;
713cab2bb3Spatrick std::string CFPath;
723cab2bb3Spatrick size_t JobId;
733cab2bb3Spatrick
743cab2bb3Spatrick int DftTimeInSeconds = 0;
753cab2bb3Spatrick
763cab2bb3Spatrick // Fuzzing Outputs.
773cab2bb3Spatrick int ExitCode;
783cab2bb3Spatrick
~FuzzJobfuzzer::FuzzJob793cab2bb3Spatrick ~FuzzJob() {
803cab2bb3Spatrick RemoveFile(CFPath);
813cab2bb3Spatrick RemoveFile(LogPath);
823cab2bb3Spatrick RemoveFile(SeedListPath);
833cab2bb3Spatrick RmDirRecursive(CorpusDir);
843cab2bb3Spatrick RmDirRecursive(FeaturesDir);
853cab2bb3Spatrick }
863cab2bb3Spatrick };
873cab2bb3Spatrick
883cab2bb3Spatrick struct GlobalEnv {
89*810390e3Srobert std::vector<std::string> Args;
90*810390e3Srobert std::vector<std::string> CorpusDirs;
913cab2bb3Spatrick std::string MainCorpusDir;
923cab2bb3Spatrick std::string TempDir;
933cab2bb3Spatrick std::string DFTDir;
943cab2bb3Spatrick std::string DataFlowBinary;
95*810390e3Srobert std::set<uint32_t> Features, Cov;
96*810390e3Srobert std::set<std::string> FilesWithDFT;
97*810390e3Srobert std::vector<std::string> Files;
98*810390e3Srobert std::vector<std::size_t> FilesSizes;
993cab2bb3Spatrick Random *Rand;
1003cab2bb3Spatrick std::chrono::system_clock::time_point ProcessStartTime;
1013cab2bb3Spatrick int Verbosity = 0;
102*810390e3Srobert int Group = 0;
103*810390e3Srobert int NumCorpuses = 8;
1043cab2bb3Spatrick
1053cab2bb3Spatrick size_t NumTimeouts = 0;
1063cab2bb3Spatrick size_t NumOOMs = 0;
1073cab2bb3Spatrick size_t NumCrashes = 0;
1083cab2bb3Spatrick
1093cab2bb3Spatrick
1103cab2bb3Spatrick size_t NumRuns = 0;
1113cab2bb3Spatrick
StopFilefuzzer::GlobalEnv1123cab2bb3Spatrick std::string StopFile() { return DirPlusFile(TempDir, "STOP"); }
1133cab2bb3Spatrick
secondsSinceProcessStartUpfuzzer::GlobalEnv1143cab2bb3Spatrick size_t secondsSinceProcessStartUp() const {
1153cab2bb3Spatrick return std::chrono::duration_cast<std::chrono::seconds>(
1163cab2bb3Spatrick std::chrono::system_clock::now() - ProcessStartTime)
1173cab2bb3Spatrick .count();
1183cab2bb3Spatrick }
1193cab2bb3Spatrick
CreateNewJobfuzzer::GlobalEnv1203cab2bb3Spatrick FuzzJob *CreateNewJob(size_t JobId) {
1213cab2bb3Spatrick Command Cmd(Args);
1223cab2bb3Spatrick Cmd.removeFlag("fork");
1233cab2bb3Spatrick Cmd.removeFlag("runs");
1243cab2bb3Spatrick Cmd.removeFlag("collect_data_flow");
1253cab2bb3Spatrick for (auto &C : CorpusDirs) // Remove all corpora from the args.
1263cab2bb3Spatrick Cmd.removeArgument(C);
1273cab2bb3Spatrick Cmd.addFlag("reload", "0"); // working in an isolated dir, no reload.
1283cab2bb3Spatrick Cmd.addFlag("print_final_stats", "1");
1293cab2bb3Spatrick Cmd.addFlag("print_funcs", "0"); // no need to spend time symbolizing.
1303cab2bb3Spatrick Cmd.addFlag("max_total_time", std::to_string(std::min((size_t)300, JobId)));
1313cab2bb3Spatrick Cmd.addFlag("stop_file", StopFile());
1323cab2bb3Spatrick if (!DataFlowBinary.empty()) {
1333cab2bb3Spatrick Cmd.addFlag("data_flow_trace", DFTDir);
1343cab2bb3Spatrick if (!Cmd.hasFlag("focus_function"))
1353cab2bb3Spatrick Cmd.addFlag("focus_function", "auto");
1363cab2bb3Spatrick }
1373cab2bb3Spatrick auto Job = new FuzzJob;
1383cab2bb3Spatrick std::string Seeds;
1393cab2bb3Spatrick if (size_t CorpusSubsetSize =
1403cab2bb3Spatrick std::min(Files.size(), (size_t)sqrt(Files.size() + 2))) {
1413cab2bb3Spatrick auto Time1 = std::chrono::system_clock::now();
142*810390e3Srobert if (Group) { // whether to group the corpus.
143*810390e3Srobert size_t AverageCorpusSize = Files.size() / NumCorpuses + 1;
144*810390e3Srobert size_t StartIndex = ((JobId - 1) % NumCorpuses) * AverageCorpusSize;
145*810390e3Srobert for (size_t i = 0; i < CorpusSubsetSize; i++) {
146*810390e3Srobert size_t RandNum = (*Rand)(AverageCorpusSize);
147*810390e3Srobert size_t Index = RandNum + StartIndex;
148*810390e3Srobert Index = Index < Files.size() ? Index
149*810390e3Srobert : Rand->SkewTowardsLast(Files.size());
150*810390e3Srobert auto &SF = Files[Index];
151*810390e3Srobert Seeds += (Seeds.empty() ? "" : ",") + SF;
152*810390e3Srobert CollectDFT(SF);
153*810390e3Srobert }
154*810390e3Srobert } else {
1553cab2bb3Spatrick for (size_t i = 0; i < CorpusSubsetSize; i++) {
1563cab2bb3Spatrick auto &SF = Files[Rand->SkewTowardsLast(Files.size())];
1573cab2bb3Spatrick Seeds += (Seeds.empty() ? "" : ",") + SF;
1583cab2bb3Spatrick CollectDFT(SF);
1593cab2bb3Spatrick }
160*810390e3Srobert }
1613cab2bb3Spatrick auto Time2 = std::chrono::system_clock::now();
162d89ec533Spatrick auto DftTimeInSeconds = duration_cast<seconds>(Time2 - Time1).count();
163d89ec533Spatrick assert(DftTimeInSeconds < std::numeric_limits<int>::max());
164d89ec533Spatrick Job->DftTimeInSeconds = static_cast<int>(DftTimeInSeconds);
1653cab2bb3Spatrick }
1663cab2bb3Spatrick if (!Seeds.empty()) {
1673cab2bb3Spatrick Job->SeedListPath =
1683cab2bb3Spatrick DirPlusFile(TempDir, std::to_string(JobId) + ".seeds");
1693cab2bb3Spatrick WriteToFile(Seeds, Job->SeedListPath);
1703cab2bb3Spatrick Cmd.addFlag("seed_inputs", "@" + Job->SeedListPath);
1713cab2bb3Spatrick }
1723cab2bb3Spatrick Job->LogPath = DirPlusFile(TempDir, std::to_string(JobId) + ".log");
1733cab2bb3Spatrick Job->CorpusDir = DirPlusFile(TempDir, "C" + std::to_string(JobId));
1743cab2bb3Spatrick Job->FeaturesDir = DirPlusFile(TempDir, "F" + std::to_string(JobId));
1753cab2bb3Spatrick Job->CFPath = DirPlusFile(TempDir, std::to_string(JobId) + ".merge");
1763cab2bb3Spatrick Job->JobId = JobId;
1773cab2bb3Spatrick
1783cab2bb3Spatrick
1793cab2bb3Spatrick Cmd.addArgument(Job->CorpusDir);
1803cab2bb3Spatrick Cmd.addFlag("features_dir", Job->FeaturesDir);
1813cab2bb3Spatrick
1823cab2bb3Spatrick for (auto &D : {Job->CorpusDir, Job->FeaturesDir}) {
1833cab2bb3Spatrick RmDirRecursive(D);
1843cab2bb3Spatrick MkDir(D);
1853cab2bb3Spatrick }
1863cab2bb3Spatrick
1873cab2bb3Spatrick Cmd.setOutputFile(Job->LogPath);
1883cab2bb3Spatrick Cmd.combineOutAndErr();
1893cab2bb3Spatrick
1903cab2bb3Spatrick Job->Cmd = Cmd;
1913cab2bb3Spatrick
1923cab2bb3Spatrick if (Verbosity >= 2)
1933cab2bb3Spatrick Printf("Job %zd/%p Created: %s\n", JobId, Job,
1943cab2bb3Spatrick Job->Cmd.toString().c_str());
1953cab2bb3Spatrick // Start from very short runs and gradually increase them.
1963cab2bb3Spatrick return Job;
1973cab2bb3Spatrick }
1983cab2bb3Spatrick
RunOneMergeJobfuzzer::GlobalEnv1993cab2bb3Spatrick void RunOneMergeJob(FuzzJob *Job) {
2003cab2bb3Spatrick auto Stats = ParseFinalStatsFromLog(Job->LogPath);
2013cab2bb3Spatrick NumRuns += Stats.number_of_executed_units;
2023cab2bb3Spatrick
203*810390e3Srobert std::vector<SizedFile> TempFiles, MergeCandidates;
2043cab2bb3Spatrick // Read all newly created inputs and their feature sets.
2053cab2bb3Spatrick // Choose only those inputs that have new features.
2063cab2bb3Spatrick GetSizedFilesFromDir(Job->CorpusDir, &TempFiles);
2073cab2bb3Spatrick std::sort(TempFiles.begin(), TempFiles.end());
2083cab2bb3Spatrick for (auto &F : TempFiles) {
2093cab2bb3Spatrick auto FeatureFile = F.File;
2103cab2bb3Spatrick FeatureFile.replace(0, Job->CorpusDir.size(), Job->FeaturesDir);
2113cab2bb3Spatrick auto FeatureBytes = FileToVector(FeatureFile, 0, false);
2123cab2bb3Spatrick assert((FeatureBytes.size() % sizeof(uint32_t)) == 0);
213*810390e3Srobert std::vector<uint32_t> NewFeatures(FeatureBytes.size() / sizeof(uint32_t));
2143cab2bb3Spatrick memcpy(NewFeatures.data(), FeatureBytes.data(), FeatureBytes.size());
2153cab2bb3Spatrick for (auto Ft : NewFeatures) {
2163cab2bb3Spatrick if (!Features.count(Ft)) {
2173cab2bb3Spatrick MergeCandidates.push_back(F);
2183cab2bb3Spatrick break;
2193cab2bb3Spatrick }
2203cab2bb3Spatrick }
2213cab2bb3Spatrick }
2223cab2bb3Spatrick // if (!FilesToAdd.empty() || Job->ExitCode != 0)
223*810390e3Srobert Printf("#%zd: cov: %zd ft: %zd corp: %zd exec/s: %zd "
2243cab2bb3Spatrick "oom/timeout/crash: %zd/%zd/%zd time: %zds job: %zd dft_time: %d\n",
2253cab2bb3Spatrick NumRuns, Cov.size(), Features.size(), Files.size(),
2263cab2bb3Spatrick Stats.average_exec_per_sec, NumOOMs, NumTimeouts, NumCrashes,
2273cab2bb3Spatrick secondsSinceProcessStartUp(), Job->JobId, Job->DftTimeInSeconds);
2283cab2bb3Spatrick
2293cab2bb3Spatrick if (MergeCandidates.empty()) return;
2303cab2bb3Spatrick
231*810390e3Srobert std::vector<std::string> FilesToAdd;
232*810390e3Srobert std::set<uint32_t> NewFeatures, NewCov;
233*810390e3Srobert bool IsSetCoverMerge =
234*810390e3Srobert !Job->Cmd.getFlagValue("set_cover_merge").compare("1");
2353cab2bb3Spatrick CrashResistantMerge(Args, {}, MergeCandidates, &FilesToAdd, Features,
236*810390e3Srobert &NewFeatures, Cov, &NewCov, Job->CFPath, false,
237*810390e3Srobert IsSetCoverMerge);
2383cab2bb3Spatrick for (auto &Path : FilesToAdd) {
2393cab2bb3Spatrick auto U = FileToVector(Path);
2403cab2bb3Spatrick auto NewPath = DirPlusFile(MainCorpusDir, Hash(U));
2413cab2bb3Spatrick WriteToFile(U, NewPath);
242*810390e3Srobert if (Group) { // Insert the queue according to the size of the seed.
243*810390e3Srobert size_t UnitSize = U.size();
244*810390e3Srobert auto Idx =
245*810390e3Srobert std::upper_bound(FilesSizes.begin(), FilesSizes.end(), UnitSize) -
246*810390e3Srobert FilesSizes.begin();
247*810390e3Srobert FilesSizes.insert(FilesSizes.begin() + Idx, UnitSize);
248*810390e3Srobert Files.insert(Files.begin() + Idx, NewPath);
249*810390e3Srobert } else {
2503cab2bb3Spatrick Files.push_back(NewPath);
2513cab2bb3Spatrick }
252*810390e3Srobert }
2533cab2bb3Spatrick Features.insert(NewFeatures.begin(), NewFeatures.end());
2543cab2bb3Spatrick Cov.insert(NewCov.begin(), NewCov.end());
2553cab2bb3Spatrick for (auto Idx : NewCov)
2563cab2bb3Spatrick if (auto *TE = TPC.PCTableEntryByIdx(Idx))
2573cab2bb3Spatrick if (TPC.PcIsFuncEntry(TE))
2583cab2bb3Spatrick PrintPC(" NEW_FUNC: %p %F %L\n", "",
2593cab2bb3Spatrick TPC.GetNextInstructionPc(TE->PC));
2603cab2bb3Spatrick }
2613cab2bb3Spatrick
CollectDFTfuzzer::GlobalEnv2623cab2bb3Spatrick void CollectDFT(const std::string &InputPath) {
2633cab2bb3Spatrick if (DataFlowBinary.empty()) return;
2643cab2bb3Spatrick if (!FilesWithDFT.insert(InputPath).second) return;
2653cab2bb3Spatrick Command Cmd(Args);
2663cab2bb3Spatrick Cmd.removeFlag("fork");
2673cab2bb3Spatrick Cmd.removeFlag("runs");
2683cab2bb3Spatrick Cmd.addFlag("data_flow_trace", DFTDir);
2693cab2bb3Spatrick Cmd.addArgument(InputPath);
2703cab2bb3Spatrick for (auto &C : CorpusDirs) // Remove all corpora from the args.
2713cab2bb3Spatrick Cmd.removeArgument(C);
2723cab2bb3Spatrick Cmd.setOutputFile(DirPlusFile(TempDir, "dft.log"));
2733cab2bb3Spatrick Cmd.combineOutAndErr();
2743cab2bb3Spatrick // Printf("CollectDFT: %s\n", Cmd.toString().c_str());
2753cab2bb3Spatrick ExecuteCommand(Cmd);
2763cab2bb3Spatrick }
2773cab2bb3Spatrick
2783cab2bb3Spatrick };
2793cab2bb3Spatrick
2803cab2bb3Spatrick struct JobQueue {
2813cab2bb3Spatrick std::queue<FuzzJob *> Qu;
2823cab2bb3Spatrick std::mutex Mu;
2833cab2bb3Spatrick std::condition_variable Cv;
2843cab2bb3Spatrick
Pushfuzzer::JobQueue2853cab2bb3Spatrick void Push(FuzzJob *Job) {
2863cab2bb3Spatrick {
2873cab2bb3Spatrick std::lock_guard<std::mutex> Lock(Mu);
2883cab2bb3Spatrick Qu.push(Job);
2893cab2bb3Spatrick }
2903cab2bb3Spatrick Cv.notify_one();
2913cab2bb3Spatrick }
Popfuzzer::JobQueue2923cab2bb3Spatrick FuzzJob *Pop() {
2933cab2bb3Spatrick std::unique_lock<std::mutex> Lk(Mu);
2943cab2bb3Spatrick // std::lock_guard<std::mutex> Lock(Mu);
2953cab2bb3Spatrick Cv.wait(Lk, [&]{return !Qu.empty();});
2963cab2bb3Spatrick assert(!Qu.empty());
2973cab2bb3Spatrick auto Job = Qu.front();
2983cab2bb3Spatrick Qu.pop();
2993cab2bb3Spatrick return Job;
3003cab2bb3Spatrick }
3013cab2bb3Spatrick };
3023cab2bb3Spatrick
WorkerThread(JobQueue * FuzzQ,JobQueue * MergeQ)3033cab2bb3Spatrick void WorkerThread(JobQueue *FuzzQ, JobQueue *MergeQ) {
3043cab2bb3Spatrick while (auto Job = FuzzQ->Pop()) {
3053cab2bb3Spatrick // Printf("WorkerThread: job %p\n", Job);
3063cab2bb3Spatrick Job->ExitCode = ExecuteCommand(Job->Cmd);
3073cab2bb3Spatrick MergeQ->Push(Job);
3083cab2bb3Spatrick }
3093cab2bb3Spatrick }
3103cab2bb3Spatrick
3113cab2bb3Spatrick // This is just a skeleton of an experimental -fork=1 feature.
FuzzWithFork(Random & Rand,const FuzzingOptions & Options,const std::vector<std::string> & Args,const std::vector<std::string> & CorpusDirs,int NumJobs)3123cab2bb3Spatrick void FuzzWithFork(Random &Rand, const FuzzingOptions &Options,
313*810390e3Srobert const std::vector<std::string> &Args,
314*810390e3Srobert const std::vector<std::string> &CorpusDirs, int NumJobs) {
3153cab2bb3Spatrick Printf("INFO: -fork=%d: fuzzing in separate process(s)\n", NumJobs);
3163cab2bb3Spatrick
3173cab2bb3Spatrick GlobalEnv Env;
3183cab2bb3Spatrick Env.Args = Args;
3193cab2bb3Spatrick Env.CorpusDirs = CorpusDirs;
3203cab2bb3Spatrick Env.Rand = &Rand;
3213cab2bb3Spatrick Env.Verbosity = Options.Verbosity;
3223cab2bb3Spatrick Env.ProcessStartTime = std::chrono::system_clock::now();
3233cab2bb3Spatrick Env.DataFlowBinary = Options.CollectDataFlow;
324*810390e3Srobert Env.Group = Options.ForkCorpusGroups;
3253cab2bb3Spatrick
326*810390e3Srobert std::vector<SizedFile> SeedFiles;
3273cab2bb3Spatrick for (auto &Dir : CorpusDirs)
3283cab2bb3Spatrick GetSizedFilesFromDir(Dir, &SeedFiles);
3293cab2bb3Spatrick std::sort(SeedFiles.begin(), SeedFiles.end());
3301f9cb04fSpatrick Env.TempDir = TempPath("FuzzWithFork", ".dir");
3313cab2bb3Spatrick Env.DFTDir = DirPlusFile(Env.TempDir, "DFT");
3323cab2bb3Spatrick RmDirRecursive(Env.TempDir); // in case there is a leftover from old runs.
3333cab2bb3Spatrick MkDir(Env.TempDir);
3343cab2bb3Spatrick MkDir(Env.DFTDir);
3353cab2bb3Spatrick
3363cab2bb3Spatrick
3373cab2bb3Spatrick if (CorpusDirs.empty())
3383cab2bb3Spatrick MkDir(Env.MainCorpusDir = DirPlusFile(Env.TempDir, "C"));
3393cab2bb3Spatrick else
3403cab2bb3Spatrick Env.MainCorpusDir = CorpusDirs[0];
3413cab2bb3Spatrick
342d89ec533Spatrick if (Options.KeepSeed) {
343d89ec533Spatrick for (auto &File : SeedFiles)
344d89ec533Spatrick Env.Files.push_back(File.File);
345d89ec533Spatrick } else {
3463cab2bb3Spatrick auto CFPath = DirPlusFile(Env.TempDir, "merge.txt");
347*810390e3Srobert std::set<uint32_t> NewFeatures, NewCov;
348d89ec533Spatrick CrashResistantMerge(Env.Args, {}, SeedFiles, &Env.Files, Env.Features,
349*810390e3Srobert &NewFeatures, Env.Cov, &NewCov, CFPath,
350*810390e3Srobert /*Verbose=*/false, /*IsSetCoverMerge=*/false);
351d89ec533Spatrick Env.Features.insert(NewFeatures.begin(), NewFeatures.end());
352d89ec533Spatrick Env.Cov.insert(NewFeatures.begin(), NewFeatures.end());
3533cab2bb3Spatrick RemoveFile(CFPath);
354d89ec533Spatrick }
355*810390e3Srobert
356*810390e3Srobert if (Env.Group) {
357*810390e3Srobert for (auto &path : Env.Files)
358*810390e3Srobert Env.FilesSizes.push_back(FileSize(path));
359*810390e3Srobert }
360*810390e3Srobert
3613cab2bb3Spatrick Printf("INFO: -fork=%d: %zd seed inputs, starting to fuzz in %s\n", NumJobs,
3623cab2bb3Spatrick Env.Files.size(), Env.TempDir.c_str());
3633cab2bb3Spatrick
3643cab2bb3Spatrick int ExitCode = 0;
3653cab2bb3Spatrick
3663cab2bb3Spatrick JobQueue FuzzQ, MergeQ;
3673cab2bb3Spatrick
3683cab2bb3Spatrick auto StopJobs = [&]() {
3693cab2bb3Spatrick for (int i = 0; i < NumJobs; i++)
3703cab2bb3Spatrick FuzzQ.Push(nullptr);
3713cab2bb3Spatrick MergeQ.Push(nullptr);
3723cab2bb3Spatrick WriteToFile(Unit({1}), Env.StopFile());
3733cab2bb3Spatrick };
3743cab2bb3Spatrick
375*810390e3Srobert size_t MergeCycle = 20;
376*810390e3Srobert size_t JobExecuted = 0;
3773cab2bb3Spatrick size_t JobId = 1;
378*810390e3Srobert std::vector<std::thread> Threads;
3793cab2bb3Spatrick for (int t = 0; t < NumJobs; t++) {
3803cab2bb3Spatrick Threads.push_back(std::thread(WorkerThread, &FuzzQ, &MergeQ));
3813cab2bb3Spatrick FuzzQ.Push(Env.CreateNewJob(JobId++));
3823cab2bb3Spatrick }
3833cab2bb3Spatrick
3843cab2bb3Spatrick while (true) {
3853cab2bb3Spatrick std::unique_ptr<FuzzJob> Job(MergeQ.Pop());
3863cab2bb3Spatrick if (!Job)
3873cab2bb3Spatrick break;
3883cab2bb3Spatrick ExitCode = Job->ExitCode;
3893cab2bb3Spatrick if (ExitCode == Options.InterruptExitCode) {
3903cab2bb3Spatrick Printf("==%lu== libFuzzer: a child was interrupted; exiting\n", GetPid());
3913cab2bb3Spatrick StopJobs();
3923cab2bb3Spatrick break;
3933cab2bb3Spatrick }
3943cab2bb3Spatrick Fuzzer::MaybeExitGracefully();
3953cab2bb3Spatrick
3963cab2bb3Spatrick Env.RunOneMergeJob(Job.get());
3973cab2bb3Spatrick
398*810390e3Srobert // merge the corpus .
399*810390e3Srobert JobExecuted++;
400*810390e3Srobert if (Env.Group && JobExecuted >= MergeCycle) {
401*810390e3Srobert std::vector<SizedFile> CurrentSeedFiles;
402*810390e3Srobert for (auto &Dir : CorpusDirs)
403*810390e3Srobert GetSizedFilesFromDir(Dir, &CurrentSeedFiles);
404*810390e3Srobert std::sort(CurrentSeedFiles.begin(), CurrentSeedFiles.end());
405*810390e3Srobert
406*810390e3Srobert auto CFPath = DirPlusFile(Env.TempDir, "merge.txt");
407*810390e3Srobert std::set<uint32_t> TmpNewFeatures, TmpNewCov;
408*810390e3Srobert std::set<uint32_t> TmpFeatures, TmpCov;
409*810390e3Srobert Env.Files.clear();
410*810390e3Srobert Env.FilesSizes.clear();
411*810390e3Srobert CrashResistantMerge(Env.Args, {}, CurrentSeedFiles, &Env.Files,
412*810390e3Srobert TmpFeatures, &TmpNewFeatures, TmpCov, &TmpNewCov,
413*810390e3Srobert CFPath, /*Verbose=*/false, /*IsSetCoverMerge=*/false);
414*810390e3Srobert for (auto &path : Env.Files)
415*810390e3Srobert Env.FilesSizes.push_back(FileSize(path));
416*810390e3Srobert RemoveFile(CFPath);
417*810390e3Srobert JobExecuted = 0;
418*810390e3Srobert MergeCycle += 5;
419*810390e3Srobert }
420*810390e3Srobert
421*810390e3Srobert // Since the number of corpus seeds will gradually increase, in order to
422*810390e3Srobert // control the number in each group to be about three times the number of
423*810390e3Srobert // seeds selected each time, the number of groups is dynamically adjusted.
424*810390e3Srobert if (Env.Files.size() < 2000)
425*810390e3Srobert Env.NumCorpuses = 12;
426*810390e3Srobert else if (Env.Files.size() < 6000)
427*810390e3Srobert Env.NumCorpuses = 20;
428*810390e3Srobert else if (Env.Files.size() < 12000)
429*810390e3Srobert Env.NumCorpuses = 32;
430*810390e3Srobert else if (Env.Files.size() < 16000)
431*810390e3Srobert Env.NumCorpuses = 40;
432*810390e3Srobert else if (Env.Files.size() < 24000)
433*810390e3Srobert Env.NumCorpuses = 60;
434*810390e3Srobert else
435*810390e3Srobert Env.NumCorpuses = 80;
436*810390e3Srobert
437*810390e3Srobert // Continue if our crash is one of the ignored ones.
4383cab2bb3Spatrick if (Options.IgnoreTimeouts && ExitCode == Options.TimeoutExitCode)
4393cab2bb3Spatrick Env.NumTimeouts++;
4403cab2bb3Spatrick else if (Options.IgnoreOOMs && ExitCode == Options.OOMExitCode)
4413cab2bb3Spatrick Env.NumOOMs++;
4423cab2bb3Spatrick else if (ExitCode != 0) {
4433cab2bb3Spatrick Env.NumCrashes++;
4443cab2bb3Spatrick if (Options.IgnoreCrashes) {
4453cab2bb3Spatrick std::ifstream In(Job->LogPath);
4463cab2bb3Spatrick std::string Line;
4473cab2bb3Spatrick while (std::getline(In, Line, '\n'))
4483cab2bb3Spatrick if (Line.find("ERROR:") != Line.npos ||
4493cab2bb3Spatrick Line.find("runtime error:") != Line.npos)
4503cab2bb3Spatrick Printf("%s\n", Line.c_str());
4513cab2bb3Spatrick } else {
4523cab2bb3Spatrick // And exit if we don't ignore this crash.
4533cab2bb3Spatrick Printf("INFO: log from the inner process:\n%s",
4543cab2bb3Spatrick FileToString(Job->LogPath).c_str());
4553cab2bb3Spatrick StopJobs();
4563cab2bb3Spatrick break;
4573cab2bb3Spatrick }
4583cab2bb3Spatrick }
4593cab2bb3Spatrick
4603cab2bb3Spatrick // Stop if we are over the time budget.
4613cab2bb3Spatrick // This is not precise, since other threads are still running
4623cab2bb3Spatrick // and we will wait while joining them.
4633cab2bb3Spatrick // We also don't stop instantly: other jobs need to finish.
4643cab2bb3Spatrick if (Options.MaxTotalTimeSec > 0 &&
4653cab2bb3Spatrick Env.secondsSinceProcessStartUp() >= (size_t)Options.MaxTotalTimeSec) {
4663cab2bb3Spatrick Printf("INFO: fuzzed for %zd seconds, wrapping up soon\n",
4673cab2bb3Spatrick Env.secondsSinceProcessStartUp());
4683cab2bb3Spatrick StopJobs();
4693cab2bb3Spatrick break;
4703cab2bb3Spatrick }
4713cab2bb3Spatrick if (Env.NumRuns >= Options.MaxNumberOfRuns) {
4723cab2bb3Spatrick Printf("INFO: fuzzed for %zd iterations, wrapping up soon\n",
4733cab2bb3Spatrick Env.NumRuns);
4743cab2bb3Spatrick StopJobs();
4753cab2bb3Spatrick break;
4763cab2bb3Spatrick }
4773cab2bb3Spatrick
4783cab2bb3Spatrick FuzzQ.Push(Env.CreateNewJob(JobId++));
4793cab2bb3Spatrick }
4803cab2bb3Spatrick
4813cab2bb3Spatrick for (auto &T : Threads)
4823cab2bb3Spatrick T.join();
4833cab2bb3Spatrick
4843cab2bb3Spatrick // The workers have terminated. Don't try to remove the directory before they
4853cab2bb3Spatrick // terminate to avoid a race condition preventing cleanup on Windows.
4863cab2bb3Spatrick RmDirRecursive(Env.TempDir);
4873cab2bb3Spatrick
4883cab2bb3Spatrick // Use the exit code from the last child process.
4893cab2bb3Spatrick Printf("INFO: exiting: %d time: %zds\n", ExitCode,
4903cab2bb3Spatrick Env.secondsSinceProcessStartUp());
4913cab2bb3Spatrick exit(ExitCode);
4923cab2bb3Spatrick }
4933cab2bb3Spatrick
4943cab2bb3Spatrick } // namespace fuzzer
495