diff options
author | Kostya Serebryany <kcc@google.com> | 2019-02-13 04:04:45 +0000 |
---|---|---|
committer | Kostya Serebryany <kcc@google.com> | 2019-02-13 04:04:45 +0000 |
commit | 92f7768ce940f6437b32ecc0985a1446cd040f7a (patch) | |
tree | 428a1721cd313ed4bc5a2358e7edb0212c4dd81c | |
parent | 2e6c8b4ca404aa9db6fc658c0feea74cbe105781 (diff) | |
download | compiler-rt-92f7768ce940f6437b32ecc0985a1446cd040f7a.tar.gz |
[libFuzzer] a bit of refactoring of the fork mode
git-svn-id: https://llvm.org/svn/llvm-project/compiler-rt/trunk@353910 91177308-0d34-0410-b5e6-96231b3b80d8
-rw-r--r-- | lib/fuzzer/FuzzerFork.cpp | 174 | ||||
-rw-r--r-- | lib/fuzzer/FuzzerIO.cpp | 10 | ||||
-rw-r--r-- | lib/fuzzer/FuzzerIO.h | 11 | ||||
-rw-r--r-- | lib/fuzzer/FuzzerIOPosix.cpp | 22 | ||||
-rw-r--r-- | lib/fuzzer/FuzzerIOWindows.cpp | 9 | ||||
-rw-r--r-- | lib/fuzzer/FuzzerMerge.cpp | 1 | ||||
-rw-r--r-- | test/fuzzer/merge-control-file.test | 5 |
7 files changed, 156 insertions, 76 deletions
diff --git a/lib/fuzzer/FuzzerFork.cpp b/lib/fuzzer/FuzzerFork.cpp index bb0ea2a3c..5126fe74c 100644 --- a/lib/fuzzer/FuzzerFork.cpp +++ b/lib/fuzzer/FuzzerFork.cpp @@ -5,7 +5,7 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// -// Spawn an orchestrate separate fuzzing processes. +// Spawn and orchestrate separate fuzzing processes. //===----------------------------------------------------------------------===// #include "FuzzerCommand.h" @@ -17,6 +17,65 @@ 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, @@ -24,99 +83,78 @@ void FuzzWithFork(Random &Rand, const FuzzingOptions &Options, 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 CFPath = TempPath(".fork"); - auto LogPath = TempPath(".log"); - auto TempDir = TempPath(".scratch_dir"); - std::string MainCorpusDir; + 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(MainCorpusDir = TempPath(".corpus_dir")); + MkDir(Env.MainCorpusDir = DirPlusFile(TempDir, "C")); else - MainCorpusDir = CorpusDirs[0]; - MkDir(TempDir); + Env.MainCorpusDir = CorpusDirs[0]; - Vector<std::string> Files; - Set<uint32_t> Features; - if (!SeedFiles.empty()) { - CrashResistantMerge(Args, {}, SeedFiles, &Files, {}, &Features, CFPath, - false); - RemoveFile(CFPath); - } + 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", - Files.size(), TempDir.c_str()); + Env.Files.size(), TempDir.c_str()); - Command BaseCmd(Args); + Command BaseCmd(*Env.Args); BaseCmd.removeFlag("fork"); for (auto &C : CorpusDirs) // Remove all corpora from the args. BaseCmd.removeArgument(C); - BaseCmd.addArgument(TempDir); - BaseCmd.addFlag("len_control", "0"); // len_control is bad for short runs. BaseCmd.addFlag("reload", "0"); // working in an isolated dir, no reload. int ExitCode = 0; - int max_total_time = 1; - for (size_t i = 0; i < 1000000; i++) { + + + for (size_t i = 1; i < 1000000; i++) { // TODO: take new files from disk e.g. those generated by another process. - Command Cmd(BaseCmd); - if (size_t CorpusSubsetSize = std::min(Files.size(), (size_t)10)) { - std::string Seeds; - for (size_t i = 0; i < CorpusSubsetSize; i++) { - if (i) Seeds += ","; - Seeds += Files[Rand.SkewTowardsLast(Files.size())]; - } - Cmd.addFlag("seed_inputs", Seeds); - } - if (Options.MaxTotalTimeSec > max_total_time) - max_total_time++; - if (!Cmd.hasFlag("max_total_time")) - Cmd.addFlag("max_total_time", std::to_string(max_total_time)); - Cmd.setOutputFile(LogPath); - Cmd.combineOutAndErr(); - RmFilesInDir(TempDir); - ExitCode = ExecuteCommand(Cmd); - // Printf("done [%d] %s\n", ExitCode, Cmd.toString().c_str()); - if (ExitCode == Options.InterruptExitCode) + + 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; - Vector<SizedFile> TempFiles; - Vector<std::string>FilesToAdd; - Set<uint32_t> NewFeatures; - GetSizedFilesFromDir(TempDir, &TempFiles); - if (!TempFiles.empty()) - CrashResistantMerge(Args, {}, TempFiles, &FilesToAdd, Features, - &NewFeatures, CFPath, false); - RemoveFile(CFPath); - for (auto &Path : FilesToAdd) { - auto U = FileToVector(Path); - auto NewPath = DirPlusFile(MainCorpusDir, Hash(U)); - WriteToFile(U, NewPath); - Files.push_back(NewPath); - } - 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(), - Features.size()); + + RunOneMergeJob(&Env, &Job); + // Continue if our crash is one of the ignorred ones. - if (Options.IgnoreTimeouts && ExitCode == Options.TimeoutExitCode) + if (Options.IgnoreTimeouts && Job.ExitCode == Options.TimeoutExitCode) continue; - if (Options.IgnoreOOMs && ExitCode == Options.OOMExitCode) + if (Options.IgnoreOOMs && Job.ExitCode == Options.OOMExitCode) continue; // And exit if we don't ignore this crash. - if (ExitCode != 0) { + if (Job.ExitCode != 0) { Printf("INFO: log from the inner process:\n%s", FileToString(LogPath).c_str()); + ExitCode = Job.ExitCode; break; } } - RmFilesInDir(TempDir); - RmDir(TempDir); - - if (CorpusDirs.empty()) { - RmFilesInDir(MainCorpusDir); - RmDir(MainCorpusDir); - } + RmDirRecursive(TempDir); // Use the exit code from the last child process. Printf("Fork: exiting: %d\n", ExitCode); diff --git a/lib/fuzzer/FuzzerIO.cpp b/lib/fuzzer/FuzzerIO.cpp index 91e1d2080..33d6568c9 100644 --- a/lib/fuzzer/FuzzerIO.cpp +++ b/lib/fuzzer/FuzzerIO.cpp @@ -135,11 +135,11 @@ void VPrintf(bool Verbose, const char *Fmt, ...) { fflush(OutputFile); } -void RmFilesInDir(const std::string &Path) { - Vector<std::string> Files; - ListFilesInDirRecursive(Path, 0, &Files, /*TopDir*/true); - for (auto &F : Files) - RemoveFile(F); +void RmDirRecursive(const std::string &Dir) { + IterateDirRecurisve( + Dir, [](const std::string &Path) {}, + [](const std::string &Path) { RmDir(Path); }, + [](const std::string &Path) { RemoveFile(Path); }); } std::string TempPath(const char *Extension) { diff --git a/lib/fuzzer/FuzzerIO.h b/lib/fuzzer/FuzzerIO.h index 9d849bed6..588cf9379 100644 --- a/lib/fuzzer/FuzzerIO.h +++ b/lib/fuzzer/FuzzerIO.h @@ -60,6 +60,16 @@ size_t FileSize(const std::string &Path); void ListFilesInDirRecursive(const std::string &Dir, long *Epoch, Vector<std::string> *V, bool TopDir); +void RmDirRecursive(const std::string &Dir); + +// Iterate files and dirs inside Dir, recursively. +// Call DirPreCallback/DirPostCallback on dirs before/after +// calling FileCallback on files. +void IterateDirRecurisve(const std::string &Dir, + void (*DirPreCallback)(const std::string &Dir), + void (*DirPostCallback)(const std::string &Dir), + void (*FileCallback)(const std::string &Dir)); + struct SizedFile { std::string File; size_t Size; @@ -86,7 +96,6 @@ intptr_t GetHandleFromFd(int fd); void MkDir(const std::string &Path); void RmDir(const std::string &Path); -void RmFilesInDir(const std::string &Path); } // namespace fuzzer diff --git a/lib/fuzzer/FuzzerIOPosix.cpp b/lib/fuzzer/FuzzerIOPosix.cpp index 54c511ed0..93eaad6c9 100644 --- a/lib/fuzzer/FuzzerIOPosix.cpp +++ b/lib/fuzzer/FuzzerIOPosix.cpp @@ -78,6 +78,28 @@ void ListFilesInDirRecursive(const std::string &Dir, long *Epoch, *Epoch = E; } + +void IterateDirRecurisve(const std::string &Dir, + void (*DirPreCallback)(const std::string &Dir), + void (*DirPostCallback)(const std::string &Dir), + void (*FileCallback)(const std::string &Dir)) { + DirPreCallback(Dir); + DIR *D = opendir(Dir.c_str()); + if (!D) return; + while (auto E = readdir(D)) { + std::string Path = DirPlusFile(Dir, E->d_name); + if (E->d_type == DT_REG || E->d_type == DT_LNK || + (E->d_type == DT_UNKNOWN && IsFile(Path))) + FileCallback(Path); + else if ((E->d_type == DT_DIR || + (E->d_type == DT_UNKNOWN && IsDirectory(Path))) && + *E->d_name != '.') + IterateDirRecurisve(Path, DirPreCallback, DirPostCallback, FileCallback); + } + closedir(D); + DirPostCallback(Dir); +} + char GetSeparator() { return '/'; } diff --git a/lib/fuzzer/FuzzerIOWindows.cpp b/lib/fuzzer/FuzzerIOWindows.cpp index bb33343b7..00256ca25 100644 --- a/lib/fuzzer/FuzzerIOWindows.cpp +++ b/lib/fuzzer/FuzzerIOWindows.cpp @@ -140,6 +140,15 @@ void ListFilesInDirRecursive(const std::string &Dir, long *Epoch, *Epoch = E; } + +void IterateDirRecurisve(const std::string &Dir, + void (*DirPreCallback)(const std::string &Dir), + void (*DirPostCallback)(const std::string &Dir), + void (*FileCallback)(const std::string &Dir)) { + // Unimplemented. + // TODO: implement, and then implement ListFilesInDirRecursive via this one. +} + char GetSeparator() { return '\\'; } diff --git a/lib/fuzzer/FuzzerMerge.cpp b/lib/fuzzer/FuzzerMerge.cpp index 9a86512df..556a231f1 100644 --- a/lib/fuzzer/FuzzerMerge.cpp +++ b/lib/fuzzer/FuzzerMerge.cpp @@ -261,6 +261,7 @@ void CrashResistantMerge(const Vector<std::string> &Args, const Set<uint32_t> &InitialFeatures, Set<uint32_t> *NewFeatures, const std::string &CFPath, bool V /*Verbose*/) { + if (NewCorpus.empty() && OldCorpus.empty()) return; // Nothing to merge. size_t NumAttempts = 0; if (FileSize(CFPath)) { VPrintf(V, "MERGE-OUTER: non-empty control file provided: '%s'\n", diff --git a/test/fuzzer/merge-control-file.test b/test/fuzzer/merge-control-file.test index e7cb0eabf..0fed37565 100644 --- a/test/fuzzer/merge-control-file.test +++ b/test/fuzzer/merge-control-file.test @@ -12,10 +12,11 @@ RUN: echo ..Z... > %t/T0/3 # Test what happens if the control file is junk. +RUN: rm -f %t/T1/*; cp %t/T0/* %t/T1 RUN: echo JUNK > %t/MCF -RUN: not %run %t/T.exe -merge=1 %t/T1 %t/T2 -merge_control_file=%t/MCF 2>&1 | FileCheck %s --check-prefix=JUNK +RUN: %run %t/T.exe -merge=1 %t/T1 %t/T2 -merge_control_file=%t/MCF 2>&1 | FileCheck %s --check-prefix=JUNK RUN: echo 3 > %t/MCF; echo 0 >> %t/MCF; echo %t/T1/1 >> %t/MCF -RUN: not %run %t/T.exe -merge=1 %t/T1 %t/T2 -merge_control_file=%t/MCF 2>&1 | FileCheck %s --check-prefix=JUNK +RUN: %run %t/T.exe -merge=1 %t/T1 %t/T2 -merge_control_file=%t/MCF 2>&1 | FileCheck %s --check-prefix=JUNK JUNK: MERGE-OUTER: non-empty control file provided: {{.*}}MCF JUNK: MERGE-OUTER: bad control file, will overwrite it |