about summary refs log tree commit diff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/afl-fuzz-globals.c9
-rw-r--r--src/afl-fuzz-init.c28
-rw-r--r--src/afl-fuzz-mutators.c311
-rw-r--r--src/afl-fuzz-one.c122
-rw-r--r--src/afl-fuzz-python.c402
-rw-r--r--src/afl-fuzz-run.c10
-rw-r--r--src/afl-fuzz-stats.c2
-rw-r--r--src/afl-fuzz.c39
8 files changed, 525 insertions, 398 deletions
diff --git a/src/afl-fuzz-globals.c b/src/afl-fuzz-globals.c
index ae343026..87418753 100644
--- a/src/afl-fuzz-globals.c
+++ b/src/afl-fuzz-globals.c
@@ -88,8 +88,7 @@ u8 cal_cycles = CAL_CYCLES,             /* Calibration cycles defaults      */
     no_unlink,                          /* do not unlink cur_input          */
     use_stdin = 1,                      /* use stdin for sending data       */
     be_quiet,                           /* is AFL_QUIET set?                */
-    custom_only,                        /* Custom mutator only mode         */
-    python_only;                        /* Python-only mode                 */
+    custom_only;                        /* Custom mutator only mode         */
 
 u32 stats_update_freq = 1;              /* Stats update frequency (execs)   */
 
@@ -256,10 +255,8 @@ u8 *(*post_handler)(u8 *buf, u32 *len);
 u8 *cmplog_binary;
 s32 cmplog_child_pid, cmplog_forksrv_pid;
 
-/* hooks for the custom mutator function */
-size_t (*custom_mutator)(u8 *data, size_t size, u8 *mutated_out,
-                         size_t max_size, unsigned int seed);
-size_t (*pre_save_handler)(u8 *data, size_t size, u8 **new_data);
+/* Custom mutator */
+struct custom_mutator* mutator;
 
 /* Interesting values, as per config.h */
 
diff --git a/src/afl-fuzz-init.c b/src/afl-fuzz-init.c
index a82fa8f9..08b6de60 100644
--- a/src/afl-fuzz-init.c
+++ b/src/afl-fuzz-init.c
@@ -296,34 +296,6 @@ void setup_post(void) {
 
 }
 
