about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/afl-cc.c7
-rw-r--r--src/afl-common.c46
-rw-r--r--src/afl-forkserver.c7
-rw-r--r--src/afl-fuzz-run.c43
-rw-r--r--src/afl-fuzz-state.c4
-rw-r--r--src/afl-fuzz-stats.c178
-rw-r--r--src/afl-fuzz.c40
-rw-r--r--src/afl-tmin.c34
8 files changed, 262 insertions, 97 deletions
diff --git a/src/afl-cc.c b/src/afl-cc.c
index faa46103..45fd398b 100644
--- a/src/afl-cc.c
+++ b/src/afl-cc.c
@@ -1369,6 +1369,13 @@ void mode_final_checkout(aflcc_state_t *aflcc, int argc, char **argv) {
 
   }
 
+  if (getenv("AFL_LLVM_DICT2FILE") &&
+      (getenv("AFL_LLVM_LAF_SPLIT_SWITCHES") ||
+       getenv("AFL_LLVM_LAF_SPLIT_COMPARES") ||
+       getenv("AFL_LLVM_LAF_SPLIT_FLOATS") ||
+       getenv("AFL_LLVM_LAF_TRANSFORM_COMPARES")))
+    FATAL("AFL_LLVM_DICT2FILE is incompatible with AFL_LLVM_LAF_*");
+
   aflcc->cmplog_mode = getenv("AFL_CMPLOG") || getenv("AFL_LLVM_CMPLOG") ||
                        getenv("AFL_GCC_CMPLOG");
 
diff --git a/src/afl-common.c b/src/afl-common.c
index 87003b03..6d915b00 100644
--- a/src/afl-common.c
+++ b/src/afl-common.c
@@ -34,6 +34,7 @@
 #endif
 #include <string.h>
 #include <strings.h>
+#include <time.h>
 #include <math.h>
 #include <sys/mman.h>
 
@@ -58,6 +59,27 @@ u8  last_intr = 0;
   #define AFL_PATH "/usr/local/lib/afl/"
 #endif
 
