diff --git a/cmdline.c b/cmdline.c index fa66fb60b..4ef81298c 100644 --- a/cmdline.c +++ b/cmdline.c @@ -322,6 +322,7 @@ bool cmdlineParse(int argc, char* argv[], honggfuzz_t* hfuzz) { .crashDir = NULL, .covDirNew = NULL, .saveUnique = true, + .saveSmaller = false, .dynfileqMaxSz = 0U, .dynfileqCnt = 0U, .dynfileqCurrent = NULL, @@ -508,6 +509,7 @@ bool cmdlineParse(int argc, char* argv[], honggfuzz_t* hfuzz) { { { "clear_env", no_argument, NULL, 0x108 }, "Clear all environment variables before executing the binary" }, { { "env", required_argument, NULL, 'E' }, "Pass this environment variable, can be used multiple times" }, { { "save_all", no_argument, NULL, 'u' }, "Save all test-cases (not only the unique ones) by appending the current time-stamp to the filenames" }, + { { "save_smaller", no_argument, NULL, 'U' }, "Save smaller test-cases, renaming first filename with .orig suffix" }, { { "tmout_sigvtalrm", no_argument, NULL, 'T' }, "Treat time-outs as crashes - use SIGVTALRM to kill timeouting processes (default: use SIGKILL)" }, { { "sanitizers", no_argument, NULL, 'S' }, "** DEPRECATED ** Enable sanitizers settings (default: false)" }, { { "sanitizers_del_report", required_argument, NULL, 0x10F }, "Delete sanitizer report after use (default: false)" }, @@ -555,7 +557,7 @@ bool cmdlineParse(int argc, char* argv[], honggfuzz_t* hfuzz) { int opt_index = 0; for (;;) { int c = getopt_long( - argc, argv, "-?hQvVsuPxf:i:o:dqe:W:r:c:F:t:R:n:N:l:p:g:E:w:B:zMTS", opts, &opt_index); + argc, argv, "-?hQvVsuUPxf:i:o:dqe:W:r:c:F:t:R:n:N:l:p:g:E:w:B:zMTS", opts, &opt_index); if (c < 0) { break; } @@ -587,6 +589,9 @@ bool cmdlineParse(int argc, char* argv[], honggfuzz_t* hfuzz) { case 'u': hfuzz->io.saveUnique = false; break; + case 'U': + hfuzz->io.saveSmaller = true; + break; case 'l': logfile = optarg; break; diff --git a/docs/USAGE.md b/docs/USAGE.md index d925d261b..f037329fe 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -179,6 +179,8 @@ Options: Pass this environment variable, can be used multiple times --save_all|-u Save all test-cases (not only the unique ones) by appending the current time-stamp to the filenames + --save_smaller|-U + Save smaller test-cases, renaming first found with .orig suffix --tmout_sigvtalrm|-T Use SIGVTALRM to kill timeouting processes (default: use SIGKILL) --sanitizers|-S diff --git a/honggfuzz.h b/honggfuzz.h index 9d07fdf42..963fd0bde 100644 --- a/honggfuzz.h +++ b/honggfuzz.h @@ -208,6 +208,7 @@ typedef struct { const char* crashDir; const char* covDirNew; bool saveUnique; + bool saveSmaller; size_t dynfileqMaxSz; size_t dynfileqCnt; dynfile_t* dynfileqCurrent; diff --git a/linux/trace.c b/linux/trace.c index 2ea20fd54..920053c51 100644 --- a/linux/trace.c +++ b/linux/trace.c @@ -703,10 +703,39 @@ static void arch_traceSaveData(run_t* run, pid_t pid) { } if (files_exists(run->crashFileName)) { - LOG_I("Crash (dup): '%s' already exists, skipping", run->crashFileName); - /* Clear filename so that verifier can understand we hit a duplicate */ - memset(run->crashFileName, 0, sizeof(run->crashFileName)); - return; + if (run->global->io.saveSmaller) { + /* + * If the new run produces a smaller file than exists already, we + * will replace it. + * + * If this is the second test case, we save the first with .orig + * suffix before overwriting. + */ + struct stat st; + char origFile[PATH_MAX]; + if (stat(run->crashFileName, &st) == -1) { + LOG_W("Couldn't stat() the '%s' file", run->crashFileName); + } else if (st.st_size <= (off_t)run->dynfile->size) { + LOG_I("Crash (dup): '%s' exists and is smaller, skipping", run->crashFileName); + /* Clear filename so that verifier can understand we hit a duplicate */ + memset(run->crashFileName, 0, sizeof(run->crashFileName)); + return; + } else { + /* we have a new champion */ + LOG_I("Crash: overwriting '%s' (old %zu bytes, new %zu bytes)", + run->crashFileName, (size_t)st.st_size, (size_t)run->dynfile->size); + } + + snprintf(origFile, sizeof(origFile), "%s.orig", run->crashFileName); + if (! files_exists(origFile)) { + rename(run->crashFileName, origFile); + } + } else { + LOG_I("Crash (dup): '%s' already exists, skipping", run->crashFileName); + /* Clear filename so that verifier can understand we hit a duplicate */ + memset(run->crashFileName, 0, sizeof(run->crashFileName)); + return; + } } if (!files_writeBufToFile(run->crashFileName, run->dynfile->data, run->dynfile->size,