blob: 09da1924b9b9d5909e75a69e212250c93c3017e9 [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"
kcc9c0ed932019-02-15 01:22:00 +000016#include "FuzzerTracePC.h"
kcc2e6ca5c2019-02-12 22:48:55 +000017#include "FuzzerUtil.h"
18
kccb1fa9e02019-02-14 01:11:29 +000019#include <atomic>
kcc55e54ed2019-02-15 21:51:15 +000020#include <chrono>
kccdd391142019-02-14 21:09:32 +000021#include <fstream>
kcc6526f1d2019-02-14 00:25:43 +000022#include <mutex>
kcc6526f1d2019-02-14 00:25:43 +000023#include <queue>
kccdd391142019-02-14 21:09:32 +000024#include <sstream>
25#include <thread>
kcc6526f1d2019-02-14 00:25:43 +000026
kcc2e6ca5c2019-02-12 22:48:55 +000027namespace fuzzer {
28
kccdd391142019-02-14 21:09:32 +000029struct Stats {
30 size_t number_of_executed_units = 0;
31 size_t peak_rss_mb = 0;
32 size_t average_exec_per_sec = 0;
33};
34
35static Stats ParseFinalStatsFromLog(const std::string &LogPath) {
36 std::ifstream In(LogPath);
37 std::string Line;
38 Stats Res;
39 struct {
40 const char *Name;
41 size_t *Var;
42 } NameVarPairs[] = {
43 {"stat::number_of_executed_units:", &Res.number_of_executed_units},
44 {"stat::peak_rss_mb:", &Res.peak_rss_mb},
45 {"stat::average_exec_per_sec:", &Res.average_exec_per_sec},
46 {nullptr, nullptr},
47 };
48 while (std::getline(In, Line, '\n')) {
49 if (Line.find("stat::") != 0) continue;
50 std::istringstream ISS(Line);
51 std::string Name;
52 size_t Val;
53 ISS >> Name >> Val;
54 for (size_t i = 0; NameVarPairs[i].Name; i++)
55 if (Name == NameVarPairs[i].Name)
56 *NameVarPairs[i].Var = Val;
57 }
58 return Res;
59}
60
kcc64bcb922019-02-13 04:04:45 +000061struct FuzzJob {
62 // Inputs.
63 Command Cmd;
kcc64bcb922019-02-13 04:04:45 +000064 std::string CorpusDir;
65 std::string LogPath;
66 std::string CFPath;
kcc64bcb922019-02-13 04:04:45 +000067
68 // Fuzzing Outputs.
69 int ExitCode;
70};
71
72struct GlobalEnv {
kcc6526f1d2019-02-14 00:25:43 +000073 Vector<std::string> Args;
74 Vector<std::string> CorpusDirs;
kcc64bcb922019-02-13 04:04:45 +000075 std::string MainCorpusDir;
kcc6526f1d2019-02-14 00:25:43 +000076 std::string TempDir;
kcc98a86242019-02-15 00:08:16 +000077 Set<uint32_t> Features, Cov;
kcc64bcb922019-02-13 04:04:45 +000078 Vector<std::string> Files;
kcc6526f1d2019-02-14 00:25:43 +000079 Random *Rand;
kcc55e54ed2019-02-15 21:51:15 +000080 std::chrono::system_clock::time_point ProcessStartTime;
kcc6526f1d2019-02-14 00:25:43 +000081 int Verbosity = 0;
82
kcc55e54ed2019-02-15 21:51:15 +000083 size_t NumTimeouts = 0;
84 size_t NumOOMs = 0;
85 size_t NumCrashes = 0;
86
87
kccdd391142019-02-14 21:09:32 +000088 size_t NumRuns = 0;
89
kcc55e54ed2019-02-15 21:51:15 +000090 size_t secondsSinceProcessStartUp() const {
91 return std::chrono::duration_cast<std::chrono::seconds>(
92 std::chrono::system_clock::now() - ProcessStartTime)
93 .count();
94 }
95
kcc6526f1d2019-02-14 00:25:43 +000096 FuzzJob *CreateNewJob(size_t JobId) {
97 Command Cmd(Args);
98 Cmd.removeFlag("fork");
99 for (auto &C : CorpusDirs) // Remove all corpora from the args.
100 Cmd.removeArgument(C);
101 Cmd.addFlag("reload", "0"); // working in an isolated dir, no reload.
kccdd391142019-02-14 21:09:32 +0000102 Cmd.addFlag("print_final_stats", "1");
kcc9c0ed932019-02-15 01:22:00 +0000103 Cmd.addFlag("print_funcs", "0"); // no need to spend time symbolizing.
kcc6526f1d2019-02-14 00:25:43 +0000104 Cmd.addFlag("max_total_time", std::to_string(std::min((size_t)300, JobId)));
105
106 auto Job = new FuzzJob;
107 std::string Seeds;
kcc9c0ed932019-02-15 01:22:00 +0000108 if (size_t CorpusSubsetSize =
109 std::min(Files.size(), (size_t)sqrt(Files.size() + 2)))
kcc6526f1d2019-02-14 00:25:43 +0000110 for (size_t i = 0; i < CorpusSubsetSize; i++)
111 Seeds += (Seeds.empty() ? "" : ",") +
112 Files[Rand->SkewTowardsLast(Files.size())];
113 if (!Seeds.empty())
114 Cmd.addFlag("seed_inputs", Seeds);
115 Job->LogPath = DirPlusFile(TempDir, std::to_string(JobId) + ".log");
116 Job->CorpusDir = DirPlusFile(TempDir, "C" + std::to_string(JobId));
117 Job->CFPath = DirPlusFile(TempDir, std::to_string(JobId) + ".merge");
118
119
120 Cmd.addArgument(Job->CorpusDir);
121 RmDirRecursive(Job->CorpusDir);
122 MkDir(Job->CorpusDir);
123
124 Cmd.setOutputFile(Job->LogPath);
125 Cmd.combineOutAndErr();
126
127 Job->Cmd = Cmd;
128
129 if (Verbosity >= 2)
130 Printf("Job %zd/%p Created: %s\n", JobId, Job,
131 Job->Cmd.toString().c_str());
132 // Start from very short runs and gradually increase them.
133 return Job;
134 }
135
136 void RunOneMergeJob(FuzzJob *Job) {
137 Vector<SizedFile> TempFiles;
138 GetSizedFilesFromDir(Job->CorpusDir, &TempFiles);
139
140 Vector<std::string> FilesToAdd;
kcc98a86242019-02-15 00:08:16 +0000141 Set<uint32_t> NewFeatures, NewCov;
kcc6526f1d2019-02-14 00:25:43 +0000142 CrashResistantMerge(Args, {}, TempFiles, &FilesToAdd, Features,
kcc98a86242019-02-15 00:08:16 +0000143 &NewFeatures, Cov, &NewCov, Job->CFPath, false);
kcc6526f1d2019-02-14 00:25:43 +0000144 RemoveFile(Job->CFPath);
145 for (auto &Path : FilesToAdd) {
146 auto U = FileToVector(Path);
147 auto NewPath = DirPlusFile(MainCorpusDir, Hash(U));
148 WriteToFile(U, NewPath);
149 Files.push_back(NewPath);
150 }
kcc6526f1d2019-02-14 00:25:43 +0000151 RmDirRecursive(Job->CorpusDir);
152 Features.insert(NewFeatures.begin(), NewFeatures.end());
kcc98a86242019-02-15 00:08:16 +0000153 Cov.insert(NewCov.begin(), NewCov.end());
kcc9c0ed932019-02-15 01:22:00 +0000154 for (auto Idx : NewCov)
155 if (auto *TE = TPC.PCTableEntryByIdx(Idx))
156 if (TPC.PcIsFuncEntry(TE))
157 PrintPC(" NEW_FUNC: %p %F %L\n", "",
158 TPC.GetNextInstructionPc(TE->PC));
159
kccdd391142019-02-14 21:09:32 +0000160 auto Stats = ParseFinalStatsFromLog(Job->LogPath);
161 NumRuns += Stats.number_of_executed_units;
kcc55e54ed2019-02-15 21:51:15 +0000162 if (!FilesToAdd.empty() || Job->ExitCode != 0)
163 Printf("#%zd: cov: %zd ft: %zd corp: %zd exec/s %zd "
164 "oom/timeout/crash: %zd/%zd/%zd time: %zds\n", NumRuns,
kcc98a86242019-02-15 00:08:16 +0000165 Cov.size(), Features.size(), Files.size(),
kcc55e54ed2019-02-15 21:51:15 +0000166 Stats.average_exec_per_sec,
167 NumOOMs, NumTimeouts, NumCrashes, secondsSinceProcessStartUp());
kcc6526f1d2019-02-14 00:25:43 +0000168 }
kcc64bcb922019-02-13 04:04:45 +0000169};
170
kcc6526f1d2019-02-14 00:25:43 +0000171struct JobQueue {
172 std::queue<FuzzJob *> Qu;
173 std::mutex Mu;
kcc64bcb922019-02-13 04:04:45 +0000174
kcc6526f1d2019-02-14 00:25:43 +0000175 void Push(FuzzJob *Job) {
176 std::lock_guard<std::mutex> Lock(Mu);
177 Qu.push(Job);
kcc64bcb922019-02-13 04:04:45 +0000178 }
kcc6526f1d2019-02-14 00:25:43 +0000179 FuzzJob *Pop() {
180 std::lock_guard<std::mutex> Lock(Mu);
181 if (Qu.empty()) return nullptr;
182 auto Job = Qu.front();
183 Qu.pop();
184 return Job;
185 }
186};
187
188void WorkerThread(std::atomic<bool> *Stop, JobQueue *FuzzQ, JobQueue *MergeQ) {
kccb1fa9e02019-02-14 01:11:29 +0000189 while (!Stop->load()) {
kcc6526f1d2019-02-14 00:25:43 +0000190 auto Job = FuzzQ->Pop();
191 // Printf("WorkerThread: job %p\n", Job);
192 if (!Job) {
193 SleepSeconds(1);
194 continue;
195 }
196 Job->ExitCode = ExecuteCommand(Job->Cmd);
197 MergeQ->Push(Job);
198 }
kcc64bcb922019-02-13 04:04:45 +0000199}
200
kcc2e6ca5c2019-02-12 22:48:55 +0000201// This is just a skeleton of an experimental -fork=1 feature.
202void FuzzWithFork(Random &Rand, const FuzzingOptions &Options,
203 const Vector<std::string> &Args,
kcc6526f1d2019-02-14 00:25:43 +0000204 const Vector<std::string> &CorpusDirs, int NumJobs) {
kcc55e54ed2019-02-15 21:51:15 +0000205 Printf("INFO: -fork=%d: fuzzing in separate process(s)\n", NumJobs);
kcc2e6ca5c2019-02-12 22:48:55 +0000206
kcc64bcb922019-02-13 04:04:45 +0000207 GlobalEnv Env;
kcc6526f1d2019-02-14 00:25:43 +0000208 Env.Args = Args;
209 Env.CorpusDirs = CorpusDirs;
210 Env.Rand = &Rand;
211 Env.Verbosity = Options.Verbosity;
kcc55e54ed2019-02-15 21:51:15 +0000212 Env.ProcessStartTime = std::chrono::system_clock::now();
kcc64bcb922019-02-13 04:04:45 +0000213
kcc2e6ca5c2019-02-12 22:48:55 +0000214 Vector<SizedFile> SeedFiles;
215 for (auto &Dir : CorpusDirs)
216 GetSizedFilesFromDir(Dir, &SeedFiles);
217 std::sort(SeedFiles.begin(), SeedFiles.end());
kcc6526f1d2019-02-14 00:25:43 +0000218 Env.TempDir = TempPath(".dir");
219 RmDirRecursive(Env.TempDir); // in case there is a leftover from old runs.
220 MkDir(Env.TempDir);
kcc2e6ca5c2019-02-12 22:48:55 +0000221
kcc2e6ca5c2019-02-12 22:48:55 +0000222
kcc64bcb922019-02-13 04:04:45 +0000223 if (CorpusDirs.empty())
kcc6526f1d2019-02-14 00:25:43 +0000224 MkDir(Env.MainCorpusDir = DirPlusFile(Env.TempDir, "C"));
kcc64bcb922019-02-13 04:04:45 +0000225 else
226 Env.MainCorpusDir = CorpusDirs[0];
227
kcc6526f1d2019-02-14 00:25:43 +0000228 auto CFPath = DirPlusFile(Env.TempDir, "merge.txt");
229 CrashResistantMerge(Env.Args, {}, SeedFiles, &Env.Files, {}, &Env.Features,
kcc98a86242019-02-15 00:08:16 +0000230 {}, &Env.Cov,
kcc64bcb922019-02-13 04:04:45 +0000231 CFPath, false);
232 RemoveFile(CFPath);
kcc55e54ed2019-02-15 21:51:15 +0000233 Printf("INFO: -fork=%d: %zd seed inputs, starting to fuzz in %s\n", NumJobs,
234 Env.Files.size(), Env.TempDir.c_str());
kcc64bcb922019-02-13 04:04:45 +0000235
kcc2e6ca5c2019-02-12 22:48:55 +0000236 int ExitCode = 0;
kcc64bcb922019-02-13 04:04:45 +0000237
kcc6526f1d2019-02-14 00:25:43 +0000238 JobQueue FuzzQ, MergeQ;
239 std::atomic<bool> Stop(false);
kcc64bcb922019-02-13 04:04:45 +0000240
kcc6526f1d2019-02-14 00:25:43 +0000241 size_t JobId = 1;
242 Vector<std::thread> Threads;
243 for (int t = 0; t < NumJobs; t++) {
244 Threads.push_back(std::thread(WorkerThread, &Stop, &FuzzQ, &MergeQ));
245 FuzzQ.Push(Env.CreateNewJob(JobId++));
kcc2e6ca5c2019-02-12 22:48:55 +0000246 }
247
kcc55e54ed2019-02-15 21:51:15 +0000248 while (true) {
kcc6526f1d2019-02-14 00:25:43 +0000249 auto Job = MergeQ.Pop();
250 if (!Job) {
kcc55e54ed2019-02-15 21:51:15 +0000251 if (Stop)
252 break;
kcc6526f1d2019-02-14 00:25:43 +0000253 SleepSeconds(1);
254 continue;
255 }
256 ExitCode = Job->ExitCode;
257 if (ExitCode != Options.InterruptExitCode)
258 Env.RunOneMergeJob(Job);
259
260 // Continue if our crash is one of the ignorred ones.
261 if (Options.IgnoreTimeouts && ExitCode == Options.TimeoutExitCode)
kcc55e54ed2019-02-15 21:51:15 +0000262 Env.NumTimeouts++;
kcc6526f1d2019-02-14 00:25:43 +0000263 else if (Options.IgnoreOOMs && ExitCode == Options.OOMExitCode)
kcc55e54ed2019-02-15 21:51:15 +0000264 Env.NumOOMs++;
kcc6526f1d2019-02-14 00:25:43 +0000265 else if (ExitCode == Options.InterruptExitCode)
266 Stop = true;
267 else if (ExitCode != 0) {
kcc55e54ed2019-02-15 21:51:15 +0000268 Env.NumCrashes++;
269 if (Options.IgnoreCrashes) {
270 std::ifstream In(Job->LogPath);
271 std::string Line;
272 while (std::getline(In, Line, '\n'))
273 if (Line.find("ERROR:") != Line.npos)
274 Printf("%s\n", Line.c_str());
275 } else {
276 // And exit if we don't ignore this crash.
277 Printf("INFO: log from the inner process:\n%s",
278 FileToString(Job->LogPath).c_str());
279 Stop = true;
280 }
kcc6526f1d2019-02-14 00:25:43 +0000281 }
282 RemoveFile(Job->LogPath);
283 delete Job;
kcc55e54ed2019-02-15 21:51:15 +0000284
285 // Stop if we are over the time budget.
286 // This is not precise, since other threads are still running
287 // and we will wait while joining them.
288 // We also don't stop instantly: other jobs need to finish.
289 if (Options.MaxTotalTimeSec > 0 && !Stop &&
290 Env.secondsSinceProcessStartUp() >= (size_t)Options.MaxTotalTimeSec) {
291 Printf("INFO: fuzzed for %zd seconds, wrapping up soon\n",
292 Env.secondsSinceProcessStartUp());
293 Stop = true;
294 }
295
296 if (!Stop)
297 FuzzQ.Push(Env.CreateNewJob(JobId++));
kcc6526f1d2019-02-14 00:25:43 +0000298 }
299 Stop = true;
300
301 for (auto &T : Threads)
302 T.join();
303
304 RmDirRecursive(Env.TempDir);
kcc2e6ca5c2019-02-12 22:48:55 +0000305
306 // Use the exit code from the last child process.
kcc55e54ed2019-02-15 21:51:15 +0000307 Printf("INFO: exiting: %d time: %zds\n", ExitCode,
308 Env.secondsSinceProcessStartUp());
kcc2e6ca5c2019-02-12 22:48:55 +0000309 exit(ExitCode);
310}
311
312} // namespace fuzzer
313