forked from OSchip/llvm-project
[libFuzzer] introduce an experimental mode -fork=1, where fuzzing happens in a subprocess (still running multiple inputs per process), thus making the fuzzing more resilient to timeouts and OOMs. This is just a skeleton of the code, and some associated refactoring, not a fully working feature yet.
llvm-svn: 353570
This commit is contained in:
parent
3bf72d7d64
commit
f762a11544
|
@ -16,6 +16,7 @@
|
|||
#include "FuzzerMutate.h"
|
||||
#include "FuzzerRandom.h"
|
||||
#include "FuzzerTracePC.h"
|
||||
#include "FuzzerMerge.h"
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
|
@ -24,6 +25,7 @@
|
|||
#include <mutex>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <fstream>
|
||||
|
||||
// This function should be present in the libFuzzer so that the client
|
||||
// binary can test for its existence.
|
||||
|
@ -304,6 +306,11 @@ static std::string GetDedupTokenFromFile(const std::string &Path) {
|
|||
return S.substr(Beg, End - Beg);
|
||||
}
|
||||
|
||||
static std::string TempPath(const char *Extension) {
|
||||
return DirPlusFile(TmpDir(),
|
||||
"libFuzzerTemp." + std::to_string(GetPid()) + Extension);
|
||||
}
|
||||
|
||||
int CleanseCrashInput(const Vector<std::string> &Args,
|
||||
const FuzzingOptions &Options) {
|
||||
if (Inputs->size() != 1 || !Flags.exact_artifact_path) {
|
||||
|
@ -319,10 +326,8 @@ int CleanseCrashInput(const Vector<std::string> &Args,
|
|||
assert(Cmd.hasArgument(InputFilePath));
|
||||
Cmd.removeArgument(InputFilePath);
|
||||
|
||||
auto LogFilePath = DirPlusFile(
|
||||
TmpDir(), "libFuzzerTemp." + std::to_string(GetPid()) + ".txt");
|
||||
auto TmpFilePath = DirPlusFile(
|
||||
TmpDir(), "libFuzzerTemp." + std::to_string(GetPid()) + ".repro");
|
||||
auto LogFilePath = TempPath(".txt");
|
||||
auto TmpFilePath = TempPath(".repro");
|
||||
Cmd.addArgument(TmpFilePath);
|
||||
Cmd.setOutputFile(LogFilePath);
|
||||
Cmd.combineOutAndErr();
|
||||
|
@ -382,8 +387,7 @@ int MinimizeCrashInput(const Vector<std::string> &Args,
|
|||
BaseCmd.addFlag("max_total_time", "600");
|
||||
}
|
||||
|
||||
auto LogFilePath = DirPlusFile(
|
||||
TmpDir(), "libFuzzerTemp." + std::to_string(GetPid()) + ".txt");
|
||||
auto LogFilePath = TempPath(".txt");
|
||||
BaseCmd.setOutputFile(LogFilePath);
|
||||
BaseCmd.combineOutAndErr();
|
||||
|
||||
|
@ -467,6 +471,36 @@ int MinimizeCrashInputInternalStep(Fuzzer *F, InputCorpus *Corpus) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
// This is just a sceleton of an experimental -fork=1 feature.
|
||||
void FuzzWithFork(const FuzzingOptions &Options,
|
||||
const Vector<std::string> &Args,
|
||||
const Vector<std::string> &Corpora) {
|
||||
auto CFPath = TempPath(".fork");
|
||||
Printf("INFO: -fork=1: doing fuzzing in a separate process in order to "
|
||||
"be more resistant to crashes, timeouts, and OOMs\n");
|
||||
auto Files =
|
||||
CrashResistantMerge(Args, Corpora, CFPath, nullptr, nullptr);
|
||||
Printf("INFO: -fork=1: seed corpus analyzed, %zd seeds chosen, starting to "
|
||||
"fuzz in separate processes\n", Files.size());
|
||||
|
||||
Command Cmd(Args);
|
||||
Cmd.removeFlag("fork");
|
||||
if (Files.size() >= 2)
|
||||
Cmd.addFlag("seed_inputs",
|
||||
Files.back() + "," + Files[Files.size() - 2]);
|
||||
Cmd.addFlag("runs", "1000000");
|
||||
Cmd.addFlag("max_total_time", "30");
|
||||
for (size_t i = 0; i < 1000; i++) {
|
||||
Printf("RUN %s\n", Cmd.toString().c_str());
|
||||
int ExitCode = ExecuteCommand(Cmd);
|
||||
// TODO: sniff the crash, ignore OOMs and timeouts.
|
||||
if (ExitCode != 0) break;
|
||||
}
|
||||
|
||||
RemoveFile(CFPath);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
int AnalyzeDictionary(Fuzzer *F, const Vector<Unit>& Dict,
|
||||
UnitVector& Corpus) {
|
||||
Printf("Started dictionary minimization (up to %d tests)\n",
|
||||
|
@ -694,11 +728,25 @@ int FuzzerDriver(int *argc, char ***argv, UserCallback Callback) {
|
|||
exit(0);
|
||||
}
|
||||
|
||||
if (Flags.fork)
|
||||
FuzzWithFork(Options, Args, *Inputs);
|
||||
|
||||
if (Flags.merge) {
|
||||
F->CrashResistantMerge(Args, *Inputs,
|
||||
Flags.load_coverage_summary,
|
||||
Flags.save_coverage_summary,
|
||||
Flags.merge_control_file);
|
||||
if (Inputs->size() < 2) {
|
||||
Printf("INFO: Merge requires two or more corpus dirs\n");
|
||||
exit(0);
|
||||
}
|
||||
std::string CFPath =
|
||||
Flags.merge_control_file ? Flags.merge_control_file : TempPath(".txt");
|
||||
auto Files =
|
||||
CrashResistantMerge(Args, *Inputs, CFPath, Flags.load_coverage_summary,
|
||||
Flags.save_coverage_summary);
|
||||
for (auto &Path : Files)
|
||||
F->WriteToOutputCorpus(FileToVector(Path, Options.MaxLen));
|
||||
// We are done, delete the control file if it was a temporary one.
|
||||
if (!Flags.merge_control_file)
|
||||
RemoveFile(CFPath);
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
|
|
|
@ -41,6 +41,8 @@ FUZZER_FLAG_INT(timeout_exitcode, 77, "When libFuzzer reports a timeout "
|
|||
FUZZER_FLAG_INT(max_total_time, 0, "If positive, indicates the maximal total "
|
||||
"time in seconds to run the fuzzer.")
|
||||
FUZZER_FLAG_INT(help, 0, "Print help.")
|
||||
FUZZER_FLAG_INT(fork, 0, "Experimental mode where fuzzing happens "
|
||||
"in a subprocess")
|
||||
FUZZER_FLAG_INT(merge, 0, "If 1, the 2-nd, 3-rd, etc corpora will be "
|
||||
"merged into the 1-st corpus. Only interesting units will be taken. "
|
||||
"This flag can be used to minimize a corpus.")
|
||||
|
|
|
@ -73,11 +73,6 @@ public:
|
|||
|
||||
// Merge Corpora[1:] into Corpora[0].
|
||||
void Merge(const Vector<std::string> &Corpora);
|
||||
void CrashResistantMerge(const Vector<std::string> &Args,
|
||||
const Vector<std::string> &Corpora,
|
||||
const char *CoverageSummaryInputPathOrNull,
|
||||
const char *CoverageSummaryOutputPathOrNull,
|
||||
const char *MergeControlFilePathOrNull);
|
||||
void CrashResistantMergeInternalStep(const std::string &ControlFilePath);
|
||||
MutationDispatcher &GetMD() { return MD; }
|
||||
void PrintFinalStats();
|
||||
|
@ -91,19 +86,19 @@ public:
|
|||
bool DuringInitialCorpusExecution);
|
||||
|
||||
void HandleMalloc(size_t Size);
|
||||
static void MaybeExitGracefully();
|
||||
void WriteToOutputCorpus(const Unit &U);
|
||||
|
||||
private:
|
||||
void AlarmCallback();
|
||||
void CrashCallback();
|
||||
void ExitCallback();
|
||||
void MaybeExitGracefully();
|
||||
void CrashOnOverwrittenData();
|
||||
void InterruptCallback();
|
||||
void MutateAndTestOne();
|
||||
void PurgeAllocator();
|
||||
void ReportNewCoverage(InputInfo *II, const Unit &U);
|
||||
void PrintPulseAndReportSlowInput(const uint8_t *Data, size_t Size);
|
||||
void WriteToOutputCorpus(const Unit &U);
|
||||
void WriteUnitToFileWithPrefix(const Unit &U, const char *Prefix);
|
||||
void PrintStats(const char *Where, const char *End = "\n", size_t Units = 0);
|
||||
void PrintStatusForNewUnit(const Unit &U, const char *Text);
|
||||
|
|
|
@ -256,9 +256,9 @@ void Fuzzer::ExitCallback() {
|
|||
}
|
||||
|
||||
void Fuzzer::MaybeExitGracefully() {
|
||||
if (!GracefulExitRequested) return;
|
||||
if (!F->GracefulExitRequested) return;
|
||||
Printf("==%lu== INFO: libFuzzer: exiting as requested\n", GetPid());
|
||||
PrintFinalStats();
|
||||
F->PrintFinalStats();
|
||||
_Exit(0);
|
||||
}
|
||||
|
||||
|
|
|
@ -122,7 +122,7 @@ size_t Merger::ApproximateMemoryConsumption() const {
|
|||
|
||||
// Decides which files need to be merged (add thost to NewFiles).
|
||||
// Returns the number of new features added.
|
||||
size_t Merger::Merge(const Set<uint32_t> &InitialFeatures,
|
||||
size_t Merger::Merge(const Set<uint32_t> &InitialFeatures,
|
||||
Vector<std::string> *NewFiles) {
|
||||
NewFiles->clear();
|
||||
assert(NumFilesInFirstCorpus <= Files.size());
|
||||
|
@ -223,7 +223,7 @@ void Fuzzer::CrashResistantMergeInternalStep(const std::string &CFPath) {
|
|||
std::ofstream OF(CFPath, std::ofstream::out | std::ofstream::app);
|
||||
Set<size_t> AllFeatures;
|
||||
for (size_t i = M.FirstNotProcessedFile; i < M.Files.size(); i++) {
|
||||
MaybeExitGracefully();
|
||||
Fuzzer::MaybeExitGracefully();
|
||||
auto U = FileToVector(M.Files[i].Name);
|
||||
if (U.size() > MaxInputLen) {
|
||||
U.resize(MaxInputLen);
|
||||
|
@ -275,27 +275,18 @@ static void WriteNewControlFile(const std::string &CFPath,
|
|||
}
|
||||
|
||||
// Outer process. Does not call the target code and thus sohuld not fail.
|
||||
void Fuzzer::CrashResistantMerge(const Vector<std::string> &Args,
|
||||
const Vector<std::string> &Corpora,
|
||||
const char *CoverageSummaryInputPathOrNull,
|
||||
const char *CoverageSummaryOutputPathOrNull,
|
||||
const char *MergeControlFilePathOrNull) {
|
||||
if (Corpora.size() <= 1) {
|
||||
Printf("Merge requires two or more corpus dirs\n");
|
||||
return;
|
||||
}
|
||||
auto CFPath =
|
||||
MergeControlFilePathOrNull
|
||||
? MergeControlFilePathOrNull
|
||||
: DirPlusFile(TmpDir(),
|
||||
"libFuzzerTemp." + std::to_string(GetPid()) + ".txt");
|
||||
|
||||
Vector<std::string>
|
||||
CrashResistantMerge(const Vector<std::string> &Args,
|
||||
const Vector<std::string> &Corpora,
|
||||
const std::string &CFPath,
|
||||
const char *CoverageSummaryInputPathOrNull,
|
||||
const char *CoverageSummaryOutputPathOrNull) {
|
||||
size_t NumAttempts = 0;
|
||||
if (MergeControlFilePathOrNull && FileSize(MergeControlFilePathOrNull)) {
|
||||
if (FileSize(CFPath)) {
|
||||
Printf("MERGE-OUTER: non-empty control file provided: '%s'\n",
|
||||
MergeControlFilePathOrNull);
|
||||
CFPath.c_str());
|
||||
Merger M;
|
||||
std::ifstream IF(MergeControlFilePathOrNull);
|
||||
std::ifstream IF(CFPath);
|
||||
if (M.Parse(IF, /*ParseCoverage=*/false)) {
|
||||
Printf("MERGE-OUTER: control file ok, %zd files total,"
|
||||
" first not processed file %zd\n",
|
||||
|
@ -334,9 +325,10 @@ void Fuzzer::CrashResistantMerge(const Vector<std::string> &Args,
|
|||
// Every inner process should execute at least one input.
|
||||
Command BaseCmd(Args);
|
||||
BaseCmd.removeFlag("merge");
|
||||
BaseCmd.removeFlag("fork");
|
||||
bool Success = false;
|
||||
for (size_t Attempt = 1; Attempt <= NumAttempts; Attempt++) {
|
||||
MaybeExitGracefully();
|
||||
Fuzzer::MaybeExitGracefully();
|
||||
Printf("MERGE-OUTER: attempt %zd\n", Attempt);
|
||||
Command Cmd(BaseCmd);
|
||||
Cmd.addFlag("merge_control_file", CFPath);
|
||||
|
@ -368,7 +360,6 @@ void Fuzzer::CrashResistantMerge(const Vector<std::string> &Args,
|
|||
std::ofstream SummaryOut(CoverageSummaryOutputPathOrNull);
|
||||
M.PrintSummary(SummaryOut);
|
||||
}
|
||||
Vector<std::string> NewFiles;
|
||||
Set<uint32_t> InitialFeatures;
|
||||
if (CoverageSummaryInputPathOrNull) {
|
||||
std::ifstream SummaryIn(CoverageSummaryInputPathOrNull);
|
||||
|
@ -376,14 +367,11 @@ void Fuzzer::CrashResistantMerge(const Vector<std::string> &Args,
|
|||
Printf("MERGE-OUTER: coverage summary loaded from %s, %zd features found\n",
|
||||
CoverageSummaryInputPathOrNull, InitialFeatures.size());
|
||||
}
|
||||
Vector<std::string> NewFiles;
|
||||
size_t NumNewFeatures = M.Merge(InitialFeatures, &NewFiles);
|
||||
Printf("MERGE-OUTER: %zd new files with %zd new features added\n",
|
||||
NewFiles.size(), NumNewFeatures);
|
||||
for (auto &F: NewFiles)
|
||||
WriteToOutputCorpus(FileToVector(F, MaxInputLen));
|
||||
// We are done, delete the control file if it was a temporary one.
|
||||
if (!MergeControlFilePathOrNull)
|
||||
RemoveFile(CFPath);
|
||||
return NewFiles;
|
||||
}
|
||||
|
||||
} // namespace fuzzer
|
||||
|
|
|
@ -67,13 +67,17 @@ struct Merger {
|
|||
Set<uint32_t> ParseSummary(std::istream &IS);
|
||||
size_t Merge(const Set<uint32_t> &InitialFeatures,
|
||||
Vector<std::string> *NewFiles);
|
||||
size_t Merge(Vector<std::string> *NewFiles) {
|
||||
return Merge(Set<uint32_t>{}, NewFiles);
|
||||
}
|
||||
size_t ApproximateMemoryConsumption() const;
|
||||
Set<uint32_t> AllFeatures() const;
|
||||
};
|
||||
|
||||
Vector<std::string>
|
||||
CrashResistantMerge(const Vector<std::string> &Args,
|
||||
const Vector<std::string> &Corpora,
|
||||
const std::string &CFPath,
|
||||
const char *CoverageSummaryInputPathOrNull,
|
||||
const char *CoverageSummaryOutputPathOrNull);
|
||||
|
||||
} // namespace fuzzer
|
||||
|
||||
#endif // LLVM_FUZZER_MERGE_H
|
||||
|
|
|
@ -647,7 +647,7 @@ static void Merge(const std::string &Input,
|
|||
EXPECT_TRUE(M.Parse(Input, true));
|
||||
std::stringstream SS;
|
||||
M.PrintSummary(SS);
|
||||
EXPECT_EQ(NumNewFeatures, M.Merge(&NewFiles));
|
||||
EXPECT_EQ(NumNewFeatures, M.Merge({}, &NewFiles));
|
||||
EXPECT_EQ(M.AllFeatures(), M.ParseSummary(SS));
|
||||
EQ(NewFiles, Result);
|
||||
}
|
||||
|
@ -705,7 +705,7 @@ TEST(Merge, Good) {
|
|||
EQ(M.Files[0].Features, {1, 2, 3});
|
||||
EQ(M.Files[1].Features, {4, 5, 6});
|
||||
EQ(M.Files[2].Features, {1, 3, 6});
|
||||
EXPECT_EQ(0U, M.Merge(&NewFiles));
|
||||
EXPECT_EQ(0U, M.Merge({}, &NewFiles));
|
||||
EQ(NewFiles, {});
|
||||
|
||||
EXPECT_TRUE(M.Parse("3\n1\nA\nB\nC\n"
|
||||
|
@ -716,7 +716,7 @@ TEST(Merge, Good) {
|
|||
EQ(M.Files[0].Features, {1, 2, 3});
|
||||
EQ(M.Files[1].Features, {4, 5, 6});
|
||||
EQ(M.Files[2].Features, {1, 3, 6});
|
||||
EXPECT_EQ(3U, M.Merge(&NewFiles));
|
||||
EXPECT_EQ(3U, M.Merge({}, &NewFiles));
|
||||
EQ(NewFiles, {"B"});
|
||||
|
||||
// Same as the above, but with InitialFeatures.
|
||||
|
|
Loading…
Reference in New Issue