From 1c95804985732f36739da423472e99dbe320e412 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Ulf=20Karg=C3=A9n?= <ulf.kargen@liu.se>
Date: Tue, 14 Nov 2023 20:46:30 +0100
Subject: [PATCH] implement sync delay feature

---
 include/afl-fuzz.h   |  3 ++-
 include/config.h     |  5 +++++
 include/envs.h       |  1 +
 src/afl-fuzz-init.c  | 22 +++++++++++++++++++++
 src/afl-fuzz-one.c   |  2 ++
 src/afl-fuzz-queue.c | 14 ++++++++++++++
 src/afl-fuzz-run.c   | 46 +++++++++++++++++++++++++++++++++++++++++++-
 src/afl-fuzz-state.c |  7 +++++++
 src/afl-fuzz.c       |  3 ++-
 9 files changed, 100 insertions(+), 3 deletions(-)

diff --git a/include/afl-fuzz.h b/include/afl-fuzz.h
index 8b6502b44..f7d564b6c 100644
--- a/include/afl-fuzz.h
+++ b/include/afl-fuzz.h
@@ -399,7 +399,7 @@ typedef struct afl_env_vars {
       afl_cycle_schedules, afl_expand_havoc, afl_statsd, afl_cmplog_only_new,
       afl_exit_on_seed_issues, afl_try_affinity, afl_ignore_problems,
       afl_keep_timeouts, afl_no_crash_readme, afl_ignore_timeouts,
-      afl_no_startup_calibration, afl_no_warn_instability;
+      afl_no_startup_calibration, afl_no_warn_instability, afl_delay_sync;
 
   u8 *afl_tmpdir, *afl_custom_mutator_library, *afl_python_module, *afl_path,
       *afl_hang_tmout, *afl_forksrv_init_tmout, *afl_preload,
@@ -1088,6 +1088,7 @@ void        deinit_py(void *);
 /* Queue */
 
 void mark_as_det_done(afl_state_t *, struct queue_entry *);
+void mark_as_fuzzed(afl_state_t *, struct queue_entry *);
 void mark_as_variable(afl_state_t *, struct queue_entry *);
 void mark_as_redundant(afl_state_t *, struct queue_entry *, u8);
 void add_to_queue(afl_state_t *, u8 *, u32, u8);
diff --git a/include/config.h b/include/config.h
index b6249a0fc..1360ba256 100644
--- a/include/config.h
+++ b/include/config.h
@@ -309,6 +309,11 @@
 
 #define SYNC_TIME (30 * 60 * 1000)
 
+/* Approximate number of queue cycles to delay syncing of new discoveries
+   when AFL_DELAY_SYNC is in effect. */
+
+#define SYNC_DELAY_CYCLES 3
+
 /* Output directory reuse grace period (minutes): */
 
 #define OUTPUT_GRACE 25
diff --git a/include/envs.h b/include/envs.h
index 066921b9f..6f5576f4a 100644
--- a/include/envs.h
+++ b/include/envs.h
@@ -221,6 +221,7 @@ static char *afl_environment_variables[] = {
     "AFL_STATSD_PORT",
     "AFL_STATSD_TAGS_FLAVOR",
     "AFL_SYNC_TIME",
+    "AFL_DELAY_SYNC",
     "AFL_TESTCACHE_SIZE",
     "AFL_TESTCACHE_ENTRIES",
     "AFL_TMIN_EXACT",
diff --git a/src/afl-fuzz-init.c b/src/afl-fuzz-init.c
index 01d1e82ec..d600fff2c 100644
--- a/src/afl-fuzz-init.c
+++ b/src/afl-fuzz-init.c
@@ -928,6 +928,8 @@ void perform_dry_run(afl_state_t *afl) {
             --afl->pending_not_fuzzed;
             --afl->active_items;
 
+            mark_as_fuzzed(afl, q);
+
           }
 
           break;
@@ -1059,6 +1061,8 @@ void perform_dry_run(afl_state_t *afl) {
           --afl->pending_not_fuzzed;
           --afl->active_items;
 
+          mark_as_fuzzed(afl, q);
+
         }
 
         q->disabled = 1;
@@ -1178,6 +1182,8 @@ void perform_dry_run(afl_state_t *afl) {
             --afl->pending_not_fuzzed;
             --afl->active_items;
 
+            mark_as_fuzzed(afl, p);
+
           }
 
           p->disabled = 1;
@@ -1191,6 +1197,8 @@ void perform_dry_run(afl_state_t *afl) {
             --afl->pending_not_fuzzed;
             --afl->active_items;
 
+            mark_as_fuzzed(afl, q);
+
           }
 
           q->disabled = 1;
@@ -1556,6 +1564,10 @@ void nuke_resume_dir(afl_state_t *afl) {
   if (delete_files(fn, CASE_PREFIX)) { goto dir_cleanup_failed; }
   ck_free(fn);
 
+  fn = alloc_printf("%s/_resume/.state/was_fuzzed", afl->out_dir);
+  if (delete_files(fn, CASE_PREFIX)) { goto dir_cleanup_failed; }
+  ck_free(fn);
+
   fn = alloc_printf("%s/_resume/.state/auto_extras", afl->out_dir);
   if (delete_files(fn, "auto_")) { goto dir_cleanup_failed; }
   ck_free(fn);
@@ -1716,6 +1728,10 @@ static void handle_existing_out_dir(afl_state_t *afl) {
   if (delete_files(fn, CASE_PREFIX)) { goto dir_cleanup_failed; }
   ck_free(fn);
 
+  fn = alloc_printf("%s/queue/.state/was_fuzzed", afl->out_dir);
+  if (delete_files(fn, CASE_PREFIX)) { goto dir_cleanup_failed; }
+  ck_free(fn);
+
   fn = alloc_printf("%s/queue/.state/auto_extras", afl->out_dir);
   if (delete_files(fn, "auto_")) { goto dir_cleanup_failed; }
   ck_free(fn);
@@ -2007,6 +2023,12 @@ void setup_dirs_fds(afl_state_t *afl) {
   if (mkdir(tmp, 0700)) { PFATAL("Unable to create '%s'", tmp); }
   ck_free(tmp);
 
+  /* Directory for flagging queue entries that have been fuzzed */
+
+  tmp = alloc_printf("%s/queue/.state/was_fuzzed/", afl->out_dir);
+  if (mkdir(tmp, 0700)) { PFATAL("Unable to create '%s'", tmp); }
+  ck_free(tmp);
+
   /* Directory with the auto-selected dictionary entries. */
 
   tmp = alloc_printf("%s/queue/.state/auto_extras/", afl->out_dir);
diff --git a/src/afl-fuzz-one.c b/src/afl-fuzz-one.c
index ee562f960..9496a0520 100644
--- a/src/afl-fuzz-one.c
+++ b/src/afl-fuzz-one.c
@@ -3091,6 +3091,8 @@ abandon_entry:
     afl->reinit_table = 1;
     if (afl->queue_cur->favored) { --afl->pending_favored; }
 
+    mark_as_fuzzed(afl, afl->queue_cur);
+
   }
 
   ++afl->queue_cur->fuzz_level;
diff --git a/src/afl-fuzz-queue.c b/src/afl-fuzz-queue.c
index 8ad7cd976..b57fe0e9d 100644
--- a/src/afl-fuzz-queue.c
+++ b/src/afl-fuzz-queue.c
@@ -292,6 +292,20 @@ void mark_as_det_done(afl_state_t *afl, struct queue_entry *q) {
 
 }
 
+void mark_as_fuzzed(afl_state_t *afl, struct queue_entry *q) {
+
+  char fn[PATH_MAX];
+  s32  fd;
+
+  snprintf(fn, PATH_MAX, "%s/queue/.state/was_fuzzed/%s", afl->out_dir,
+           strrchr((char *)q->fname, '/') + 1);
+
+  fd = open(fn, O_WRONLY | O_CREAT | O_EXCL, DEFAULT_PERMISSION);
+  if (fd < 0) { PFATAL("Unable to create '%s'", fn); }
+  close(fd);
+
+}
+
 /* Mark as variable. Create symlinks if possible to make it easier to examine
    the files. */
 
diff --git a/src/afl-fuzz-run.c b/src/afl-fuzz-run.c
index 4d56f3a77..eef580a5d 100644
--- a/src/afl-fuzz-run.c
+++ b/src/afl-fuzz-run.c
@@ -648,6 +648,7 @@ void sync_fuzzers(afl_state_t *afl) {
   struct dirent *sd_ent;
   u32            sync_cnt = 0, synced = 0, entries = 0;
   u8             path[PATH_MAX + 1 + NAME_MAX];
+  u8             wf_path[PATH_MAX + 1 + NAME_MAX];
 
   sd = opendir(afl->sync_dir);
   if (!sd) { PFATAL("Unable to open '%s'", afl->sync_dir); }
@@ -703,6 +704,7 @@ void sync_fuzzers(afl_state_t *afl) {
     /* document the attempt to sync to this instance */
 
     sprintf(qd_synced_path, "%s/.synced/%s.last", afl->out_dir, sd_ent->d_name);
+    
     id_fd =
         open(qd_synced_path, O_RDWR | O_CREAT | O_TRUNC, DEFAULT_PERMISSION);
     if (id_fd >= 0) close(id_fd);
@@ -746,6 +748,12 @@ void sync_fuzzers(afl_state_t *afl) {
     afl->stage_cur = 0;
     afl->stage_max = 0;
 
+    if(afl->afl_env.afl_delay_sync && !afl->last_sync_time) { 
+      
+      goto close_sync; 
+      
+    }
+
     /* For every file queued by this fuzzer, parse ID and see if we have
        looked at it before; exec a test case if not. */
 
@@ -768,15 +776,50 @@ void sync_fuzzers(afl_state_t *afl) {
 
     if (m >= n) { goto close_sync; }  // nothing new
 
+    u8 retry_remaining = 0;
+    
     for (o = m; o < n; o++) {
 
       s32         fd;
       struct stat st;
+      struct stat wf_st;
+      u8          skip;
 
       snprintf(path, sizeof(path), "%s/%s", qd_path, namelist[o]->d_name);
+
+      /* Skip syncing of recent discoveries if AFL_DELAY_SYNC is true. */
+
+      skip = 0;
+
+      if (afl->afl_env.afl_delay_sync) {
+
+        u64 cycle_time = (get_cur_time() - 
+          (!afl->last_sync_time ? afl->start_time : afl->last_sync_time)) / 1000;
+
+        snprintf(wf_path, sizeof(wf_path), "%s/.state/was_fuzzed/%s", 
+                qd_path, namelist[o]->d_name);
+
+        if (lstat(wf_path, &wf_st) || 
+            ((u64)wf_st.st_mtime > 
+              get_cur_time() / 1000 - SYNC_DELAY_CYCLES * cycle_time)) {
+          
+          skip = 1;
+          
+          /* All entries beyond this one would need to be retried at the next
+            sync. (Pretty wasteful, would be better to document each synced
+            test case individually, e.g. in the .synced dir.) */
+
+          retry_remaining = 1;
+          ck_write(id_fd, &next_min_accept, sizeof(u32), qd_synced_path);
+
+        }
+      }
+
       afl->syncing_case = next_min_accept;
       next_min_accept++;
 
+      if (skip) { continue; }
+
       /* Allow this to fail in case the other fuzzer is resuming or so... */
 
       fd = open(path, O_RDONLY);
@@ -816,7 +859,8 @@ void sync_fuzzers(afl_state_t *afl) {
 
     }
 
-    ck_write(id_fd, &next_min_accept, sizeof(u32), qd_synced_path);
+    if (!retry_remaining)
+      ck_write(id_fd, &next_min_accept, sizeof(u32), qd_synced_path);
 
   close_sync:
     close(id_fd);
diff --git a/src/afl-fuzz-state.c b/src/afl-fuzz-state.c
index 46b67defc..b6bb0fdfb 100644
--- a/src/afl-fuzz-state.c
+++ b/src/afl-fuzz-state.c
@@ -567,6 +567,13 @@ void read_afl_environment(afl_state_t *afl, char **envp) {
 
             }
 
+          } else if (!strncmp(env, "AFL_DELAY_SYNC",
+
+                              afl_environment_variable_len)) {
+
+            afl->afl_env.afl_delay_sync =
+                get_afl_env(afl_environment_variables[i]) ? 1 : 0;
+
           } else if (!strncmp(env, "AFL_FUZZER_STATS_UPDATE_INTERVAL",
 
                               afl_environment_variable_len)) {
diff --git a/src/afl-fuzz.c b/src/afl-fuzz.c
index f6628851c..9866642b1 100644
--- a/src/afl-fuzz.c
+++ b/src/afl-fuzz.c
@@ -2578,7 +2578,8 @@ int main(int argc, char **argv_orig, char **envp) {
 
     } while (skipped_fuzz && afl->queue_cur && !afl->stop_soon);
 
-    if (likely(!afl->stop_soon && afl->sync_id)) {
+    if (likely(!afl->stop_soon && afl->sync_id && 
+               !afl->afl_env.afl_delay_sync)) {
 
       if (likely(afl->skip_deterministic)) {
 
-- 
GitLab