blob: 5126fe74c4e0b30ad75a924abd89f16e3a00f7a7 [file] [log] [blame]
//===- 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