summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--lib/fuzzer/FuzzerFlags.def2
-rw-r--r--lib/fuzzer/FuzzerMerge.cpp84
-rw-r--r--test/fuzzer/merge-control-file.test47
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