diff options
-rw-r--r-- | lib/fuzzer/FuzzerFlags.def | 2 | ||||
-rw-r--r-- | lib/fuzzer/FuzzerMerge.cpp | 84 | ||||
-rw-r--r-- | test/fuzzer/merge-control-file.test | 47 |
3 files changed, 108 insertions, 25 deletions
diff --git a/lib/fuzzer/FuzzerFlags.def b/lib/fuzzer/FuzzerFlags.def index db8c9eda0..9b2115779 100644 --- a/lib/fuzzer/FuzzerFlags.def +++ b/lib/fuzzer/FuzzerFlags.def @@ -41,6 +41,8 @@ FUZZER_FLAG_INT(merge, 0, "If 1, the 2-nd, 3-rd, etc corpora will be " FUZZER_FLAG_STRING(merge_inner, "internal flag") FUZZER_FLAG_STRING(merge_control_file, "Specify a control file used for the merge proccess. " + "If a merge process gets killed it tries to leave this file " + "in a state suitable for resuming the merge. " "By default a temporary file will be used.") FUZZER_FLAG_STRING(save_coverage_summary, "Experimental:" " save coverage summary to a given file." diff --git a/lib/fuzzer/FuzzerMerge.cpp b/lib/fuzzer/FuzzerMerge.cpp index e6e935dfc..89b4821d0 100644 --- a/lib/fuzzer/FuzzerMerge.cpp +++ b/lib/fuzzer/FuzzerMerge.cpp @@ -256,6 +256,22 @@ void Fuzzer::CrashResistantMergeInternalStep(const std::string &CFPath) { } } +static void WriteNewControlFile(const std::string &CFPath, + const Vector<SizedFile> &AllFiles, + size_t NumFilesInFirstCorpus) { + RemoveFile(CFPath); + std::ofstream ControlFile(CFPath); + ControlFile << AllFiles.size() << "\n"; + ControlFile << NumFilesInFirstCorpus << "\n"; + for (auto &SF: AllFiles) + ControlFile << SF.File << "\n"; + if (!ControlFile) { + Printf("MERGE-OUTER: failed to write to the control file: %s\n", + CFPath.c_str()); + exit(1); + } +} + // 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, @@ -266,46 +282,64 @@ void Fuzzer::CrashResistantMerge(const Vector<std::string> &Args, Printf("Merge requires two or more corpus dirs\n"); return; } - Vector<SizedFile> AllFiles; - GetSizedFilesFromDir(Corpora[0], &AllFiles); - size_t NumFilesInFirstCorpus = AllFiles.size(); - std::sort(AllFiles.begin(), AllFiles.end()); - for (size_t i = 1; i < Corpora.size(); i++) - GetSizedFilesFromDir(Corpora[i], &AllFiles); - std::sort(AllFiles.begin() + NumFilesInFirstCorpus, AllFiles.end()); - Printf("MERGE-OUTER: %zd files, %zd in the initial corpus\n", - AllFiles.size(), NumFilesInFirstCorpus); auto CFPath = MergeControlFilePathOrNull ? MergeControlFilePathOrNull : DirPlusFile(TmpDir(), "libFuzzerTemp." + std::to_string(GetPid()) + ".txt"); - // Write the control file. - RemoveFile(CFPath); - std::ofstream ControlFile(CFPath); - ControlFile << AllFiles.size() << "\n"; - ControlFile << NumFilesInFirstCorpus << "\n"; - for (auto &SF: AllFiles) - ControlFile << SF.File << "\n"; - if (!ControlFile) { - Printf("MERGE-OUTER: failed to write to the control file: %s\n", - CFPath.c_str()); - exit(1); + + size_t NumAttempts = 0; + if (MergeControlFilePathOrNull && FileSize(MergeControlFilePathOrNull)) { + Printf("MERGE-OUTER: non-empty control file provided: '%s'\n", + MergeControlFilePathOrNull); + Merger M; + std::ifstream IF(MergeControlFilePathOrNull); + if (M.Parse(IF, /*ParseCoverage=*/false)) { + Printf("MERGE-OUTER: control file ok, %zd files total," + " first not processed file %zd\n", + M.Files.size(), M.FirstNotProcessedFile); + if (!M.LastFailure.empty()) + Printf("MERGE-OUTER: '%s' will be skipped as unlucky " + "(merge has stumbled on it the last time)\n", + M.LastFailure.c_str()); + if (M.FirstNotProcessedFile >= M.Files.size()) { + Printf("MERGE-OUTER: nothing to do, merge has been completed before\n"); + exit(0); + } + + NumAttempts = M.Files.size() - M.FirstNotProcessedFile; + } else { + Printf("MERGE-OUTER: bad control file, will overwrite it\n"); + } + } + + if (!NumAttempts) { + // The supplied control file is empty or bad, create a fresh one. + Vector<SizedFile> AllFiles; + GetSizedFilesFromDir(Corpora[0], &AllFiles); + size_t NumFilesInFirstCorpus = AllFiles.size(); + std::sort(AllFiles.begin(), AllFiles.end()); + for (size_t i = 1; i < Corpora.size(); i++) + GetSizedFilesFromDir(Corpora[i], &AllFiles); + std::sort(AllFiles.begin() + NumFilesInFirstCorpus, AllFiles.end()); + Printf("MERGE-OUTER: %zd files, %zd in the initial corpus\n", + AllFiles.size(), NumFilesInFirstCorpus); + WriteNewControlFile(CFPath, AllFiles, NumFilesInFirstCorpus); + NumAttempts = AllFiles.size(); } - ControlFile.close(); - // Execute the inner process untill it passes. + // Execute the inner process until it passes. // Every inner process should execute at least one input. auto BaseCmd = SplitBefore("-ignore_remaining_args=1", CloneArgsWithoutX(Args, "merge")); bool Success = false; - for (size_t i = 1; i <= AllFiles.size(); i++) { - Printf("MERGE-OUTER: attempt %zd\n", i); + for (size_t Attempt = 1; Attempt <= NumAttempts; Attempt++) { + Printf("MERGE-OUTER: attempt %zd\n", Attempt); auto ExitCode = ExecuteCommand(BaseCmd.first + " -merge_control_file=" + CFPath + " -merge_inner=1 " + BaseCmd.second); if (!ExitCode) { - Printf("MERGE-OUTER: succesfull in %zd attempt(s)\n", i); + Printf("MERGE-OUTER: succesfull in %zd attempt(s)\n", Attempt); Success = true; break; } diff --git a/test/fuzzer/merge-control-file.test b/test/fuzzer/merge-control-file.test new file mode 100644 index 000000000..fef6d0f10 --- /dev/null +++ b/test/fuzzer/merge-control-file.test @@ -0,0 +1,47 @@ +RUN: mkdir -p %t +RUN: %cpp_compiler %S/FullCoverageSetTest.cpp -o %t/T + +RUN: rm -rf %t/T0 %t/T1 %t/T2 +RUN: mkdir -p %t/T0 %t/T1 %t/T2 +RUN: echo F..... > %t/T0/1 +RUN: echo .U.... > %t/T0/2 +RUN: echo ..Z... > %t/T0/3 + +# Test what happens if the control file is junk. + +RUN: echo JUNK > %t/MCF +RUN: not %t/T -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 %t/T -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 + + +# Check valid control files + +RUN: rm -f %t/T1/*; cp %t/T0/* %t/T1 +RUN: echo 3 > %t/MCF; echo 0 >> %t/MCF; echo %t/T1/1 >> %t/MCF; echo %t/T1/2 >> %t/MCF; echo %t/T1/3 >> %t/MCF +RUN: %t/T -merge=1 %t/T1 %t/T2 -merge_control_file=%t/MCF 2>&1 | FileCheck %s --check-prefix=OK_0 +OK_0: MERGE-OUTER: control file ok, 3 files total, first not processed file 0 +OK_0: MERGE-OUTER: 3 new files with {{.*}} new features added + +RUN: rm -f %t/T1/*; cp %t/T0/* %t/T1 +RUN: echo 3 > %t/MCF; echo 0 >> %t/MCF; echo %t/T1/1 >> %t/MCF; echo %t/T1/2 >> %t/MCF; echo %t/T1/3 >> %t/MCF +RUN: echo STARTED 0 1 >> %t/MCF +RUN: echo DONE 0 11 >> %t/MCF +RUN: echo STARTED 1 2 >> %t/MCF +RUN: echo DONE 1 12 >> %t/MCF +RUN: %t/T -merge=1 %t/T1 %t/T2 -merge_control_file=%t/MCF 2>&1 | FileCheck %s --check-prefix=OK_2 +OK_2: MERGE-OUTER: control file ok, 3 files total, first not processed file 2 +OK_2: MERGE-OUTER: 3 new files with {{.*}} new features added + +RUN: rm -f %t/T1/*; cp %t/T0/* %t/T1 +RUN: echo 3 > %t/MCF; echo 0 >> %t/MCF; echo %t/T1/1 >> %t/MCF; echo %t/T1/2 >> %t/MCF; echo %t/T1/3 >> %t/MCF +RUN: echo STARTED 0 1 >> %t/MCF +RUN: echo DONE 0 11 >> %t/MCF +RUN: echo STARTED 1 2 >> %t/MCF +RUN: echo DONE 1 12 >> %t/MCF +RUN: echo STARTED 2 2 >> %t/MCF +RUN: echo DONE 2 13 >> %t/MCF +RUN: %t/T -merge=1 %t/T1 %t/T2 -merge_control_file=%t/MCF 2>&1 | FileCheck %s --check-prefix=OK_3 +OK_3: MERGE-OUTER: nothing to do, merge has been completed before |