+/* - Some BSD (i.e.: FreeBSD) offer the FAST clock source as
+ *   equivalent to Linux COARSE clock source. Aliasing COARSE to
+ *   FAST on such systems when COARSE is not already defined.
+ * - macOS has no support of CLOCK_MONOTONIC_COARSE clock type.
+ */
+#if defined(OS_DARWIN) || defined(OS_SUNOS) || defined(__APPLE__) || \
+    defined(__sun) || defined(__NetBSD__)
+  #define CLOCK_MONOTONIC_COARSE CLOCK_MONOTONIC
+#elif defined(OS_FREEBSD)
+  #define CLOCK_MONOTONIC_COARSE CLOCK_MONOTONIC_FAST
+#endif
+
+/* Convert seconds to milliseconds. */
+#define SEC_TO_MS(sec) ((sec) * 1000)
+/* Convert seconds to microseconds. */
+#define SEC_TO_US(sec) ((sec) * 1000000)
+/* Convert nanoseconds to milliseconds. */
+#define NS_TO_MS(ns) ((ns) / 1000000)
+/* Convert nanoseconds to microseconds. */
+#define NS_TO_US(ns) ((ns) / 1000)
+
 void *afl_memmem(const void *haystack, size_t haystacklen, const void *needle,
                  size_t needlelen) {
 
@@ -974,12 +996,16 @@ void read_bitmap(u8 *fname, u8 *map, size_t len) {
 
 inline u64 get_cur_time(void) {
 
-  struct timeval  tv;
-  struct timezone tz;
+  struct timespec ts;
+  int             rc = clock_gettime(CLOCK_MONOTONIC_COARSE, &ts);
+  if (rc == -1) {
 
-  gettimeofday(&tv, &tz);
+    PFATAL("Failed to obtain timestamp (errno = %i: %s)\n", errno,
+           strerror(errno));
 
-  return (tv.tv_sec * 1000ULL) + (tv.tv_usec / 1000);
+  }
+
+  return SEC_TO_MS((uint64_t)ts.tv_sec) + NS_TO_MS((uint64_t)ts.tv_nsec);
 
 }
 
@@ -987,12 +1013,16 @@ inline u64 get_cur_time(void) {
 
 u64 get_cur_time_us(void) {
 
-  struct timeval  tv;
-  struct timezone tz;
+  struct timespec ts;
+  int             rc = clock_gettime(CLOCK_MONOTONIC_COARSE, &ts);
+  if (rc == -1) {
 
-  gettimeofday(&tv, &tz);
+    PFATAL("Failed to obtain timestamp (errno = %i: %s)\n", errno,
+           strerror(errno));
+
+  }
 
-  return (tv.tv_sec * 1000000ULL) + tv.tv_usec;
+  return SEC_TO_US((uint64_t)ts.tv_sec) + NS_TO_US((uint64_t)ts.tv_nsec);
 
 }
 
diff --git a/src/afl-forkserver.c b/src/afl-forkserver.c
index cf5c511e..f28a2a64 100644
--- a/src/afl-forkserver.c
+++ b/src/afl-forkserver.c
@@ -1111,8 +1111,7 @@ void afl_fsrv_start(afl_forkserver_t *fsrv, char **argv,
 
       }
 
-      if ((status & FS_NEW_OPT_SHDMEM_FUZZ) && fsrv->add_extra_func &&
-          !ignore_autodict) {
+      if (status & FS_NEW_OPT_SHDMEM_FUZZ) {
 
         if (fsrv->support_shmem_fuzz) {
 
@@ -1129,7 +1128,7 @@ void afl_fsrv_start(afl_forkserver_t *fsrv, char **argv,
 
       }
 
-      if ((status & FS_NEW_OPT_AUTODICT)) {
+      if (status & FS_NEW_OPT_AUTODICT) {
 
         // even if we do not need the dictionary we have to read it
 
@@ -1875,7 +1874,7 @@ afl_fsrv_run_target(afl_forkserver_t *fsrv, u32 timeout,
      territory. */
 
 #ifdef __linux__
-  if (!fsrv->nyx_mode) {
+  if (likely(!fsrv->nyx_mode)) {
 
     memset(fsrv->trace_bits, 0, fsrv->map_size);
     MEM_BARRIER();
diff --git a/src/afl-fuzz-run.c b/src/afl-fuzz-run.c
index d764952c..edcddc8e 100644
--- a/src/afl-fuzz-run.c
+++ b/src/afl-fuzz-run.c
@@ -409,6 +409,7 @@ u8 calibrate_case(afl_state_t *afl, struct queue_entry *q, u8 *use_mem,
   u32 use_tmout = afl->fsrv.exec_tmout;
   u8 *old_sn = afl->stage_name;
 
+  u64 calibration_start_us = get_cur_time_us();
   if (unlikely(afl->shm.cmplog_mode)) { q->exec_cksum = 0; }
 
   /* Be a bit more generous about timeouts when resuming sessions, or when
@@ -504,6 +505,10 @@ u8 calibrate_case(afl_state_t *afl, struct queue_entry *q, u8 *use_mem,
 
     fault = fuzz_run_target(afl, &afl->fsrv, use_tmout);
 
+    // update the time spend in calibration after each execution, as those may
+    // be slow
+    update_calibration_time(afl, &calibration_start_us);
+
     /* afl->stop_soon is set by the handler for Ctrl+C. When it's pressed,
        we want to bail out quickly. */
 
@@ -650,6 +655,7 @@ abort_calibration:
 
   if (!first_run) { show_stats(afl); }
 
+  update_calibration_time(afl, &calibration_start_us);
   return fault;
 
 }
@@ -669,11 +675,16 @@ void sync_fuzzers(afl_state_t *afl) {
   afl->stage_max = afl->stage_cur = 0;
   afl->cur_depth = 0;
 
+  u64 sync_start_us = get_cur_time_us();
   /* Look at the entries created for every other fuzzer in the sync directory.
    */
 
   while ((sd_ent = readdir(sd))) {
 
+    // since sync can take substantial amounts of time, update time spend every
+    // iteration
+    update_sync_time(afl, &sync_start_us);
+
     u8  qd_synced_path[PATH_MAX], qd_path[PATH_MAX];
     u32 min_accept = 0, next_min_accept = 0;
 
@@ -811,7 +822,7 @@ void sync_fuzzers(afl_state_t *afl) {
         /* See what happens. We rely on save_if_interesting() to catch major
            errors and save the test case. */
 
-        (void)write_to_testcase(afl, (void **)&mem, st.st_size, 1);
+        u32 new_len = write_to_testcase(afl, (void **)&mem, st.st_size, 1);
 
         fault = fuzz_run_target(afl, &afl->fsrv, afl->fsrv.exec_tmout);
 
@@ -819,7 +830,7 @@ void sync_fuzzers(afl_state_t *afl) {
 
         afl->syncing_party = sd_ent->d_name;
         afl->queued_imported +=
-            save_if_interesting(afl, mem, st.st_size, fault);
+            save_if_interesting(afl, mem, new_len, fault);
         afl->syncing_party = 0;
 
         munmap(mem, st.st_size);
@@ -861,6 +872,9 @@ void sync_fuzzers(afl_state_t *afl) {
 
   if (afl->foreign_sync_cnt) read_foreign_testcases(afl, 0);
 
+  // add time in sync one last time
+  update_sync_time(afl, &sync_start_us);
+
   afl->last_sync_time = get_cur_time();
   afl->last_sync_cycle = afl->queue_cycle;
 
@@ -872,8 +886,9 @@ void sync_fuzzers(afl_state_t *afl) {
 
 u8 trim_case(afl_state_t *afl, struct queue_entry *q, u8 *in_buf) {
 
+  u8  needs_write = 0, fault = 0;
   u32 orig_len = q->len;
-
+  u64 trim_start_us = get_cur_time_us();
   /* Custom mutator trimmer */
   if (afl->custom_mutators_count) {
 
@@ -897,11 +912,15 @@ u8 trim_case(afl_state_t *afl, struct queue_entry *q, u8 *in_buf) {
 
     }
 
-    if (custom_trimmed) return trimmed_case;
+    if (custom_trimmed) {
+
+      fault = trimmed_case;
+      goto abort_trimming;
+
+    }
 
   }
 
-  u8  needs_write = 0, fault = 0;
   u32 trim_exec = 0;
   u32 remove_len;
   u32 len_p2;
@@ -912,7 +931,12 @@ u8 trim_case(afl_state_t *afl, struct queue_entry *q, u8 *in_buf) {
      detected, it will still work to some extent, so we don't check for
      this. */
 
-  if (unlikely(q->len < 5)) { return 0; }
+  if (unlikely(q->len < 5)) {
+
+    fault = 0;
+    goto abort_trimming;
+
+  }
 
   afl->stage_name = afl->stage_name_buf;
   afl->bytes_trim_in += q->len;
@@ -946,6 +970,8 @@ u8 trim_case(afl_state_t *afl, struct queue_entry *q, u8 *in_buf) {
 
       fault = fuzz_run_target(afl, &afl->fsrv, afl->fsrv.exec_tmout);
 
+      update_trim_time(afl, &trim_start_us);
+
       if (afl->stop_soon || fault == FSRV_RUN_ERROR) { goto abort_trimming; }
 
       /* Note that we don't keep track of crashes or hangs here; maybe TODO?
@@ -972,7 +998,6 @@ u8 trim_case(afl_state_t *afl, struct queue_entry *q, u8 *in_buf) {
 
         /* Let's save a clean trace, which will be needed by
            update_bitmap_score once we're done with the trimming stuff. */
-
         if (!needs_write) {
 
           needs_write = 1;
@@ -987,7 +1012,6 @@ u8 trim_case(afl_state_t *afl, struct queue_entry *q, u8 *in_buf) {
       }
 
       /* Since this can be slow, update the screen every now and then. */
-
       if (!(trim_exec++ % afl->stats_update_freq)) { show_stats(afl); }
       ++afl->stage_cur;
 
@@ -1039,8 +1063,9 @@ u8 trim_case(afl_state_t *afl, struct queue_entry *q, u8 *in_buf) {
   }
 
 abort_trimming:
-
   afl->bytes_trim_out += q->len;
+  update_trim_time(afl, &trim_start_us);
+
   return fault;
 
 }
diff --git a/src/afl-fuzz-state.c b/src/afl-fuzz-state.c
index ae327117..c61f00bd 100644
--- a/src/afl-fuzz-state.c
+++ b/src/afl-fuzz-state.c
@@ -28,10 +28,6 @@
 #include "afl-fuzz.h"
 #include "envs.h"
 
-s8  interesting_8[] = {INTERESTING_8};
-s16 interesting_16[] = {INTERESTING_8, INTERESTING_16};
-s32 interesting_32[] = {INTERESTING_8, INTERESTING_16, INTERESTING_32};
-
 char *power_names[POWER_SCHEDULES_NUM] = {"explore", "mmopt", "exploit",
                                           "fast",    "coe",   "lin",
                                           "quad",    "rare",  "seek"};
diff --git a/src/afl-fuzz-stats.c b/src/afl-fuzz-stats.c
index b6900506..7e1a3b92 100644
--- a/src/afl-fuzz-stats.c
+++ b/src/afl-fuzz-stats.c
@@ -133,6 +133,12 @@ void write_setup_file(afl_state_t *afl, u32 argc, char **argv) {
 
 }
 
+static bool starts_with(char *key, char *line) {
+
+  return strncmp(key, line, strlen(key)) == 0;
+
+}
+
 /* load some of the existing stats file when resuming.*/
 void load_stats_file(afl_state_t *afl) {
 
@@ -175,58 +181,84 @@ void load_stats_file(afl_state_t *afl) {
       strcpy(keystring, lstartptr);
       lptr++;
       char *nptr;
-      switch (lineno) {
-
-        case 3:
-          if (!strcmp(keystring, "run_time          "))
-            afl->prev_run_time = 1000 * strtoull(lptr, &nptr, 10);
-          break;
-        case 5:
-          if (!strcmp(keystring, "cycles_done       "))
-            afl->queue_cycle =
-                strtoull(lptr, &nptr, 10) ? strtoull(lptr, &nptr, 10) + 1 : 0;
-          break;
-        case 7:
-          if (!strcmp(keystring, "execs_done        "))
-            afl->fsrv.total_execs = strtoull(lptr, &nptr, 10);
-          break;
-        case 10:
-          if (!strcmp(keystring, "corpus_count      ")) {
-
-            u32 corpus_count = strtoul(lptr, &nptr, 10);
-            if (corpus_count != afl->queued_items) {
-
-              WARNF(
-                  "queue/ has been modified -- things might not work, you're "
-                  "on your own!");
-
-            }
-
-          }
-
-          break;
-        case 12:
-          if (!strcmp(keystring, "corpus_found      "))
-            afl->queued_discovered = strtoul(lptr, &nptr, 10);
-          break;
-        case 13:
-          if (!strcmp(keystring, "corpus_imported   "))
-            afl->queued_imported = strtoul(lptr, &nptr, 10);
-          break;
-        case 14:
-          if (!strcmp(keystring, "max_depth         "))
-            afl->max_depth = strtoul(lptr, &nptr, 10);
-          break;
-        case 21:
-          if (!strcmp(keystring, "saved_crashes    "))
-            afl->saved_crashes = strtoull(lptr, &nptr, 10);
-          break;
-        case 22:
-          if (!strcmp(keystring, "saved_hangs      "))
-            afl->saved_hangs = strtoull(lptr, &nptr, 10);
-          break;
-        default:
-          break;
+      if (starts_with("run_time", keystring)) {
+
+        afl->prev_run_time = 1000 * strtoull(lptr, &nptr, 10);
+
+      }
+
+      if (starts_with("cycles_done", keystring)) {
+
+        afl->queue_cycle =
+            strtoull(lptr, &nptr, 10) ? strtoull(lptr, &nptr, 10) + 1 : 0;
+
+      }
+
+      if (starts_with("calibration_time", keystring)) {
+
+        afl->calibration_time_us = strtoull(lptr, &nptr, 10) * 1000000;
+
+      }
+
+      if (starts_with("sync_time", keystring)) {
+
+        afl->sync_time_us = strtoull(lptr, &nptr, 10) * 1000000;
+
+      }
+
+      if (starts_with("trim_time", keystring)) {
+
+        afl->trim_time_us = strtoull(lptr, &nptr, 10) * 1000000;
+
+      }
+
+      if (starts_with("execs_done", keystring)) {
+
+        afl->fsrv.total_execs = strtoull(lptr, &nptr, 10);
+
+      }
+
+      if (starts_with("corpus_count", keystring)) {
+
+        u32 corpus_count = strtoul(lptr, &nptr, 10);
+        if (corpus_count != afl->queued_items) {
+
+          WARNF(
+              "queue/ has been modified -- things might not work, you're "
+              "on your own!");
+          sleep(3);
+
+        }
+
+      }
+
+      if (starts_with("corpus_found", keystring)) {
+
+        afl->queued_discovered = strtoul(lptr, &nptr, 10);
+
+      }
+
+      if (starts_with("corpus_imported", keystring)) {
+
+        afl->queued_imported = strtoul(lptr, &nptr, 10);
+
+      }
+
+      if (starts_with("max_depth", keystring)) {
+
+        afl->max_depth = strtoul(lptr, &nptr, 10);
+
+      }
+
+      if (starts_with("saved_crashes", keystring)) {
+
+        afl->saved_crashes = strtoull(lptr, &nptr, 10);
+
+      }
+
+      if (starts_with("saved_hangs", keystring)) {
+
+        afl->saved_hangs = strtoull(lptr, &nptr, 10);
 
       }
 
@@ -300,6 +332,10 @@ void write_stats_file(afl_state_t *afl, u32 t_bytes, double bitmap_cvg,
       "cycles_done       : %llu\n"
       "cycles_wo_finds   : %llu\n"
       "time_wo_finds     : %llu\n"
+      "fuzz_time         : %llu\n"
+      "calibration_time  : %llu\n"
+      "sync_time         : %llu\n"
+      "trim_time         : %llu\n"
       "execs_done        : %llu\n"
       "execs_per_sec     : %0.02f\n"
       "execs_ps_last_min : %0.02f\n"
@@ -337,7 +373,7 @@ void write_stats_file(afl_state_t *afl, u32 t_bytes, double bitmap_cvg,
       "\n"
       "target_mode       : %s%s%s%s%s%s%s%s%s%s\n"
       "command_line      : %s\n",
-      (afl->start_time - afl->prev_run_time) / 1000, cur_time / 1000,
+      (afl->start_time /*- afl->prev_run_time*/) / 1000, cur_time / 1000,
       runtime / 1000, (u32)getpid(),
       afl->queue_cycle ? (afl->queue_cycle - 1) : 0, afl->cycles_wo_finds,
       afl->longest_find_time > cur_time - afl->last_find_time
@@ -345,7 +381,13 @@ void write_stats_file(afl_state_t *afl, u32 t_bytes, double bitmap_cvg,
           : ((afl->start_time == 0 || afl->last_find_time == 0)
                  ? 0
                  : (cur_time - afl->last_find_time) / 1000),
-      afl->fsrv.total_execs, afl->fsrv.total_execs / ((double)(runtime) / 1000),
+      (runtime -
+       (afl->calibration_time_us + afl->sync_time_us + afl->trim_time_us) /
+           1000) /
+          1000,
+      afl->calibration_time_us / 1000000, afl->sync_time_us / 1000000,
+      afl->trim_time_us / 1000000, afl->fsrv.total_execs,
+      afl->fsrv.total_execs / ((double)(runtime) / 1000),
       afl->last_avg_execs_saved, afl->queued_items, afl->queued_favored,
       afl->queued_discovered, afl->queued_imported, afl->queued_variable,
       afl->max_depth, afl->current_entry, afl->pending_favored,
@@ -876,6 +918,10 @@ void show_stats_normal(afl_state_t *afl) {
 
 #endif
 
+    if (banner_pad)
+      for (u32 i = 0; i < banner_pad; ++i)
+        strcat(banner, " ");
+
   }
 
   SAYF("\n%s\n", banner);
@@ -2435,3 +2481,27 @@ void show_init_stats(afl_state_t *afl) {
 
 }
 
+void update_calibration_time(afl_state_t *afl, u64 *time) {
+
+  u64 cur = get_cur_time_us();
+  afl->calibration_time_us += cur - *time;
+  *time = cur;
+
+}
+
+void update_trim_time(afl_state_t *afl, u64 *time) {
+
+  u64 cur = get_cur_time_us();
+  afl->trim_time_us += cur - *time;
+  *time = cur;
+
+}
+
+void update_sync_time(afl_state_t *afl, u64 *time) {
+
+  u64 cur = get_cur_time_us();
+  afl->sync_time_us += cur - *time;
+  *time = cur;
+
+}
+
diff --git a/src/afl-fuzz.c b/src/afl-fuzz.c
index 443d93b0..00d24ab1 100644
--- a/src/afl-fuzz.c
+++ b/src/afl-fuzz.c
@@ -5,8 +5,9 @@
    Originally written by Michal Zalewski
 
    Now maintained by Marc Heuse <mh@mh-sec.de>,
-                        Heiko Eißfeldt <heiko.eissfeldt@hexco.de> and
-                        Andrea Fioraldi <andreafioraldi@gmail.com>
+                     Dominik Meier <mail@dmnk.co>,
+                     Andrea Fioraldi <andreafioraldi@gmail.com>, and
+                     Heiko Eissfeldt <heiko.eissfeldt@hexco.de>
 
    Copyright 2016, 2017 Google Inc. All rights reserved.
    Copyright 2019-2024 AFLplusplus Project. All rights reserved.
@@ -199,7 +200,8 @@ static void usage(u8 *argv0, int more_help) {
 
       "Test settings:\n"
       "  -s seed       - use a fixed seed for the RNG\n"
-      "  -V seconds    - fuzz for a specified time then terminate\n"
+      "  -V seconds    - fuzz for a specified time then terminate (fuzz time "
+      "only!)\n"
       "  -E execs      - fuzz for an approx. no. of total executions then "
       "terminate\n"
       "                  Note: not precise and can have several more "
@@ -2073,6 +2075,17 @@ int main(int argc, char **argv_orig, char **envp) {
 
   }
 
+  /* Simply code if AFL_TMPDIR is used or not */
+  if (!afl->afl_env.afl_tmpdir) {
+
+    afl->tmp_dir = afl->out_dir;
+
+  } else {
+
+    afl->tmp_dir = afl->afl_env.afl_tmpdir;
+
+  }
+
   write_setup_file(afl, argc, argv);
 
   setup_cmdline_file(afl, argv + optind);
@@ -2085,8 +2098,7 @@ int main(int argc, char **argv_orig, char **envp) {
 
   if (!afl->timeout_given) { find_timeout(afl); }  // only for resumes!
 
-  if ((afl->tmp_dir = afl->afl_env.afl_tmpdir) != NULL &&
-      !afl->in_place_resume) {
+  if (afl->afl_env.afl_tmpdir && !afl->in_place_resume) {
 
     char tmpfile[PATH_MAX];
 
@@ -2111,10 +2123,6 @@ int main(int argc, char **argv_orig, char **envp) {
 
     }
 
-  } else {
-
-    afl->tmp_dir = afl->out_dir;
-
   }
 
   /* If we don't have a file name chosen yet, use a safe default. */
@@ -2537,8 +2545,6 @@ int main(int argc, char **argv_orig, char **envp) {
   }
 
   // (void)nice(-20);  // does not improve the speed
-  // real start time, we reset, so this works correctly with -V
-  afl->start_time = get_cur_time();
 
   #ifdef INTROSPECTION
   u32 prev_saved_crashes = 0, prev_saved_tmouts = 0;
@@ -2559,6 +2565,9 @@ int main(int argc, char **argv_orig, char **envp) {
   OKF("Writing mutation introspection to '%s'", ifn);
   #endif
 
+  // real start time, we reset, so this works correctly with -V
+  afl->start_time = get_cur_time();
+
   while (likely(!afl->stop_soon)) {
 
     cull_queue(afl);
@@ -2579,6 +2588,13 @@ int main(int argc, char **argv_orig, char **envp) {
 
         sync_fuzzers(afl);
 
+        if (!afl->queue_cycle && afl->afl_env.afl_import_first) {
+
+          // real start time, we reset, so this works correctly with -V
+          afl->start_time = get_cur_time();
+
+        }
+
       }
 
       ++afl->queue_cycle;
@@ -3068,7 +3084,7 @@ stop_fuzzing:
   afl_fsrv_deinit(&afl->fsrv);
 
   /* remove tmpfile */
-  if (afl->tmp_dir != NULL && !afl->in_place_resume && afl->fsrv.out_file) {
+  if (!afl->in_place_resume && afl->fsrv.out_file) {
 
     (void)unlink(afl->fsrv.out_file);
 
diff --git a/src/afl-tmin.c b/src/afl-tmin.c
index 4e5dab41..994174ed 100644
--- a/src/afl-tmin.c
+++ b/src/afl-tmin.c
@@ -82,6 +82,8 @@ static u8 crash_mode,                  /* Crash-centric mode?               */
     remove_shm = 1,                    /* remove shmem on exit?             */
     debug;                             /* debug mode                        */
 
+static u32 del_len_limit = 1;          /* Minimum block deletion length     */
+
 static volatile u8 stop_soon;          /* Ctrl-C pressed?                   */
 
 static afl_forkserver_t *fsrv;
@@ -480,7 +482,7 @@ next_del_blksize:
 
   }
 
-  if (del_len > 1 && in_len >= 1) {
+  if (del_len > del_len_limit && in_len >= 1) {
 
     del_len /= 2;
     goto next_del_blksize;
@@ -796,8 +798,9 @@ static void usage(u8 *argv0) {
       "Minimization settings:\n"
 
       "  -e            - solve for edge coverage only, ignore hit counts\n"
-      "  -x            - treat non-zero exit codes as crashes\n\n"
-      "  -H            - minimize a hang (hang mode)\n"
+      "  -l bytes      - set minimum block deletion length to speed up minimization\n"
+      "  -x            - treat non-zero exit codes as crashes\n"
+      "  -H            - minimize a hang (hang mode)\n\n"
 
       "For additional tips, please consult %s/README.md.\n\n"
 
@@ -829,8 +832,9 @@ static void usage(u8 *argv0) {
 
 int main(int argc, char **argv_orig, char **envp) {
 
-  s32    opt;
-  u8     mem_limit_given = 0, timeout_given = 0, unicorn_mode = 0, use_wine = 0;
+  s32 opt;
+  u8  mem_limit_given = 0, timeout_given = 0, unicorn_mode = 0, use_wine = 0,
+     del_limit_given = 0;
   char **use_argv;
 
   char **argv = argv_cpy_dup(argc, argv_orig);
@@ -846,7 +850,7 @@ int main(int argc, char **argv_orig, char **envp) {
 
   SAYF(cCYA "afl-tmin" VERSION cRST " by Michal Zalewski\n");
 
-  while ((opt = getopt(argc, argv, "+i:o:f:m:t:B:xeAOQUWXYHh")) > 0) {
+  while ((opt = getopt(argc, argv, "+i:o:f:m:t:l:B:xeAOQUWXYHh")) > 0) {
 
     switch (opt) {
 
@@ -1055,6 +1059,24 @@ int main(int argc, char **argv_orig, char **envp) {
         read_bitmap(optarg, mask_bitmap, map_size);
         break;
 
+      case 'l':
+        if (del_limit_given) { FATAL("Multiple -l options not supported"); }
+        del_limit_given = 1;
+
+        if (!optarg) { FATAL("Wrong usage of -l"); }
+
+        if (optarg[0] == '-') { FATAL("Dangerously low value of -l"); }
+
+        del_len_limit = atoi(optarg);
+
+        if (del_len_limit < 1 || del_len_limit > TMIN_MAX_FILE) {
+
+          FATAL("Value of -l out of range between 1 and TMIN_MAX_FILE");
+
+        }
+
+        break;
+
       case 'h':
         usage(argv[0]);
         return -1;