-void setup_custom_mutator(void) {
-
-  void* dh;
-  u8*   fn = getenv("AFL_CUSTOM_MUTATOR_LIBRARY");
-
-  if (!fn) return;
-
-  if (limit_time_sig)
-    FATAL(
-        "MOpt and custom mutator are mutually exclusive. We accept pull "
-        "requests that integrates MOpt with the optional mutators "
-        "(custom/radamsa/redquenn/...).");
-
-  ACTF("Loading custom mutator library from '%s'...", fn);
-
-  dh = dlopen(fn, RTLD_NOW);
-  if (!dh) FATAL("%s", dlerror());
-
-  custom_mutator = dlsym(dh, "afl_custom_mutator");
-  if (!custom_mutator) FATAL("Symbol 'afl_custom_mutator' not found.");
-
-  pre_save_handler = dlsym(dh, "afl_pre_save_handler");
-  //  if (!pre_save_handler) WARNF("Symbol 'afl_pre_save_handler' not found.");
-
-  OKF("Custom mutator installed successfully.");
-
-}
-
 /* Shuffle an array of pointers. Might be slightly biased. */
 
 static void shuffle_ptrs(void** ptrs, u32 cnt) {
diff --git a/src/afl-fuzz-mutators.c b/src/afl-fuzz-mutators.c
new file mode 100644
index 00000000..26eaea59
--- /dev/null
+++ b/src/afl-fuzz-mutators.c
@@ -0,0 +1,311 @@
+/*
+   american fuzzy lop++ - custom mutators related routines
+   -------------------------------------------------------
+
+   Originally written by Shengtuo Hu
+
+   Now maintained by  Marc Heuse <mh@mh-sec.de>,
+                        Heiko Eißfeldt <heiko.eissfeldt@hexco.de> and
+                        Andrea Fioraldi <andreafioraldi@gmail.com>
+
+   Copyright 2016, 2017 Google Inc. All rights reserved.
+   Copyright 2019-2020 AFLplusplus Project. All rights reserved.
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at:
+
+     http://www.apache.org/licenses/LICENSE-2.0
+
+   This is the real deal: the program takes an instrumented binary and
+   attempts a variety of basic fuzzing tricks, paying close attention to
+   how they affect the execution path.
+
+ */
+
+#include "afl-fuzz.h"
+
+void setup_custom_mutator(void) {
+
+  /* Try mutator library first */
+  u8* fn = getenv("AFL_CUSTOM_MUTATOR_LIBRARY");
+
+  if (fn) {
+    if (limit_time_sig)
+      FATAL(
+          "MOpt and custom mutator are mutually exclusive. We accept pull "
+          "requests that integrates MOpt with the optional mutators "
+          "(custom/radamsa/redquenn/...).");
+
+    load_custom_mutator(fn);
+
+    return;
+  }
+
+  /* Try Python module */
+#ifdef USE_PYTHON
+  u8* module_name = getenv("AFL_PYTHON_MODULE");
+
+  if (module_name) {
+
+    if (limit_time_sig)
+      FATAL(
+          "MOpt and Python mutator are mutually exclusive. We accept pull "
+          "requests that integrates MOpt with the optional mutators "
+          "(custom/radamsa/redquenn/...).");
+
+    if (init_py_module(module_name))
+      FATAL("Failed to initialize Python module");
+
+    load_custom_mutator_py(module_name);
+
+  }
+#else
+  if (getenv("AFL_PYTHON_MODULE"))
+    FATAL("Your AFL binary was built without Python support");
+#endif
+
+}
+
+void destroy_custom_mutator(void) {
+
+  if (mutator) {
+    if (mutator->dh)
+      dlclose(mutator->dh);
+    else {
+      /* Python mutator */
+#ifdef USE_PYTHON
+      finalize_py_module();
+#endif
+    }
+
+    ck_free(mutator);
+  }
+
+}
+
+void load_custom_mutator(const char* fn) {
+
+  void* dh;
+  mutator = ck_alloc(sizeof(struct custom_mutator));
+
+  mutator->name = fn;
+  ACTF("Loading custom mutator library from '%s'...", fn);
+
+  dh = dlopen(fn, RTLD_NOW);
+  if (!dh) FATAL("%s", dlerror());
+  mutator->dh = dh;
+
+  /* Mutator */
+  /* "afl_custom_init", optional for backward compatibility */
+  mutator->afl_custom_init = dlsym(dh, "afl_custom_init");
+  if (!mutator->afl_custom_init)
+    WARNF("Symbol 'afl_custom_init' not found.");
+
+  /* "afl_custom_fuzz" or "afl_custom_mutator", required */
+  mutator->afl_custom_fuzz = dlsym(dh, "afl_custom_fuzz");
+  if (!mutator->afl_custom_fuzz) {
+
+    /* Try "afl_custom_mutator" for backward compatibility */
+    WARNF("Symbol 'afl_custom_fuzz' not found. Try 'afl_custom_mutator'.");
+
+    mutator->afl_custom_fuzz = dlsym(dh, "afl_custom_mutator");
+    if (!mutator->afl_custom_fuzz)
+      FATAL("Symbol 'afl_custom_mutator' not found.");
+
+  }
+
+  /* "afl_custom_pre_save", optional */
+  mutator->afl_custom_pre_save = dlsym(dh, "afl_custom_pre_save");
+  if (!mutator->afl_custom_pre_save)
+    WARNF("Symbol 'afl_custom_pre_save' not found.");
+
+  u8 notrim = 0;
+  /* "afl_custom_init_trim", optional */
+  mutator->afl_custom_init_trim = dlsym(dh, "afl_custom_init_trim");
+  if (!mutator->afl_custom_init_trim)
+    WARNF("Symbol 'afl_custom_init_trim' not found.");
+
+  /* "afl_custom_trim", optional */
+  mutator->afl_custom_trim = dlsym(dh, "afl_custom_trim");
+  if (!mutator->afl_custom_trim)
+    WARNF("Symbol 'afl_custom_trim' not found.");
+
+  /* "afl_custom_post_trim", optional */
+  mutator->afl_custom_post_trim = dlsym(dh, "afl_custom_post_trim");
+  if (!mutator->afl_custom_post_trim)
+    WARNF("Symbol 'afl_custom_post_trim' not found.");
+
+  if (notrim) {
+
+    mutator->afl_custom_init_trim = NULL;
+    mutator->afl_custom_trim = NULL;
+    mutator->afl_custom_post_trim = NULL;
+    WARNF(
+        "Custom mutator does not implement all three trim APIs, standard "
+        "trimming will be used.");
+
+  }
+
+  OKF("Custom mutator '%s' installed successfully.", fn);
+
+  /* Initialize the custom mutator */
+  if (mutator->afl_custom_init)
+    mutator->afl_custom_init(UR(0xFFFFFFFF));
+
+}
+
+u8 trim_case_custom(char** argv, struct queue_entry* q, u8* in_buf) {
+
+  static u8 tmp[64];
+  static u8 clean_trace[MAP_SIZE];
+
+  u8  needs_write = 0, fault = 0;
+  u32 trim_exec = 0;
+  u32 orig_len = q->len;
+
+  stage_name = tmp;
+  bytes_trim_in += q->len;
+
+  /* Initialize trimming in the custom mutator */
+  stage_cur = 0;
+  stage_max = mutator->afl_custom_init_trim(in_buf, q->len);
+
+  if (not_on_tty && debug)
+    SAYF("[Custom Trimming] START: Max %d iterations, %u bytes", stage_max,
+         q->len);
+
+  while (stage_cur < stage_max) {
+
+    sprintf(tmp, "ptrim %s", DI(trim_exec));
+
+    u32 cksum;
+
+    u8*    retbuf = NULL;
+    size_t retlen = 0;
+
+    mutator->afl_custom_trim(&retbuf, &retlen);
+
+    if (retlen > orig_len)
+      FATAL(
+          "Trimmed data returned by custom mutator is larger than original "
+          "data");
+
+    write_to_testcase(retbuf, retlen);
+
+    fault = run_target(argv, exec_tmout);
+    ++trim_execs;
+
+    if (stop_soon || fault == FAULT_ERROR) {
+
+      free(retbuf);
+      goto abort_trimming;
+
+    }
+
+    cksum = hash32(trace_bits, MAP_SIZE, HASH_CONST);
+
+    if (cksum == q->exec_cksum) {
+
+      q->len = retlen;
+      memcpy(in_buf, retbuf, retlen);
+
+      /* 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;
+        memcpy(clean_trace, trace_bits, MAP_SIZE);
+
+      }
+
+      /* Tell the custom mutator that the trimming was successful */
+      stage_cur = mutator->afl_custom_post_trim(1);
+
+      if (not_on_tty && debug)
+        SAYF("[Custom Trimming] SUCCESS: %d/%d iterations (now at %u bytes)",
+             stage_cur, stage_max, q->len);
+
+    } else {
+
+      /* Tell the custom mutator that the trimming was unsuccessful */
+      stage_cur = mutator->afl_custom_post_trim(0);
+      if (not_on_tty && debug)
+        SAYF("[Custom Trimming] FAILURE: %d/%d iterations", stage_cur,
+             stage_max);
+
+    }
+
+    free(retbuf);
+
+    /* Since this can be slow, update the screen every now and then. */
+
+    if (!(trim_exec++ % stats_update_freq)) show_stats();
+
+  }
+
+  if (not_on_tty && debug)
+    SAYF("[Custom Trimming] DONE: %u bytes -> %u bytes", orig_len, q->len);
+
+  /* If we have made changes to in_buf, we also need to update the on-disk
+     version of the test case. */
+
+  if (needs_write) {
+
+    s32 fd;
+
+    unlink(q->fname);                                      /* ignore errors */
+
+    fd = open(q->fname, O_WRONLY | O_CREAT | O_EXCL, 0600);
+
+    if (fd < 0) PFATAL("Unable to create '%s'", q->fname);
+
+    ck_write(fd, in_buf, q->len, q->fname);
+    close(fd);
+
+    memcpy(trace_bits, clean_trace, MAP_SIZE);
+    update_bitmap_score(q);
+
+  }
+
+abort_trimming:
+
+  bytes_trim_out += q->len;
+  return fault;
+
+}
+
+void load_custom_mutator_py(const char* module_name) {
+
+  mutator = ck_alloc(sizeof(struct custom_mutator));
+
+  mutator->name = module_name;
+  ACTF("Loading Python mutator library from '%s'...", module_name);
+
+  if (py_functions[PY_FUNC_INIT])
+    mutator->afl_custom_init = init_py;
+
+  /* "afl_custom_fuzz" should not be NULL, but the interface of Python mutator
+     is quite different from the custom mutator. */
+  mutator->afl_custom_fuzz = fuzz_py;
+
+  if (py_functions[PY_FUNC_PRE_SAVE])
+    mutator->afl_custom_pre_save = pre_save_py;
+
+  if (py_functions[PY_FUNC_INIT_TRIM])
+    mutator->afl_custom_init_trim = init_trim_py;
+
+  if (py_functions[PY_FUNC_POST_TRIM])
+    mutator->afl_custom_post_trim = post_trim_py;
+
+  if (py_functions[PY_FUNC_TRIM])
+    mutator->afl_custom_trim = trim_py;
+
+  OKF("Python mutator '%s' installed successfully.", module_name);
+
+  /* Initialize the custom mutator */
+  if (mutator->afl_custom_init)
+    mutator->afl_custom_init(UR(0xFFFFFFFF));
+
+}
diff --git a/src/afl-fuzz-one.c b/src/afl-fuzz-one.c
index f1efe2df..5d00e8df 100644
--- a/src/afl-fuzz-one.c
+++ b/src/afl-fuzz-one.c
@@ -449,7 +449,7 @@ u8 fuzz_one_original(char** argv) {
    * TRIMMING *
    ************/
 
-  if (!dumb_mode && !queue_cur->trim_done && !custom_mutator && !disable_trim) {
+  if (!dumb_mode && !queue_cur->trim_done && !disable_trim) {
 
     u8 res = trim_case(argv, queue_cur, in_buf);
 
@@ -482,55 +482,6 @@ u8 fuzz_one_original(char** argv) {
 
   if (use_radamsa > 1) goto radamsa_stage;
 
-  // custom_stage:	// not used - yet
-
-  if (custom_mutator) {
-
-    stage_short = "custom";
-    stage_name = "custom mutator";
-    stage_max = len << 3;
-    stage_val_type = STAGE_VAL_NONE;
-
-    const u32 max_seed_size = 4096 * 4096;
-    u8*       mutated_buf = ck_alloc(max_seed_size);
-
-    orig_hit_cnt = queued_paths + unique_crashes;
-
-    for (stage_cur = 0; stage_cur < stage_max; ++stage_cur) {
-
-      size_t orig_size = (size_t)len;
-      size_t mutated_size = custom_mutator(in_buf, orig_size, mutated_buf,
-                                           max_seed_size, UR(UINT32_MAX));
-      if (mutated_size > 0) {
-
-        out_buf = ck_realloc(out_buf, mutated_size);
-        memcpy(out_buf, mutated_buf, mutated_size);
-        if (common_fuzz_stuff(argv, out_buf, (u32)mutated_size)) {
-
-          goto abandon_entry;
-
-        }
-
-      }
-
-    }
-
-    ck_free(mutated_buf);
-    new_hit_cnt = queued_paths + unique_crashes;
-
-    stage_finds[STAGE_CUSTOM_MUTATOR] += new_hit_cnt - orig_hit_cnt;
-    stage_cycles[STAGE_CUSTOM_MUTATOR] += stage_max;
-
-    if (custom_only) {
-
-      /* Skip other stages */
-      ret_val = 0;
-      goto abandon_entry;
-
-    }
-
-  }
-
   if (cmplog_mode) {
 
     if (input_to_state_stage(argv, in_buf, out_buf, len, queue_cur->exec_cksum))
@@ -550,11 +501,7 @@ u8 fuzz_one_original(char** argv) {
                          : havoc_max_mult * 100)) ||
       queue_cur->passed_det) {
 
-#ifdef USE_PYTHON
-    goto python_stage;
-#else
-    goto havoc_stage;
-#endif
+    goto custom_mutator_stage;
 
   }
 
@@ -563,11 +510,7 @@ u8 fuzz_one_original(char** argv) {
 
   if (master_max && (queue_cur->exec_cksum % master_max) != master_id - 1) {
 
-#ifdef USE_PYTHON
-    goto python_stage;
-#else
-    goto havoc_stage;
-#endif
+    goto custom_mutator_stage;
 
   }
 
@@ -1582,24 +1525,25 @@ skip_extras:
 
   if (!queue_cur->passed_det) mark_as_det_done(queue_cur);
 
-#ifdef USE_PYTHON
-python_stage:
-  /**********************************
-   * EXTERNAL MUTATORS (Python API) *
-   **********************************/
+custom_mutator_stage:
+  /*******************
+   * CUSTOM MUTATORS *
+   *******************/
 
-  if (!py_module) goto havoc_stage;
+  if (!mutator) goto havoc_stage;
+  if (!mutator->afl_custom_fuzz) goto havoc_stage;
 
-  stage_name = "python";
-  stage_short = "python";
+  stage_name = "custom mutator";
+  stage_short = "custom";
   stage_max = HAVOC_CYCLES * perf_score / havoc_div / 100;
+  stage_val_type = STAGE_VAL_NONE;
 
   if (stage_max < HAVOC_MIN) stage_max = HAVOC_MIN;
 
-  orig_hit_cnt = queued_paths + unique_crashes;
+  const u32 max_seed_size = 4096 * 4096;
+  u8*       mutated_buf = ck_alloc(max_seed_size);
 
-  char*  retbuf = NULL;
-  size_t retlen = 0;
+  orig_hit_cnt = queued_paths + unique_crashes;
 
   for (stage_cur = 0; stage_cur < stage_max; ++stage_cur) {
 
@@ -1646,26 +1590,24 @@ python_stage:
     ck_read(fd, new_buf, target->len, target->fname);
     close(fd);
 
-    fuzz_py(out_buf, len, new_buf, target->len, &retbuf, &retlen);
+    size_t mutated_size = mutator->afl_custom_fuzz(out_buf, len,
+                                                   new_buf, target->len,
+                                                   mutated_buf, max_seed_size);
 
     ck_free(new_buf);
 
-    if (retbuf) {
+    if (mutated_size > 0) {
 
-      if (!retlen) goto abandon_entry;
+      out_buf = ck_realloc(out_buf, mutated_size);
+      memcpy(out_buf, mutated_buf, mutated_size);
 
-      if (common_fuzz_stuff(argv, retbuf, retlen)) {
+      if (common_fuzz_stuff(argv, out_buf, (u32)mutated_size)) {
 
-        free(retbuf);
+        ck_free(mutated_buf);
         goto abandon_entry;
 
       }
 
-      /* Reset retbuf/retlen */
-      free(retbuf);
-      retbuf = NULL;
-      retlen = 0;
-
       /* If we're finding new stuff, let's run for a bit longer, limits
          permitting. */
 
@@ -1686,12 +1628,13 @@ python_stage:
 
   }
 
+  ck_free(mutated_buf);
   new_hit_cnt = queued_paths + unique_crashes;
 
-  stage_finds[STAGE_PYTHON] += new_hit_cnt - orig_hit_cnt;
-  stage_cycles[STAGE_PYTHON] += stage_max;
+  stage_finds[STAGE_CUSTOM_MUTATOR] += new_hit_cnt - orig_hit_cnt;
+  stage_cycles[STAGE_CUSTOM_MUTATOR] += stage_max;
 
-  if (python_only) {
+  if (custom_only) {
 
     /* Skip other stages */
     ret_val = 0;
@@ -1699,8 +1642,6 @@ python_stage:
 
   }
 
-#endif
-
   /****************
    * RANDOM HAVOC *
    ****************/
@@ -2269,11 +2210,10 @@ retry_splicing:
     out_buf = ck_alloc_nozero(len);
     memcpy(out_buf, in_buf, len);
 
-#ifdef USE_PYTHON
-    goto python_stage;
-#else
-    goto havoc_stage;
-#endif
+    goto custom_mutator_stage;
+    /* ???: While integrating Python module, the author decided to jump to
+       python stage, but the reason behind this is not clear.*/
+    // goto havoc_stage;
 
   }
 
diff --git a/src/afl-fuzz-python.c b/src/afl-fuzz-python.c
index 42286527..c22e4402 100644
--- a/src/afl-fuzz-python.c
+++ b/src/afl-fuzz-python.c
@@ -28,195 +28,244 @@
 /* Python stuff */
 #ifdef USE_PYTHON
 
-int init_py() {
+int init_py_module(u8* module_name) {
 
-  Py_Initialize();
-  u8* module_name = getenv("AFL_PYTHON_MODULE");
-
-  if (module_name) {
+  if (!module_name) return 1;
 
-    if (limit_time_sig)
-      FATAL(
-          "MOpt and Python mutator are mutually exclusive. We accept pull "
-          "requests that integrates MOpt with the optional mutators "
-          "(custom/radamsa/redquenn/...).");
+  Py_Initialize();
 
 #if PY_MAJOR_VERSION >= 3
-    PyObject* py_name = PyUnicode_FromString(module_name);
+  PyObject* py_name = PyUnicode_FromString(module_name);
 #else
-    PyObject* py_name = PyString_FromString(module_name);
+  PyObject* py_name = PyString_FromString(module_name);
 #endif
 
-    py_module = PyImport_Import(py_name);
-    Py_DECREF(py_name);
+  py_module = PyImport_Import(py_name);
+  Py_DECREF(py_name);
+
+  if (py_module != NULL) {
 
-    if (py_module != NULL) {
+    u8 py_notrim = 0, py_idx;
+    py_functions[PY_FUNC_INIT] = PyObject_GetAttrString(py_module, "init");
+    py_functions[PY_FUNC_FUZZ] = PyObject_GetAttrString(py_module, "fuzz");
+    py_functions[PY_FUNC_PRE_SAVE] =
+        PyObject_GetAttrString(py_module, "pre_save");
+    py_functions[PY_FUNC_INIT_TRIM] =
+        PyObject_GetAttrString(py_module, "init_trim");
+    py_functions[PY_FUNC_POST_TRIM] =
+        PyObject_GetAttrString(py_module, "post_trim");
+    py_functions[PY_FUNC_TRIM] = PyObject_GetAttrString(py_module, "trim");
 
-      u8 py_notrim = 0, py_idx;
-      py_functions[PY_FUNC_INIT] = PyObject_GetAttrString(py_module, "init");
-      py_functions[PY_FUNC_FUZZ] = PyObject_GetAttrString(py_module, "fuzz");
-      py_functions[PY_FUNC_INIT_TRIM] =
-          PyObject_GetAttrString(py_module, "init_trim");
-      py_functions[PY_FUNC_POST_TRIM] =
-          PyObject_GetAttrString(py_module, "post_trim");
-      py_functions[PY_FUNC_TRIM] = PyObject_GetAttrString(py_module, "trim");
+    for (py_idx = 0; py_idx < PY_FUNC_COUNT; ++py_idx) {
 
-      for (py_idx = 0; py_idx < PY_FUNC_COUNT; ++py_idx) {
+      if (!py_functions[py_idx] || !PyCallable_Check(py_functions[py_idx])) {
 
-        if (!py_functions[py_idx] || !PyCallable_Check(py_functions[py_idx])) {
+        if (py_idx == PY_FUNC_PRE_SAVE) {
 
-          if (py_idx >= PY_FUNC_INIT_TRIM && py_idx <= PY_FUNC_TRIM) {
+          // Implenting the pre_save API is optional for now
+          if (PyErr_Occurred()) PyErr_Print();
 
-            // Implementing the trim API is optional for now
-            if (PyErr_Occurred()) PyErr_Print();
-            py_notrim = 1;
+        } else if (py_idx >= PY_FUNC_INIT_TRIM && py_idx <= PY_FUNC_TRIM) {
 
-          } else {
+          // Implementing the trim API is optional for now
+          if (PyErr_Occurred()) PyErr_Print();
+          py_notrim = 1;
 
-            if (PyErr_Occurred()) PyErr_Print();
-            fprintf(stderr,
-                    "Cannot find/call function with index %d in external "
-                    "Python module.\n",
-                    py_idx);
-            return 1;
+        } else {
 
-          }
+          if (PyErr_Occurred()) PyErr_Print();
+          fprintf(stderr,
+                  "Cannot find/call function with index %d in external "
+                  "Python module.\n",
+                  py_idx);
+          return 1;
 
         }
 
       }
 
-      if (py_notrim) {
+    }
 
-        py_functions[PY_FUNC_INIT_TRIM] = NULL;
-        py_functions[PY_FUNC_POST_TRIM] = NULL;
-        py_functions[PY_FUNC_TRIM] = NULL;
-        WARNF(
-            "Python module does not implement trim API, standard trimming will "
-            "be used.");
+    if (py_notrim) {
 
-      }
+      py_functions[PY_FUNC_INIT_TRIM] = NULL;
+      py_functions[PY_FUNC_POST_TRIM] = NULL;
+      py_functions[PY_FUNC_TRIM] = NULL;
+      WARNF(
+          "Python module does not implement trim API, standard trimming will "
+          "be used.");
+
+    }
+
+  } else {
+
+    PyErr_Print();
+    fprintf(stderr, "Failed to load \"%s\"\n", module_name);
+    return 1;
+
+  }
+
+  return 0;
+
+}
 
-      PyObject *py_args, *py_value;
+void finalize_py_module() {
 
-      /* Provide the init function a seed for the Python RNG */
-      py_args = PyTuple_New(1);
+  if (py_module != NULL) {
+
+    u32 i;
+    for (i = 0; i < PY_FUNC_COUNT; ++i)
+      Py_XDECREF(py_functions[i]);
+
+    Py_DECREF(py_module);
+
+  }
+
+  Py_Finalize();
+
+}
+
+void init_py(unsigned int seed) {
+  PyObject *py_args, *py_value;
+
+  /* Provide the init function a seed for the Python RNG */
+  py_args = PyTuple_New(1);
 #if PY_MAJOR_VERSION >= 3
-      py_value = PyLong_FromLong(UR(0xFFFFFFFF));
+  py_value = PyLong_FromLong(seed);
 #else
-      py_value = PyInt_FromLong(UR(0xFFFFFFFF));
+  py_value = PyInt_FromLong(seed);
 #endif
 
-      if (!py_value) {
+  if (!py_value) {
+
+    Py_DECREF(py_args);
+    fprintf(stderr, "Cannot convert argument\n");
+    return;
 
-        Py_DECREF(py_args);
-        fprintf(stderr, "Cannot convert argument\n");
-        return 1;
+  }
 
-      }
+  PyTuple_SetItem(py_args, 0, py_value);
 
-      PyTuple_SetItem(py_args, 0, py_value);
+  py_value = PyObject_CallObject(py_functions[PY_FUNC_INIT], py_args);
 
-      py_value = PyObject_CallObject(py_functions[PY_FUNC_INIT], py_args);
+  Py_DECREF(py_args);
 
-      Py_DECREF(py_args);
+  if (py_value == NULL) {
 
-      if (py_value == NULL) {
+    PyErr_Print();
+    fprintf(stderr, "Call failed\n");
+    return;
 
-        PyErr_Print();
-        fprintf(stderr, "Call failed\n");
-        return 1;
+  }
+}
 
-      }
+size_t fuzz_py(u8* buf, size_t buf_size,
+               u8* add_buf, size_t add_buf_size,
+               u8* mutated_out, size_t max_size) {
 
-    } else {
+  size_t mutated_size;
+  PyObject *py_args, *py_value;
+  py_args = PyTuple_New(3);
 
-      PyErr_Print();
-      fprintf(stderr, "Failed to load \"%s\"\n", module_name);
-      return 1;
+  /* buf */
+  py_value = PyByteArray_FromStringAndSize(buf, buf_size);
+  if (!py_value) {
 
-    }
+    Py_DECREF(py_args);
+    FATAL("Failed to convert arguments");
 
   }
 
-  return 0;
+  PyTuple_SetItem(py_args, 0, py_value);
 
-}
+  /* add_buf */
+  py_value = PyByteArray_FromStringAndSize(add_buf, add_buf_size);
+  if (!py_value) {
+
+    Py_DECREF(py_args);
+    FATAL("Failed to convert arguments");
 
-void finalize_py() {
+  }
 
-  if (py_module != NULL) {
+  PyTuple_SetItem(py_args, 1, py_value);
 
-    u32 i;
-    for (i = 0; i < PY_FUNC_COUNT; ++i)
-      Py_XDECREF(py_functions[i]);
+  /* max_size */
+#if PY_MAJOR_VERSION >= 3
+  py_value = PyLong_FromLong(max_size);
+#else
+  py_value = PyInt_FromLong(max_size);
+#endif
+  if (!py_value) {
 
-    Py_DECREF(py_module);
+    Py_DECREF(py_args);
+    FATAL("Failed to convert arguments");
 
   }
 
-  Py_Finalize();
+  PyTuple_SetItem(py_args, 2, py_value);
 
-}
+  py_value = PyObject_CallObject(py_functions[PY_FUNC_FUZZ], py_args);
 
-void fuzz_py(char* buf, size_t buflen, char* add_buf, size_t add_buflen,
-             char** ret, size_t* retlen) {
+  Py_DECREF(py_args);
 
-  if (py_module != NULL) {
+  if (py_value != NULL) {
 
-    PyObject *py_args, *py_value;
-    py_args = PyTuple_New(2);
-    py_value = PyByteArray_FromStringAndSize(buf, buflen);
-    if (!py_value) {
+    mutated_size = PyByteArray_Size(py_value);
+    memcpy(mutated_out, PyByteArray_AsString(py_value), mutated_size);
+    Py_DECREF(py_value);
+    return mutated_size;
 
-      Py_DECREF(py_args);
-      fprintf(stderr, "Cannot convert argument\n");
-      return;
+  } else {
 
-    }
+    PyErr_Print();
+    FATAL("Call failed");
 
-    PyTuple_SetItem(py_args, 0, py_value);
+  }
 
-    py_value = PyByteArray_FromStringAndSize(add_buf, add_buflen);
-    if (!py_value) {
+}
 
-      Py_DECREF(py_args);
-      fprintf(stderr, "Cannot convert argument\n");
-      return;
+size_t pre_save_py(u8* buf, size_t buf_size, u8** out_buf) {
 
-    }
+  size_t out_buf_size;
+  PyObject *py_args, *py_value;
+  py_args = PyTuple_New(2);
+  py_value = PyByteArray_FromStringAndSize(buf, buf_size);
+  if (!py_value) {
 
-    PyTuple_SetItem(py_args, 1, py_value);
+    Py_DECREF(py_args);
+    FATAL("Failed to convert arguments");
 
-    py_value = PyObject_CallObject(py_functions[PY_FUNC_FUZZ], py_args);
+  }
 
-    Py_DECREF(py_args);
+  PyTuple_SetItem(py_args, 0, py_value);
 
-    if (py_value != NULL) {
+  py_value = PyObject_CallObject(py_functions[PY_FUNC_PRE_SAVE], py_args);
 
-      *retlen = PyByteArray_Size(py_value);
-      *ret = malloc(*retlen);
-      memcpy(*ret, PyByteArray_AsString(py_value), *retlen);
-      Py_DECREF(py_value);
+  Py_DECREF(py_args);
 
-    } else {
+  if (py_value != NULL) {
 
-      PyErr_Print();
-      fprintf(stderr, "Call failed\n");
-      return;
+    out_buf_size = PyByteArray_Size(py_value);
+    *out_buf = malloc(out_buf_size);
+    memcpy(*out_buf, PyByteArray_AsString(py_value), out_buf_size);
+    Py_DECREF(py_value);
+    return out_buf_size;
 
-    }
+  } else {
+
+    PyErr_Print();
+    FATAL("Call failed");
 
   }
 
 }
 
-u32 init_trim_py(char* buf, size_t buflen) {
+u32 init_trim_py(u8* buf, size_t buf_size) {
 
   PyObject *py_args, *py_value;
 
   py_args = PyTuple_New(1);
-  py_value = PyByteArray_FromStringAndSize(buf, buflen);
+  py_value = PyByteArray_FromStringAndSize(buf, buf_size);
   if (!py_value) {
 
     Py_DECREF(py_args);
@@ -248,7 +297,7 @@ u32 init_trim_py(char* buf, size_t buflen) {
 
 }
 
-u32 post_trim_py(char success) {
+u32 post_trim_py(u8 success) {
 
   PyObject *py_args, *py_value;
 
@@ -286,7 +335,7 @@ u32 post_trim_py(char success) {
 
 }
 
-void trim_py(char** ret, size_t* retlen) {
+void trim_py(u8** out_buf, size_t* out_buf_size) {
 
   PyObject *py_args, *py_value;
 
@@ -296,9 +345,9 @@ void trim_py(char** ret, size_t* retlen) {
 
   if (py_value != NULL) {
 
-    *retlen = PyByteArray_Size(py_value);
-    *ret = malloc(*retlen);
-    memcpy(*ret, PyByteArray_AsString(py_value), *retlen);
+    *out_buf_size = PyByteArray_Size(py_value);
+    *out_buf = malloc(*out_buf_size);
+    memcpy(*out_buf, PyByteArray_AsString(py_value), *out_buf_size);
     Py_DECREF(py_value);
 
   } else {
@@ -310,126 +359,5 @@ void trim_py(char** ret, size_t* retlen) {
 
 }
 
-u8 trim_case_python(char** argv, struct queue_entry* q, u8* in_buf) {
-
-  static u8 tmp[64];
-  static u8 clean_trace[MAP_SIZE];
-
-  u8  needs_write = 0, fault = 0;
-  u32 trim_exec = 0;
-  u32 orig_len = q->len;
-
-  stage_name = tmp;
-  bytes_trim_in += q->len;
-
-  /* Initialize trimming in the Python module */
-  stage_cur = 0;
-  stage_max = init_trim_py(in_buf, q->len);
-
-  if (not_on_tty && debug)
-    SAYF("[Python Trimming] START: Max %d iterations, %u bytes", stage_max,
-         q->len);
-
-  while (stage_cur < stage_max) {
-
-    sprintf(tmp, "ptrim %s", DI(trim_exec));
-
-    u32 cksum;
-
-    char*  retbuf = NULL;
-    size_t retlen = 0;
-
-    trim_py(&retbuf, &retlen);
-
-    if (retlen > orig_len)
-      FATAL(
-          "Trimmed data returned by Python module is larger than original "
-          "data");
-
-    write_to_testcase(retbuf, retlen);
-
-    fault = run_target(argv, exec_tmout);
-    ++trim_execs;
-
-    if (stop_soon || fault == FAULT_ERROR) {
-
-      free(retbuf);
-      goto abort_trimming;
-
-    }
-
-    cksum = hash32(trace_bits, MAP_SIZE, HASH_CONST);
-
-    if (cksum == q->exec_cksum) {
-
-      q->len = retlen;
-      memcpy(in_buf, retbuf, retlen);
-
-      /* 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;
-        memcpy(clean_trace, trace_bits, MAP_SIZE);
-
-      }
-
-      /* Tell the Python module that the trimming was successful */
-      stage_cur = post_trim_py(1);
-
-      if (not_on_tty && debug)
-        SAYF("[Python Trimming] SUCCESS: %d/%d iterations (now at %u bytes)",
-             stage_cur, stage_max, q->len);
-
-    } else {
-
-      /* Tell the Python module that the trimming was unsuccessful */
-      stage_cur = post_trim_py(0);
-      if (not_on_tty && debug)
-        SAYF("[Python Trimming] FAILURE: %d/%d iterations", stage_cur,
-             stage_max);
-
-    }
-
-    free(retbuf);
-
-    /* Since this can be slow, update the screen every now and then. */
-
-    if (!(trim_exec++ % stats_update_freq)) show_stats();
-
-  }
-
-  if (not_on_tty && debug)
-    SAYF("[Python Trimming] DONE: %u bytes -> %u bytes", orig_len, q->len);
-
-  /* If we have made changes to in_buf, we also need to update the on-disk
-     version of the test case. */
-
-  if (needs_write) {
-
-    s32 fd;
-
-    unlink(q->fname);                                      /* ignore errors */
-
-    fd = open(q->fname, O_WRONLY | O_CREAT | O_EXCL, 0600);
-
-    if (fd < 0) PFATAL("Unable to create '%s'", q->fname);
-
-    ck_write(fd, in_buf, q->len, q->fname);
-    close(fd);
-
-    memcpy(trace_bits, clean_trace, MAP_SIZE);
-    update_bitmap_score(q);
-
-  }
-
-abort_trimming:
-
-  bytes_trim_out += q->len;
-  return fault;
-
-}
-
 #endif                                                        /* USE_PYTHON */
 
diff --git a/src/afl-fuzz-run.c b/src/afl-fuzz-run.c
index 95c2c5d4..12352355 100644
--- a/src/afl-fuzz-run.c
+++ b/src/afl-fuzz-run.c
@@ -309,11 +309,12 @@ void write_to_testcase(void* mem, u32 len) {
 
     lseek(fd, 0, SEEK_SET);
 
-  if (pre_save_handler) {
+  if (mutator && mutator->afl_custom_pre_save) {
 
     u8*    new_data;
-    size_t new_size = pre_save_handler(mem, len, &new_data);
+    size_t new_size = mutator->afl_custom_pre_save(mem, len, &new_data);
     ck_write(fd, new_data, new_size, out_file);
+    ck_free(new_data);
 
   } else {
 
@@ -678,9 +679,8 @@ void sync_fuzzers(char** argv) {
 
 u8 trim_case(char** argv, struct queue_entry* q, u8* in_buf) {
 
-#ifdef USE_PYTHON
-  if (py_functions[PY_FUNC_TRIM]) return trim_case_python(argv, q, in_buf);
-#endif
+  /* Custom mutator trimmer */
+  if (mutator->afl_custom_trim) return trim_case_custom(argv, q, in_buf);
 
   static u8 tmp[64];
   static u8 clean_trace[MAP_SIZE];
diff --git a/src/afl-fuzz-stats.c b/src/afl-fuzz-stats.c
index c1aa8315..1b763c01 100644
--- a/src/afl-fuzz-stats.c
+++ b/src/afl-fuzz-stats.c
@@ -655,7 +655,7 @@ void show_stats(void) {
 
   }
 
-  if (custom_mutator) {
+  if (mutator) {
 
     sprintf(tmp, "%s/%s", DI(stage_finds[STAGE_CUSTOM_MUTATOR]),
             DI(stage_cycles[STAGE_CUSTOM_MUTATOR]));
diff --git a/src/afl-fuzz.c b/src/afl-fuzz.c
index 548f029b..2d5a5743 100644
--- a/src/afl-fuzz.c
+++ b/src/afl-fuzz.c
@@ -155,10 +155,9 @@ static void usage(u8* argv0, int more_help) {
       "LD_BIND_LAZY: do not set LD_BIND_NOW env var for target\n"
       "AFL_BENCH_JUST_ONE: run the target just once\n"
       "AFL_DUMB_FORKSRV: use fork server without feedback from target\n"
-      "AFL_CUSTOM_MUTATOR_LIBRARY: lib with afl_custom_mutator() to mutate inputs\n"
+      "AFL_CUSTOM_MUTATOR_LIBRARY: lib with afl_custom_fuzz() to mutate inputs\n"
       "AFL_CUSTOM_MUTATOR_ONLY: avoid AFL++'s internal mutators\n"
       "AFL_PYTHON_MODULE: mutate and trim inputs with the specified Python module\n"
-      "AFL_PYTHON_ONLY: skip AFL++'s own mutators\n"
       "AFL_DEBUG: extra debugging output for Python mode trimming\n"
       "AFL_DISABLE_TRIM: disable the trimming of test cases\n"
       "AFL_NO_UI: switch status screen off\n"
@@ -195,7 +194,7 @@ static void usage(u8* argv0, int more_help) {
         "use \"-hh\".\n\n");
 
 #ifdef USE_PYTHON
-  SAYF("Compiled with %s module support, see docs/python_mutators.md\n",
+  SAYF("Compiled with %s module support, see docs/custom_mutator.md\n",
        (char*)PYTHON_VERSION);
 #endif
 
@@ -658,11 +657,10 @@ int main(int argc, char** argv, char** envp) {
   OKF("afl-tmin fork server patch from github.com/nccgroup/TriforceAFL");
   OKF("MOpt Mutator from github.com/puppet-meteor/MOpt-AFL");
 
-  if (sync_id && force_deterministic &&
-      (getenv("AFL_CUSTOM_MUTATOR_ONLY") || getenv("AFL_PYTHON_ONLY")))
+  if (sync_id && force_deterministic && getenv("AFL_CUSTOM_MUTATOR_ONLY"))
     WARNF(
-        "Using -M master with the AFL_..._ONLY mutator options will result in "
-        "no deterministic mutations being done!");
+        "Using -M master with the AFL_CUSTOM_MUTATOR_ONLY mutator options will "
+        "result in no deterministic mutations being done!");
 
   check_environment_vars(envp);
 
@@ -752,9 +750,9 @@ int main(int argc, char** argv, char** envp) {
   if (get_afl_env("AFL_FAST_CAL")) fast_cal = 1;
 
   if (get_afl_env("AFL_AUTORESUME")) {
-    
+
     autoresume = 1;
-    if (in_place_resume) 
+    if (in_place_resume)
       SAYF("AFL_AUTORESUME has no effect for '-i -'");
 
   }
@@ -832,16 +830,6 @@ int main(int argc, char** argv, char** envp) {
 
   if (get_afl_env("AFL_DEBUG")) debug = 1;
 
-  if (get_afl_env("AFL_PYTHON_ONLY")) {
-
-    /* This ensures we don't proceed to havoc/splice */
-    python_only = 1;
-
-    /* Ensure we also skip all deterministic steps */
-    skip_deterministic = 1;
-
-  }
-
   if (get_afl_env("AFL_CUSTOM_MUTATOR_ONLY")) {
 
     /* This ensures we don't proceed to havoc/splice */
@@ -862,7 +850,6 @@ int main(int argc, char** argv, char** envp) {
   check_cpu_governor();
 
   setup_post();
-  setup_custom_mutator();
   setup_shm(dumb_mode);
 
   if (!in_bitmap) memset(virgin_bits, 255, MAP_SIZE);
@@ -873,12 +860,7 @@ int main(int argc, char** argv, char** envp) {
 
   setup_dirs_fds();
 
-#ifdef USE_PYTHON
-  if (init_py()) FATAL("Failed to initialize Python module");
-#else
-  if (getenv("AFL_PYTHON_MODULE"))
-    FATAL("Your AFL binary was built without Python support");
-#endif
+  setup_custom_mutator();
 
   setup_cmdline_file(argv + optind);
 
@@ -1156,13 +1138,10 @@ stop_fuzzing:
   destroy_extras();
   ck_free(target_path);
   ck_free(sync_id);
+  destroy_custom_mutator();
 
   alloc_report();
 
-#ifdef USE_PYTHON
-  finalize_py();
-#endif
-
   OKF("We're done here. Have a nice day!\n");
 
   exit(0);