blob: 10fa012fad0bff0a1b5a6361bfc7d4425f081d3e [file] [log] [blame]
kcc2e6ca5c2019-02-12 22:48:55 +00001//===- FuzzerFork.cpp - run fuzzing in separate subprocesses --------------===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
kcc64bcb922019-02-13 04:04:45 +00008// Spawn and orchestrate separate fuzzing processes.
kcc2e6ca5c2019-02-12 22:48:55 +00009//===----------------------------------------------------------------------===//
10
11#include "FuzzerCommand.h"
12#include "FuzzerFork.h"
13#include "FuzzerIO.h"
14#include "FuzzerMerge.h"
15#include "FuzzerSHA1.h"
16#include "FuzzerUtil.h"
17
kccb1fa9e02019-02-14 01:11:29 +000018#include <atomic>
kcc6526f1d2019-02-14 00:25:43 +000019#include <mutex>
20#include <thread>
21#include <queue>
22
kcc2e6ca5c2019-02-12 22:48:55 +000023namespace fuzzer {
24
kcc64bcb922019-02-13 04:04:45 +000025struct FuzzJob {
26 // Inputs.
27 Command Cmd;
kcc64bcb922019-02-13 04:04:45 +000028 std::string CorpusDir;
29 std::string LogPath;
30 std::string CFPath;
kcc64bcb922019-02-13 04:04:45 +000031
32 // Fuzzing Outputs.
33 int ExitCode;
34};
35
36struct GlobalEnv {
kcc6526f1d2019-02-14 00:25:43 +000037 Vector<std::string> Args;
38 Vector<std::string> CorpusDirs;
kcc64bcb922019-02-13 04:04:45 +000039 std::string MainCorpusDir;
kcc6526f1d2019-02-14 00:25:43 +000040 std::string TempDir;
kcc64bcb922019-02-13 04:04:45 +000041 Set<uint32_t> Features;
42 Vector<std::string> Files;
kcc6526f1d2019-02-14 00:25:43 +000043 Random *Rand;
44 int Verbosity = 0;
45
46 FuzzJob *CreateNewJob(size_t JobId) {
47 Command Cmd(Args);
48 Cmd.removeFlag("fork");
49 for (auto &C : CorpusDirs) // Remove all corpora from the args.
50 Cmd.removeArgument(C);
51 Cmd.addFlag("reload", "0"); // working in an isolated dir, no reload.
52 Cmd.addFlag("max_total_time", std::to_string(std::min((size_t)300, JobId)));
53
54 auto Job = new FuzzJob;
55 std::string Seeds;
56 if (size_t CorpusSubsetSize = std::min(Files.size(), (size_t)100))
57 for (size_t i = 0; i < CorpusSubsetSize; i++)
58 Seeds += (Seeds.empty() ? "" : ",") +
59 Files[Rand->SkewTowardsLast(Files.size())];
60 if (!Seeds.empty())
61 Cmd.addFlag("seed_inputs", Seeds);
62 Job->LogPath = DirPlusFile(TempDir, std::to_string(JobId) + ".log");
63 Job->CorpusDir = DirPlusFile(TempDir, "C" + std::to_string(JobId));
64 Job->CFPath = DirPlusFile(TempDir, std::to_string(JobId) + ".merge");
65
66
67 Cmd.addArgument(Job->CorpusDir);
68 RmDirRecursive(Job->CorpusDir);
69 MkDir(Job->CorpusDir);
70
71 Cmd.setOutputFile(Job->LogPath);
72 Cmd.combineOutAndErr();
73
74 Job->Cmd = Cmd;
75
76 if (Verbosity >= 2)
77 Printf("Job %zd/%p Created: %s\n", JobId, Job,
78 Job->Cmd.toString().c_str());
79 // Start from very short runs and gradually increase them.
80 return Job;
81 }
82
83 void RunOneMergeJob(FuzzJob *Job) {
84 Vector<SizedFile> TempFiles;
85 GetSizedFilesFromDir(Job->CorpusDir, &TempFiles);
86
87 Vector<std::string> FilesToAdd;
88 Set<uint32_t> NewFeatures;
89 CrashResistantMerge(Args, {}, TempFiles, &FilesToAdd, Features,
90 &NewFeatures, Job->CFPath, false);
91 RemoveFile(Job->CFPath);
92 for (auto &Path : FilesToAdd) {
93 auto U = FileToVector(Path);
94 auto NewPath = DirPlusFile(MainCorpusDir, Hash(U));
95 WriteToFile(U, NewPath);
96 Files.push_back(NewPath);
97 }
98 Printf("Removing %s\n", Job->CorpusDir.c_str());
99 RmDirRecursive(Job->CorpusDir);
100 Features.insert(NewFeatures.begin(), NewFeatures.end());
101 Printf("INFO: temp_files: %zd files_added: %zd newft: %zd ft: %zd\n",
102 TempFiles.size(), FilesToAdd.size(), NewFeatures.size(),
103 Features.size());
104 }
kcc64bcb922019-02-13 04:04:45 +0000105};
106
kcc6526f1d2019-02-14 00:25:43 +0000107struct JobQueue {
108 std::queue<FuzzJob *> Qu;
109 std::mutex Mu;
kcc64bcb922019-02-13 04:04:45 +0000110
kcc6526f1d2019-02-14 00:25:43 +0000111 void Push(FuzzJob *Job) {
112 std::lock_guard<std::mutex> Lock(Mu);
113 Qu.push(Job);
kcc64bcb922019-02-13 04:04:45 +0000114 }
kcc6526f1d2019-02-14 00:25:43 +0000115 FuzzJob *Pop() {
116 std::lock_guard<std::mutex> Lock(Mu);
117 if (Qu.empty()) return nullptr;
118 auto Job = Qu.front();
119 Qu.pop();
120 return Job;
121 }
122};
123
124void WorkerThread(std::atomic<bool> *Stop, JobQueue *FuzzQ, JobQueue *MergeQ) {
kccb1fa9e02019-02-14 01:11:29 +0000125 while (!Stop->load()) {
kcc6526f1d2019-02-14 00:25:43 +0000126 auto Job = FuzzQ->Pop();
127 // Printf("WorkerThread: job %p\n", Job);
128 if (!Job) {
129 SleepSeconds(1);
130 continue;
131 }
132 Job->ExitCode = ExecuteCommand(Job->Cmd);
133 MergeQ->Push(Job);
134 }
kcc64bcb922019-02-13 04:04:45 +0000135}
136
kcc2e6ca5c2019-02-12 22:48:55 +0000137// This is just a skeleton of an experimental -fork=1 feature.
138void FuzzWithFork(Random &Rand, const FuzzingOptions &Options,
139 const Vector<std::string> &Args,
kcc6526f1d2019-02-14 00:25:43 +0000140 const Vector<std::string> &CorpusDirs, int NumJobs) {
141 Printf("INFO: -fork=%d: doing fuzzing in a separate process in order to "
142 "be more resistant to crashes, timeouts, and OOMs\n", NumJobs);
kcc2e6ca5c2019-02-12 22:48:55 +0000143
kcc64bcb922019-02-13 04:04:45 +0000144 GlobalEnv Env;
kcc6526f1d2019-02-14 00:25:43 +0000145 Env.Args = Args;
146 Env.CorpusDirs = CorpusDirs;
147 Env.Rand = &Rand;
148 Env.Verbosity = Options.Verbosity;
kcc64bcb922019-02-13 04:04:45 +0000149
kcc2e6ca5c2019-02-12 22:48:55 +0000150 Vector<SizedFile> SeedFiles;
151 for (auto &Dir : CorpusDirs)
152 GetSizedFilesFromDir(Dir, &SeedFiles);
153 std::sort(SeedFiles.begin(), SeedFiles.end());
kcc6526f1d2019-02-14 00:25:43 +0000154 Env.TempDir = TempPath(".dir");
155 RmDirRecursive(Env.TempDir); // in case there is a leftover from old runs.
156 MkDir(Env.TempDir);
kcc2e6ca5c2019-02-12 22:48:55 +0000157
kcc2e6ca5c2019-02-12 22:48:55 +0000158
kcc64bcb922019-02-13 04:04:45 +0000159 if (CorpusDirs.empty())
kcc6526f1d2019-02-14 00:25:43 +0000160 MkDir(Env.MainCorpusDir = DirPlusFile(Env.TempDir, "C"));
kcc64bcb922019-02-13 04:04:45 +0000161 else
162 Env.MainCorpusDir = CorpusDirs[0];
163
kcc6526f1d2019-02-14 00:25:43 +0000164 auto CFPath = DirPlusFile(Env.TempDir, "merge.txt");
165 CrashResistantMerge(Env.Args, {}, SeedFiles, &Env.Files, {}, &Env.Features,
kcc64bcb922019-02-13 04:04:45 +0000166 CFPath, false);
167 RemoveFile(CFPath);
kcc6526f1d2019-02-14 00:25:43 +0000168 Printf("INFO: -fork=%d: %zd seeds, starting to fuzz; scratch: %s\n",
169 NumJobs, Env.Files.size(), Env.TempDir.c_str());
kcc64bcb922019-02-13 04:04:45 +0000170
kcc2e6ca5c2019-02-12 22:48:55 +0000171 int ExitCode = 0;
kcc64bcb922019-02-13 04:04:45 +0000172
kcc6526f1d2019-02-14 00:25:43 +0000173 JobQueue FuzzQ, MergeQ;
174 std::atomic<bool> Stop(false);
kcc64bcb922019-02-13 04:04:45 +0000175
kcc6526f1d2019-02-14 00:25:43 +0000176 size_t JobId = 1;
177 Vector<std::thread> Threads;
178 for (int t = 0; t < NumJobs; t++) {
179 Threads.push_back(std::thread(WorkerThread, &Stop, &FuzzQ, &MergeQ));
180 FuzzQ.Push(Env.CreateNewJob(JobId++));
kcc2e6ca5c2019-02-12 22:48:55 +0000181 }
182
kcc6526f1d2019-02-14 00:25:43 +0000183 while (!Stop) {
184 auto Job = MergeQ.Pop();
185 if (!Job) {
186 SleepSeconds(1);
187 continue;
188 }
189 ExitCode = Job->ExitCode;
190 if (ExitCode != Options.InterruptExitCode)
191 Env.RunOneMergeJob(Job);
192
193 // Continue if our crash is one of the ignorred ones.
194 if (Options.IgnoreTimeouts && ExitCode == Options.TimeoutExitCode)
195 ;
196 else if (Options.IgnoreOOMs && ExitCode == Options.OOMExitCode)
197 ;
198 else if (ExitCode == Options.InterruptExitCode)
199 Stop = true;
200 else if (ExitCode != 0) {
201 // And exit if we don't ignore this crash.
202 Printf("INFO: log from the inner process:\n%s",
203 FileToString(Job->LogPath).c_str());
204 Stop = true;
205 }
206 RemoveFile(Job->LogPath);
207 delete Job;
208 FuzzQ.Push(Env.CreateNewJob(JobId++));
209 }
210 Stop = true;
211
212 for (auto &T : Threads)
213 T.join();
214
215 RmDirRecursive(Env.TempDir);
kcc2e6ca5c2019-02-12 22:48:55 +0000216
217 // Use the exit code from the last child process.
218 Printf("Fork: exiting: %d\n", ExitCode);
219 exit(ExitCode);
220}
221
222} // namespace fuzzer
223