about summary refs log tree commit diff
path: root/custom_mutators
diff options
context:
space:
mode:
Diffstat (limited to 'custom_mutators')
-rw-r--r--custom_mutators/README.md10
-rw-r--r--custom_mutators/autotokens/Makefile26
-rw-r--r--custom_mutators/autotokens/README34
-rw-r--r--custom_mutators/autotokens/autotokens.cpp1101
-rw-r--r--custom_mutators/examples/custom_mutator_helpers.h342
-rw-r--r--custom_mutators/examples/custom_send.c63
-rw-r--r--custom_mutators/examples/example.c116
-rw-r--r--custom_mutators/examples/post_library_gif.so.c53
-rw-r--r--custom_mutators/examples/post_library_png.so.c24
-rw-r--r--custom_mutators/examples/simple_example.c28
-rwxr-xr-xcustom_mutators/gramatron/build_gramatron_mutator.sh4
-rwxr-xr-xcustom_mutators/grammar_mutator/build_grammar_mutator.sh4
-rw-r--r--custom_mutators/libafl_base/Cargo.toml2
-rw-r--r--custom_mutators/libafl_base/src/lib.rs36
-rw-r--r--custom_mutators/rust/custom_mutator-sys/Cargo.toml6
-rw-r--r--custom_mutators/rust/custom_mutator-sys/build.rs4
-rw-r--r--custom_mutators/rust/custom_mutator-sys/src/lib.rs2
-rw-r--r--custom_mutators/rust/custom_mutator/Cargo.toml2
-rw-r--r--custom_mutators/rust/custom_mutator/src/lib.rs109
-rw-r--r--custom_mutators/rust/example/Cargo.toml2
-rw-r--r--custom_mutators/rust/example_lain/Cargo.toml2
21 files changed, 1396 insertions, 574 deletions
diff --git a/custom_mutators/README.md b/custom_mutators/README.md
index 0289e150..8d01856f 100644
--- a/custom_mutators/README.md
+++ b/custom_mutators/README.md
@@ -11,6 +11,16 @@ The `./examples` folder contains examples for custom mutators in python and C.
 
 In `./rust`, you will find rust bindings, including a simple example in `./rust/example` and an example for structured fuzzing, based on lain, in`./rust/example_lain`.
 
+## The AFL++ grammar agnostic grammar mutator
+
+In `./autotokens` you find a token-level fuzzer that does not need to know
+anything about the grammar of an input as long as it is in ascii and allows
+whitespace.
+It is very fast and effective.
+
+If you are looking for an example of how to effectively create a custom
+mutator take a look at this one.
+
 ## The AFL++ Grammar Mutator
 
 If you use git to clone AFL++, then the following will incorporate our
diff --git a/custom_mutators/autotokens/Makefile b/custom_mutators/autotokens/Makefile
new file mode 100644
index 00000000..0daba17d
--- /dev/null
+++ b/custom_mutators/autotokens/Makefile
@@ -0,0 +1,26 @@
+ifdef debug
+	CPPLAGS += -fsanitize=address
+	CXXFLAGS += -Wall
+	CC := clang
+	CXX := clang++
+endif
+ifdef DEBUG
+	CPPFLAGS += -fsanitize=address
+	CXXFLAGS += -Wall
+	CC := clang
+	CXX := clang++
+endif
+
+all:	autotokens.so
+
+afl-fuzz-queue.o:	../../src/afl-fuzz-queue.c
+	$(CC) -D_STANDALONE_MODULE=1 -I../../include -g -O3 $(CPPFLAGS) -fPIC -c -o ./afl-fuzz-queue.o ../../src/afl-fuzz-queue.c
+
+afl-common.o:	../../src/afl-common.c
+	$(CC) -I../../include -g -O3 $(CPPFLAGS) -DBIN_PATH=\"dummy\" -Wno-pointer-sign -fPIC -c -o ./afl-common.o ../../src/afl-common.c
+
+autotokens.so:	afl-fuzz-queue.o afl-common.o autotokens.cpp
+	$(CXX) -Wno-deprecated -g -O3 $(CXXFLAGS) $(CPPFLAGS) -shared -fPIC -o autotokens.so -I../../include autotokens.cpp  ./afl-fuzz-queue.o ../../src/afl-performance.o ./afl-common.o
+
+clean:
+	rm -f autotokens.so *.o *~ core
diff --git a/custom_mutators/autotokens/README b/custom_mutators/autotokens/README
new file mode 100644
index 00000000..cca168fd
--- /dev/null
+++ b/custom_mutators/autotokens/README
@@ -0,0 +1,34 @@
+# Autotokens
+
+This implements an improved autotoken grammar fuzzing idea presented in
+[Token-Level Fuzzing][https://www.usenix.org/system/files/sec21-salls.pdf].
+It is a grammar fuzzer without actually knowing the grammar, but only works
+with text based inputs.
+
+It is recommended to run with together in an instance with `CMPLOG`.
+
+If you have a dictionary (`-x`) this improves this custom grammar mutator.
+
+If **not** running with `CMPLOG`, it is possible to set
+`AFL_CUSTOM_MUTATOR_ONLY` to concentrate on grammar bug classes.
+
+Do **not** set `AFL_DISABLE_TRIM` with this custom mutator!
+
+## Configuration via environment variables
+
+`AUTOTOKENS_ONLY_FAV` - only use this mutator on favorite queue items
+`AUTOTOKENS_COMMENT` - what character or string starts a comment which will be
+                       removed. Default: `/* ... */`
+`AUTOTOKENS_FUZZ_COUNT_SHIFT` - reduce the number of fuzzing performed, shifting
+                                the value by this number, e.g. 1.
+`AUTOTOKENS_AUTO_DISABLE` - disable this module if the seeds are not ascii
+                            (or no input and no (ascii) dictionary)
+`AUTOTOKENS_LEARN_DICT` - learn from dictionaries?
+                          0 = none
+                          1 = only -x or autodict
+                          2 = -x, autodict and `CMPLOG`
+`AUTOTOKENS_CHANGE_MIN` - minimum number of mutations (1-256, default 8)
+`AUTOTOKENS_CHANGE_MAX` - maximum number of mutations (1-4096, default 64)
+`AUTOTOKENS_CREATE_FROM_THIN_AIR` - if only one small start file is present and
+                                    a dictionary loaded then create one initial
+                                    structure based on the dictionary.
diff --git a/custom_mutators/autotokens/autotokens.cpp b/custom_mutators/autotokens/autotokens.cpp
new file mode 100644
index 00000000..8135aba1
--- /dev/null
+++ b/custom_mutators/autotokens/autotokens.cpp
@@ -0,0 +1,1101 @@
+/*
+   token level fuzzing custom mutator for afl++
+   (c) by Marc Heuse <mh@mh-sec.de>
+   License: Apache 2.0
+*/
+
+extern "C" {
+
+#include "afl-fuzz.h"
+
+}
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include <iostream>
+#include <fstream>
+#include <unordered_map>
+#include <vector>
+#include <regex>
+
+#define AUTOTOKENS_DEBUG 0
+#define AUTOTOKENS_ONLY_FAV 0
+#define AUTOTOKENS_CHANGE_MIN 8
+#define AUTOTOKENS_CHANGE_MAX 64
+#define AUTOTOKENS_SIZE_MIN 8
+#define AUTOTOKENS_SIZE_MAX 65535
+#define AUTOTOKENS_SPLICE_MIN 4
+#define AUTOTOKENS_SPLICE_MAX 64
+#define AUTOTOKENS_CREATE_FROM_THIN_AIR 0
+#define AUTOTOKENS_FUZZ_COUNT_SHIFT 0
+#define AUTOTOKENS_AUTO_DISABLE 0
+// 0 = no learning, 1 only from -x dict/autodict, 2 also from cmplog
+#define AUTOTOKENS_LEARN_DICT 1
+#ifndef AUTOTOKENS_SPLICE_DISABLE
+  #define AUTOTOKENS_SPLICE_DISABLE 0
+#endif
+#ifndef AFL_TXT_MAX_LEN
+  #define AFL_TXT_MAX_LEN 65535
+#endif
+
+#if AUTOTOKENS_SPLICE_MIN >= AUTOTOKENS_SIZE_MIN
+  #error SPLICE_MIN must be lower than SIZE_MIN
+#endif
+
+using namespace std;
+
+typedef struct my_mutator {
+
+  afl_state *afl;
+
+} my_mutator_t;
+
+#undef DEBUGF
+#define DEBUGF \
+  if (unlikely(debug)) fprintf
+#define IFDEBUG if (unlikely(debug))
+
+static afl_state *afl_ptr;
+static int        module_disabled = 0;
+static int        auto_disable = AUTOTOKENS_AUTO_DISABLE;
+static int        debug = AUTOTOKENS_DEBUG;
+static int        only_fav = AUTOTOKENS_ONLY_FAV;
+static int        learn_dictionary_tokens = AUTOTOKENS_LEARN_DICT;
+static int        fuzz_count_shift = AUTOTOKENS_FUZZ_COUNT_SHIFT;
+static int        create_from_thin_air = AUTOTOKENS_CREATE_FROM_THIN_AIR;
+static int        change_min = AUTOTOKENS_CHANGE_MIN;
+static int        change_max = AUTOTOKENS_CHANGE_MAX;
+static u32        current_id;
+static u32        valid_structures;
+static u32        whitespace_ids;
+static u32        extras_cnt, a_extras_cnt;
+static u64        all_spaces, all_tabs, all_lf, all_ws;
+static u64        all_structure_items;
+static u64        fuzz_count;
+static unordered_map<string, vector<u32> *> file_mapping;
+static unordered_map<u32, vector<u32> *>    id_mapping;
+static unordered_map<string, u32>           token_to_id;
+static unordered_map<u32, string>           id_to_token;
+static string                               output;
+static regex                               *regex_comment_custom;
+// multiline requires g++-11 libs :(
+static regex regex_comment_star(
+    "/\\*([:print:]|\n)*?\\*/",
+    regex_constants::optimize /* | regex_constants::multiline */);
+static regex        regex_word("[A-Za-z0-9_$.-]+", regex::optimize);
+static regex        regex_whitespace(R"([ \t]+)", regex::optimize);
+static vector<u32> *s;  // the structure of the currently selected input
+
+// FUNCTIONS
+
+/* This function is called once after everything is set up but before
+   any fuzzing attempt has been performed.
+   This is called in afl_custom_queue_get() */
+static void first_run(void *data) {
+
+  (void)(data);
+
+  /* For auto-loading this module we check here if we can analyze from the
+     input if the inputs look like text inputs and disable the module if
+     not. */
+
+  if (afl_ptr->custom_only || !auto_disable) { return; }
+
+  if (unlikely(afl_ptr->active_items == 1 &&
+               afl_ptr->queue_cur->len < AFL_TXT_MIN_LEN)) {
+
+    if (afl_ptr->extras_cnt > 8) {
+
+      u32 valid = 0;
+
+      while (extras_cnt < afl_ptr->extras_cnt) {
+
+        u32 ok = 1, l = afl_ptr->extras[extras_cnt].len;
+        u8 *buf, *ptr = afl_ptr->extras[extras_cnt].data;
+
+        for (u32 i = 0; i < l; ++i) {
+
+          if (!isascii((int)ptr[i]) && !isprint((int)ptr[i])) {
+
+            ok = 0;
+            break;
+
+          }
+
+        }
+
+        if (ok) {
+
+          buf = (u8 *)malloc(afl_ptr->extras[extras_cnt].len + 1);
+          memcpy(buf, afl_ptr->extras[extras_cnt].data,
+                 afl_ptr->extras[extras_cnt].len);
+          buf[afl_ptr->extras[extras_cnt].len] = 0;
+          token_to_id[(char *)buf] = current_id;
+          id_to_token[current_id] = (char *)buf;
+          ++current_id;
+          ++valid;
+
+        }
+
+        ++extras_cnt;
+
+      }
+
+      if ((valid * 100) / afl_ptr->extras_cnt <= 70) { module_disabled = 1; }
+
+      DEBUGF(stderr, "DICT: total %u, valid %u, %u <= 70 == disable\n",
+             afl_ptr->extras_cnt, valid,
+             (u32)((valid * 100) / afl_ptr->extras_cnt));
+
+    } else {
+
+      module_disabled = 1;
+
+    }
+
+    return;
+
+  }
+
+  u32 is_ascii = 0, valid = 0;
+
+  for (u32 i = 0; i < afl_ptr->queued_items; ++i) {
+
+    struct queue_entry *q;
+
+    q = afl_ptr->queue_buf[i];
+
+    if (!q->disabled && q->len >= AUTOTOKENS_SIZE_MIN &&
+        q->len <= AFL_TXT_MAX_LEN) {
+
+      ++valid;
+      u8 *input = queue_testcase_get(afl_ptr, q);
+
+      u32 valid_chars = 0;
+      for (u32 i = 0; i < q->len; ++i) {
+
+        if (isascii((int)input[i]) || isprint((int)input[i])) { ++valid_chars; }
+
+      }
+
+      // we want at least 99% of text characters ...
+      if (((q->len * AFL_TXT_MIN_PERCENT) / 100) <= valid_chars) {
+
+        ++is_ascii;
+        q->is_ascii = 1;
+
+      }
+
+    }
+
+  }
+
+  if ((is_ascii * 100) / valid <= 70) { module_disabled = 1; }
+
+  DEBUGF(stderr, "seeds: total %u, valid %u, ascii %u, %u <= 70 == disabled\n",
+         afl_ptr->active_items, valid, is_ascii,
+         (u32)((is_ascii * 100) / valid));
+
+}
+
+static u32 good_whitespace_or_singleval() {
+
+  u32 i = rand_below(afl_ptr, current_id);
+  if (id_to_token[i].size() == 1) { return i; }
+  i = rand_below(afl_ptr, all_ws);
+  if (i < all_spaces) {
+
+    return 0;
+
+  } else if (i < all_tabs) {
+
+    return 1;
+
+  } else
+
+    return 2;  // linefeed
+
+}
+
+extern "C" u32 afl_custom_fuzz_count(void *data, const u8 *buf,
+                                     size_t buf_size) {
+
+  (void)(data);
+
+  if (s == NULL) return 0;
+
+  u32 shift = unlikely(afl_ptr->custom_only) ? 7 : 8;
+  u32 stage_max = (u32)((HAVOC_CYCLES * afl_ptr->queue_cur->perf_score) /
+                        afl_ptr->havoc_div) >>
+                  shift;
+  if (fuzz_count_shift) { stage_max >>= (u32)fuzz_count_shift; };
+  DEBUGF(stderr, "fuzz count: %u\n", stage_max);
+
+  return stage_max;
+
+}
+
+extern "C" size_t afl_custom_fuzz(my_mutator_t *data, u8 *buf, size_t buf_size,
+                                  u8 **out_buf, u8 *add_buf,
+                                  size_t add_buf_size, size_t max_size) {
+
+  (void)(data);
+
+  if (unlikely(s == NULL)) {
+
+    *out_buf = NULL;
+    return 0;
+
+  }
+
+  vector<u32> m = *s;  // copy of the structure we will modify
+  u32         i, m_size = (u32)m.size();
+
+  u32 rounds =
+      MIN(change_max,
+          MAX(change_min,
+              MIN(m_size >> 3, HAVOC_CYCLES * afl_ptr->queue_cur->perf_score *
+                                   afl_ptr->havoc_div / 256)));
+  // DEBUGF(stderr, "structure size: %lu, rounds: %u \n", m.size(), rounds);
+
+#if AUTOTOKENS_SPLICE_DISABLE == 1
+  #define AUTOTOKENS_MUT_MAX 18
+#else
+  #define AUTOTOKENS_MUT_MAX 27
+#endif
+
+  u32 max_rand = AUTOTOKENS_MUT_MAX, new_item, pos;
+
+  for (i = 0; i < rounds; ++i) {
+
+    switch (rand_below(afl_ptr, max_rand)) {
+
+      /* CHANGE/MUTATE single item */
+      case 0 ... 9: {
+
+        pos = rand_below(afl_ptr, m_size);
+        u32 cur_item = m[pos];
+        do {
+
+          new_item = rand_below(afl_ptr, current_id);
+
+        } while (unlikely(
+
+            new_item == cur_item ||
+            ((whitespace_ids < new_item && whitespace_ids >= cur_item) ||
+             (whitespace_ids >= new_item && whitespace_ids < cur_item))));
+
+        // DEBUGF(stderr, "MUT: %u -> %u\n", cur_item, new_item);
+        m[pos] = new_item;
+        break;
+
+      }
+
+      /* INSERT (m_size +1 so we insert also after last place) */
+      case 10 ... 13: {
+
+        do {
+
+          new_item = rand_below(afl_ptr, current_id);
+
+        } while (unlikely(new_item >= whitespace_ids));
+
+        u32 pos = rand_below(afl_ptr, m_size + 1);
+        m.insert(m.begin() + pos, new_item);
+        ++m_size;
+        // DEBUGF(stderr, "INS: %u at %u\n", new_item, pos);
+
+        break;
+
+      }
+
+#if AUTOTOKENS_SPLICE_DISABLE != 1
+      /* SPLICING */
+      case 14 ... 22: {
+
+        u32  strategy = rand_below(afl_ptr, 4), dst_off, n;
+        auto src = id_mapping[rand_below(afl_ptr, valid_structures)];
+        u32  src_size = src->size();
+        u32  src_off = rand_below(afl_ptr, src_size - AUTOTOKENS_SPLICE_MIN);
+        u32  rand_r = 1 + MAX(AUTOTOKENS_SPLICE_MIN,
+                              MIN(AUTOTOKENS_SPLICE_MAX, src_size - src_off));
+
+        switch (strategy) {
+
+          // insert
+          case 0: {
+
+            dst_off = rand_below(afl_ptr, m_size);
+            n = AUTOTOKENS_SPLICE_MIN +
+                rand_below(afl_ptr, MIN(AUTOTOKENS_SPLICE_MAX,
+                                        rand_r - AUTOTOKENS_SPLICE_MIN));
+            m.insert(m.begin() + dst_off, src->begin() + src_off,
+                     src->begin() + src_off + n);
+            m_size += n;
+            // DEBUGF(stderr, "SPLICE-INS: %u at %u\n", n, dst_off);
+
+            break;
+
+          }
+
+          // overwrite
+          default: {
+
+            dst_off = rand_below(afl_ptr, m_size - AUTOTOKENS_SPLICE_MIN);
+            n = AUTOTOKENS_SPLICE_MIN +
+                rand_below(
+                    afl_ptr,
+                    MIN(AUTOTOKENS_SPLICE_MAX - AUTOTOKENS_SPLICE_MIN,
+                        MIN(m_size - dst_off - AUTOTOKENS_SPLICE_MIN,
+                            src_size - src_off - AUTOTOKENS_SPLICE_MIN)));
+
+            copy(src->begin() + src_off, src->begin() + src_off + n,
+                 m.begin() + dst_off);
+
+            // DEBUGF(stderr, "SPLICE-MUT: %u at %u\n", n, dst_off);
+            break;
+
+          }
+
+        }
+
+        break;
+
+      }
+
+#endif
+
+      /* ERASE - only if large enough */
+      default: {
+
+        if (m_size > 8) {
+
+          do {
+
+            pos = rand_below(afl_ptr, m_size);
+
+          } while (unlikely(m[pos] < whitespace_ids));
+
+          m.erase(m.begin() + pos);
+          --m_size;
+
+        } else {
+
+          // if the data is already too small do not try to make it smaller
+          // again this run.
+
+          max_rand -= 4;
+
+        }
+
+        break;
+
+      }
+
+    }
+
+  }
+
+  /* Now we create the output */
+
+  output = "";
+  u32 prev_size = 1, was_whitespace = 1;
+
+  for (i = 0; i < m_size; ++i) {
+
+    if (likely(i + 1 < m_size)) {
+
+      u32 this_size = id_to_token[m[i]].size();
+      u32 is_whitespace = m[i] < whitespace_ids;
+
+      /* The output we are generating might need repairing.
+         General rule: two items that have a size larger than 2 are strings
+         or identifizers and need a whitespace or an item of length 1 in
+         between. */
+      if (unlikely(!(prev_size == 1 || was_whitespace || this_size == 1 ||
+                     is_whitespace))) {
+
+        output += id_to_token[good_whitespace_or_singleval()];
+
+      }
+
+      prev_size = this_size;
+      was_whitespace = is_whitespace;
+
+    }
+
+    output += id_to_token[m[i]];
+
+  }
+
+  u32 mutated_size = (u32)output.size();
+  u8 *mutated_out = (u8 *)output.data();
+
+  if (unlikely(mutated_size > max_size)) { mutated_size = max_size; }
+
+  /*
+  IFDEBUG {
+
+    DEBUGF(stderr, "MUTATED to %u bytes:\n", mutated_size);
+    fwrite(output.data(), 1, mutated_size, stderr);
+    DEBUGF(stderr, "\n---\n");
+
+  }
+
+  */
+
+  *out_buf = mutated_out;
+  ++fuzz_count;
+  return mutated_size;
+
+}
+
+/* I get f*cking stack overflow using C++ regex with a regex of
+   "\"[[:print:]]*?\"" if this matches a long string even with regex::optimize
+   enabled :-( */
+static u8 my_search_string(string::const_iterator  cur,
+                           string::const_iterator  ende,
+                           string::const_iterator *match_begin,
+                           string::const_iterator *match_end) {
+
+  string::const_iterator start = cur, found_begin;
+  u8                     quote_type = 0;
+
+  while (cur < ende) {
+
+    switch (*cur) {
+
+      case '"': {
+
+        if (cur == start || *(cur - 1) != '\\') {
+
+          if (!quote_type) {
+
+            found_begin = cur;
+            quote_type = 1;
+
+          } else if (quote_type == 1) {
+
+            *match_begin = found_begin;
+            *match_end = cur + 1;
+            return 1;
+
+          }
+
+        }
+
+        break;
+
+      }
+
+      case '\'': {
+
+        if (cur == start || *(cur - 1) != '\\') {
+
+          if (!quote_type) {
+
+            found_begin = cur;
+            quote_type = 2;
+
+          } else if (quote_type == 2) {
+
+            *match_begin = found_begin;
+            *match_end = cur + 1;
+            return 1;
+
+          }
+
+        }
+
+        break;
+
+      }
+
+      case '\n':
+      case '\r':
+      case 0: {
+
+        quote_type = 0;
+        break;
+
+      }
+
+      default:
+        if (unlikely(quote_type && !isprint(*cur))) { quote_type = 0; }
+        break;
+
+    }
+
+    ++cur;
+
+  }
+
+  return 0;
+
+}
+
+/* We are not using afl_custom_queue_new_entry() because not every corpus entry
+   will be necessarily fuzzed with this custom mutator.
+   So we use afl_custom_queue_get() instead. */
+
+extern "C" unsigned char afl_custom_queue_get(void                *data,
+                                              const unsigned char *filename) {
+
+  static int learn_state = 0;
+  static int is_first_run = 1;
+  (void)(data);
+
+  if (unlikely(is_first_run)) {
+
+    is_first_run = 0;
+    first_run(data);
+
+    if (module_disabled) {
+
+      WARNF("Autotokens custom module is disabled.");
+
+    } else if (auto_disable) {
+
+      OKF("Autotokens custom module is enabled.");
+
+    }
+
+  }
+
+  if (likely(module_disabled) ||
+      (unlikely(!afl_ptr->custom_only) && !create_from_thin_air &&
+       ((afl_ptr->shm.cmplog_mode && !afl_ptr->queue_cur->is_ascii) ||
+        (only_fav && !afl_ptr->queue_cur->favored)))) {
+
+    s = NULL;
+    DEBUGF(stderr,
+           "cmplog not ascii or only_fav and not favorite or disabled\n");
+    return 1;
+
+  }
+
+  // check if there are new dictionary entries and add them to the tokens
+  if (unlikely(learn_state < learn_dictionary_tokens) &&
+      likely(valid_structures || create_from_thin_air)) {
+
+    if (unlikely(!learn_state)) { learn_state = 1; }
+
+    while (extras_cnt < afl_ptr->extras_cnt) {
+
+      u32 ok = 1, l = afl_ptr->extras[extras_cnt].len;
+      u8 *buf, *ptr = afl_ptr->extras[extras_cnt].data;
+
+      for (u32 i = 0; i < l; ++i) {
+
+        if (!isascii((int)ptr[i]) && !isprint((int)ptr[i])) {
+
+          ok = 0;
+          break;
+
+        }
+
+      }
+
+      if (ok) {
+
+        buf = (u8 *)malloc(afl_ptr->extras[extras_cnt].len + 1);
+        memcpy(buf, afl_ptr->extras[extras_cnt].data,
+               afl_ptr->extras[extras_cnt].len);
+        buf[afl_ptr->extras[extras_cnt].len] = 0;
+        token_to_id[(char *)buf] = current_id;
+        id_to_token[current_id] = (char *)buf;
+        ++current_id;
+
+      }
+
+      ++extras_cnt;
+
+    }
+
+    while (a_extras_cnt < afl_ptr->a_extras_cnt) {
+
+      u32 ok = 1, l = afl_ptr->a_extras[a_extras_cnt].len;
+      u8 *ptr = afl_ptr->a_extras[a_extras_cnt].data;
+
+      for (u32 i = 0; i < l; ++i) {
+
+        if (!isascii((int)ptr[i]) && !isprint((int)ptr[i])) {
+
+          ok = 0;
+          break;
+
+        }
+
+      }
+
+      if (ok) {
+
+        token_to_id[(char *)ptr] = current_id;
+        id_to_token[current_id] = (char *)ptr;
+        ++current_id;
+
+      }
+
+      ++a_extras_cnt;
+
+    }
+
+  }
+
+  vector<u32> *structure = NULL;
+  string       fn = (char *)filename;
+  auto         entry = file_mapping.find(fn);
+
+  // if there is only one active queue item at start and it is very small
+  // the we create once a structure randomly.
+  if (unlikely(create_from_thin_air)) {
+
+    if (current_id > whitespace_ids + 6 && afl_ptr->active_items == 1 &&
+        afl_ptr->queue_cur->len < AFL_TXT_MIN_LEN) {
+
+      DEBUGF(stderr, "Creating an entry from thin air...\n");
+      structure = new vector<u32>();
+      u32 item, prev, cnt = current_id >> 1;
+      structure->reserve(cnt + 4);
+      for (u32 i = 0; i < cnt; i++) {
+
+        item = rand_below(afl_ptr, current_id);
+        if (i && id_to_token[item].length() > 1 &&
+            id_to_token[prev].length() > 1) {
+
+          structure->push_back(good_whitespace_or_singleval());
+
+        }
+
+        structure->push_back(item);
+        prev = item;
+
+      }
+
+      s = structure;
+      file_mapping[fn] = structure;
+      id_mapping[valid_structures] = structure;
+      ++valid_structures;
+      all_structure_items += structure->size();
+
+      return 1;
+
+    }
+
+    create_from_thin_air = 0;
+
+  }
+
+  if (entry == file_mapping.end()) {
+
+    // this input file was not analyzed for tokens yet, so let's do it!
+    size_t len = afl_ptr->queue_cur->len;
+
+    if (len < AFL_TXT_MIN_LEN) {
+
+      file_mapping[fn] = structure;  // NULL ptr so we don't read the file again
+      s = NULL;
+      DEBUGF(stderr, "Too short (%lu) %s\n", len, filename);
+      return 1;
+
+    } else if (len > AFL_TXT_MAX_LEN) {
+
+      file_mapping[fn] = structure;  // NULL ptr so we don't read the file again
+      s = NULL;
+      DEBUGF(stderr, "Too long (%lu) %s\n", len, filename);
+      return 1;
+
+    }
+
+    u8    *input_buf = queue_testcase_get(afl_ptr, afl_ptr->queue_cur);
+    string input((char *)input_buf, afl_ptr->queue_cur->len);
+
+    if (!afl_ptr->shm.cmplog_mode) {
+
+      // not running with CMPLOG? bad choice, but whatever ...
+      // we only want text inputs, so we have to check it ourselves.
+
+      u32 valid_chars = 0;
+      for (u32 i = 0; i < len; ++i) {
+
+        if (isascii((int)input[i]) || isprint((int)input[i])) { ++valid_chars; }
+
+      }
+
+      // we want at least 95% of text characters ...
+      if (((len * AFL_TXT_MIN_PERCENT) / 100) > valid_chars) {
+
+        file_mapping[fn] = NULL;
+        s = NULL;
+        DEBUGF(stderr, "Not text (%lu) %s\n", len, filename);
+        return 1;
+
+      }
+
+    }
+
+    // DEBUGF(stderr, "Read %lu bytes for %s\nBefore comment trim:\n%s\n",
+    // input.size(), filename, input.c_str());
+
+    if (regex_comment_custom) {
+
+      input = regex_replace(input, *regex_comment_custom, "$2");
+
+    } else {
+
+      input = regex_replace(input, regex_comment_star, "");
+
+    }
+
+    DEBUGF(stderr, "After replace %lu bytes for %s\n%s\n", input.size(),
+           filename, input.c_str());
+
+    u32  spaces = count(input.begin(), input.end(), ' ');
+    u32  tabs = count(input.begin(), input.end(), '\t');
+    u32  linefeeds = count(input.begin(), input.end(), '\n');
+    bool ends_with_linefeed = input[input.length() - 1] == '\n';
+
+    DEBUGF(stderr, "spaces=%u tabs=%u linefeeds=%u ends=%u\n", spaces, tabs,
+           linefeeds, ends_with_linefeed);
+
+    all_spaces += spaces;
+    all_tabs += tabs;
+    all_lf += linefeeds;
+    all_ws = all_spaces + all_tabs + all_lf;
+
+    // now extract all tokens
+    vector<string>         tokens;
+    string::const_iterator cur = input.begin(), ende = input.end(), found, prev,
+                           match_begin, match_end;
+
+    DEBUGF(stderr, "START!\n");
+
+    while (my_search_string(cur, ende, &match_begin, &match_end)) {
+
+      prev = cur;
+      found = match_begin;
+      cur = match_end;
+
+      IFDEBUG {
+
+        string foo(match_begin, match_end);
+        DEBUGF(stderr,
+               "string %s found at start %lu offset %lu continue at %lu\n",
+               foo.c_str(), prev - input.begin(), found - prev,
+               cur - input.begin());
+
+      }
+
+      if (prev < found) {  // there are items between search start and find
+        while (prev < found) {
+
+          if (isspace(*prev)) {
+
+            auto start = prev;
+            while (isspace(*prev)) {
+
+              ++prev;
+
+            }
+
+            tokens.push_back(std::string(start, prev));
+            DEBUGF(stderr, "WHITESPACE %ld \"%s\"\n", prev - start,
+                   tokens[tokens.size() - 1].c_str());
+
+          } else if (isalnum(*prev) || *prev == '$' || *prev == '_') {
+
+            auto start = prev;
+            while (isalnum(*prev) || *prev == '$' || *prev == '_' ||
+                   *prev == '.' || *prev == '/') {
+
+              ++prev;
+
+            }
+
+            tokens.push_back(string(start, prev));
+            DEBUGF(stderr, "IDENTIFIER %ld \"%s\"\n", prev - start,
+                   tokens[tokens.size() - 1].c_str());
+
+          } else {
+
+            tokens.push_back(string(prev, prev + 1));
+            DEBUGF(stderr, "OTHER \"%c\"\n", *prev);
+            ++prev;
+
+          }
+
+        }
+
+      }
+
+      tokens.push_back(string(match_begin, match_end));
+      DEBUGF(stderr, "TOK: %s\n", tokens[tokens.size() - 1].c_str());
+
+    }
+
+    DEBUGF(stderr, "AFTER all strings\n");
+
+    if (cur < ende) {
+
+      while (cur < ende) {
+
+        if (isspace(*cur)) {
+
+          auto start = cur;
+          while (isspace(*cur)) {
+
+            ++cur;
+
+          }
+
+          tokens.push_back(std::string(start, cur));
+          DEBUGF(stderr, "WHITESPACE %ld \"%s\"\n", cur - start,
+                 tokens[tokens.size() - 1].c_str());
+
+        } else if (isalnum(*cur) || *cur == '$' || *cur == '_') {
+
+          auto start = cur;
+          while (isalnum(*cur) || *cur == '$' || *cur == '_' || *cur == '.' ||
+                 *cur == '/') {
+
+            ++cur;
+
+          }
+
+          tokens.push_back(std::string(start, cur));
+          DEBUGF(stderr, "IDENTIFIER %ld \"%s\"\n", cur - start,
+                 tokens[tokens.size() - 1].c_str());
+
+        } else {
+
+          tokens.push_back(std::string(cur, cur + 1));
+          DEBUGF(stderr, "OTHER \"%c\"\n", *cur);
+          ++cur;
+
+        }
+
+      }
+
+    }
+
+    IFDEBUG {
+
+      DEBUGF(stderr, "DUMPING TOKENS:\n");
+      for (u32 i = 0; i < tokens.size(); ++i) {
+
+        DEBUGF(stderr, "%s", tokens[i].c_str());
+
+      }
+
+      DEBUGF(stderr, "---------------------------\n");
+
+    }
+
+    if (tokens.size() < AUTOTOKENS_SIZE_MIN) {
+
+      file_mapping[fn] = NULL;
+      s = NULL;
+      DEBUGF(stderr, "too few tokens\n");
+      return 1;
+
+    }
+
+    /* Now we transform the tokens into an ID list and saved that */
+
+    structure = new vector<u32>();
+    u32 id;
+
+    for (u32 i = 0; i < tokens.size(); ++i) {
+
+      if ((id = token_to_id[tokens[i]]) == 0) {
+
+        // First time we see this token, add it to the list
+        token_to_id[tokens[i]] = current_id;
+        id_to_token[current_id] = tokens[i];
+        structure->push_back(current_id);
+        ++current_id;
+
+      } else {
+
+        structure->push_back(id);
+
+      }
+
+    }
+
+    // save the token structure to the file mapping
+    file_mapping[fn] = structure;
+    id_mapping[valid_structures] = structure;
+    ++valid_structures;
+    s = structure;
+    all_structure_items += structure->size();
+
+    // we are done!
+    DEBUGF(stderr, "DONE! We have %lu tokens in the structure\n",
+           structure->size());
+
+  } else {
+
+    if (entry->second == NULL) {
+
+      DEBUGF(stderr, "Skipping %s\n", filename);
+      s = NULL;
+      return 1;
+
+    }
+
+    s = entry->second;
+    DEBUGF(stderr, "OK %s\n", filename);
+
+  }
+
+  return 1;  // we always fuzz unless non-ascii or too small
+
+}
+
+extern "C" my_mutator_t *afl_custom_init(afl_state *afl, unsigned int seed) {
+
+  (void)(seed);
+  my_mutator_t *data = (my_mutator_t *)calloc(1, sizeof(my_mutator_t));
+  if (!data) {
+
+    perror("afl_custom_init alloc");
+    return NULL;
+
+  }
+
+  if (getenv("AUTOTOKENS_DEBUG")) { debug = 1; }
+  if (getenv("AUTOTOKENS_AUTO_DISABLE")) { auto_disable = 1; }
+  if (getenv("AUTOTOKENS_ONLY_FAV")) { only_fav = 1; }
+  if (getenv("AUTOTOKENS_CREATE_FROM_THIN_AIR")) { create_from_thin_air = 1; }
+
+  if (getenv("AUTOTOKENS_LEARN_DICT")) {
+
+    learn_dictionary_tokens = atoi(getenv("AUTOTOKENS_LEARN_DICT"));
+    if (learn_dictionary_tokens < 0 || learn_dictionary_tokens > 2) {
+
+      learn_dictionary_tokens = AUTOTOKENS_LEARN_DICT;
+
+    }
+
+  }
+
+  if (getenv("AUTOTOKENS_FUZZ_COUNT_SHIFT")) {
+
+    fuzz_count_shift = atoi(getenv("AUTOTOKENS_FUZZ_COUNT_SHIFT"));
+    if (fuzz_count_shift < 0 || fuzz_count_shift > 16) { fuzz_count_shift = 0; }
+
+  }
+
+  if (getenv("AUTOTOKENS_CHANGE_MIN")) {
+
+    change_min = atoi(getenv("AUTOTOKENS_CHANGE_MIN"));
+    if (change_min < 1 || change_min > 256) {
+
+      change_min = AUTOTOKENS_CHANGE_MIN;
+
+    }
+
+  }
+
+  if (getenv("AUTOTOKENS_CHANGE_MAX")) {
+
+    change_max = atoi(getenv("AUTOTOKENS_CHANGE_MAX"));
+    if (change_max < 1 || change_max > 4096) {
+
+      change_max = AUTOTOKENS_CHANGE_MAX;
+
+    }
+
+  }
+
+  if (change_max < change_min) { change_max = change_min + 1; }
+
+  if (getenv("AUTOTOKENS_COMMENT")) {
+
+    char buf[256];
+    snprintf(buf, sizeof(buf), "(%s.*)([\r\n]?)", getenv("AUTOTOKENS_COMMENT"));
+    regex_comment_custom = new regex(buf, regex::optimize);
+
+  }
+
+  data->afl = afl_ptr = afl;
+
+  // set common whitespace tokens
+  // we deliberately do not put uncommon ones here to these will count as
+  // identifier tokens.
+  token_to_id[" "] = current_id;
+  id_to_token[current_id] = " ";
+  ++current_id;
+  token_to_id["\t"] = current_id;
+  id_to_token[current_id] = "\t";
+  ++current_id;
+  token_to_id["\n"] = current_id;
+  id_to_token[current_id] = "\n";
+  ++current_id;
+  token_to_id["\r\n"] = current_id;
+  id_to_token[current_id] = "\r\n";
+  ++current_id;
+  token_to_id[" \n"] = current_id;
+  id_to_token[current_id] = " \n";
+  ++current_id;
+  token_to_id["  "] = current_id;
+  id_to_token[current_id] = "  ";
+  ++current_id;
+  token_to_id["\t\t"] = current_id;
+  id_to_token[current_id] = "\t\t";
+  ++current_id;
+  token_to_id["\n\n"] = current_id;
+  id_to_token[current_id] = "\n\n";
+  ++current_id;
+  token_to_id["\r\n\r\n"] = current_id;
+  id_to_token[current_id] = "\r\n\r\n";
+  ++current_id;
+  token_to_id["    "] = current_id;
+  id_to_token[current_id] = "    ";
+  ++current_id;
+  token_to_id["\t\t\t\t"] = current_id;
+  id_to_token[current_id] = "\t\t\t\t";
+  ++current_id;
+  token_to_id["\n\n\n\n"] = current_id;
+  id_to_token[current_id] = "\n\n\n\n";
+  ++current_id;
+  whitespace_ids = current_id;
+  token_to_id["\""] = current_id;
+  id_to_token[current_id] = "\"";
+  ++current_id;
+  token_to_id["'"] = current_id;
+  id_to_token[current_id] = "'";
+  ++current_id;
+
+  return data;
+
+}
+
+extern "C" void afl_custom_splice_optout(my_mutator_t *data) {
+
+  (void)(data);
+
+}
+
+extern "C" void afl_custom_deinit(my_mutator_t *data) {
+
+  /* we use this to print statistics at exit :-)
+     needs to be stderr as stdout is filtered */
+
+  if (module_disabled) { return; }
+
+  fprintf(stderr,
+          "\n\nAutotoken mutator statistics:\n"
+          "  Number of all seen tokens:  %u\n"
+          "  Number of input structures: %u\n"
+          "  Number of all items in structures: %llu\n"
+          "  Number of total fuzzes: %llu\n\n",
+          current_id - 1, valid_structures, all_structure_items, fuzz_count);
+
+  free(data);
+
+}
+
diff --git a/custom_mutators/examples/custom_mutator_helpers.h b/custom_mutators/examples/custom_mutator_helpers.h
deleted file mode 100644
index 62e6efba..00000000
--- a/custom_mutators/examples/custom_mutator_helpers.h
+++ /dev/null
@@ -1,342 +0,0 @@
-#ifndef CUSTOM_MUTATOR_HELPERS
-#define CUSTOM_MUTATOR_HELPERS
-
-#include "config.h"
-#include "types.h"
-#include <stdlib.h>
-
-#define INITIAL_GROWTH_SIZE (64)
-
-#define RAND_BELOW(limit) (rand() % (limit))
-
-/* Use in a struct: creates a name_buf and a name_size variable. */
-#define BUF_VAR(type, name) \
-  type * name##_buf;        \
-  size_t name##_size;
-/* this fills in `&structptr->something_buf, &structptr->something_size`. */
-#define BUF_PARAMS(struct, name) \
-  (void **)&struct->name##_buf, &struct->name##_size
-
-typedef struct {
-
-} afl_t;
-
-static void surgical_havoc_mutate(u8 *out_buf, s32 begin, s32 end) {
-
-  static s8  interesting_8[] = {INTERESTING_8};
-  static s16 interesting_16[] = {INTERESTING_8, INTERESTING_16};
-  static s32 interesting_32[] = {INTERESTING_8, INTERESTING_16, INTERESTING_32};
-
-  switch (RAND_BELOW(12)) {
-
-    case 0: {
-
-      /* Flip a single bit somewhere. Spooky! */
-
-      s32 bit_idx = ((RAND_BELOW(end - begin) + begin) << 3) + RAND_BELOW(8);
-
-      out_buf[bit_idx >> 3] ^= 128 >> (bit_idx & 7);
-
-      break;
-
-    }
-
-    case 1: {
-
-      /* Set byte to interesting value. */
-
-      u8 val = interesting_8[RAND_BELOW(sizeof(interesting_8))];
-      out_buf[(RAND_BELOW(end - begin) + begin)] = val;
-
-      break;
-
-    }
-
-    case 2: {
-
-      /* Set word to interesting value, randomly choosing endian. */
-
-      if (end - begin < 2) break;
-
-      s32 byte_idx = (RAND_BELOW(end - begin) + begin);
-
-      if (byte_idx >= end - 1) break;
-
-      switch (RAND_BELOW(2)) {
-
-        case 0:
-          *(u16 *)(out_buf + byte_idx) =
-              interesting_16[RAND_BELOW(sizeof(interesting_16) >> 1)];
-          break;
-        case 1:
-          *(u16 *)(out_buf + byte_idx) =
-              SWAP16(interesting_16[RAND_BELOW(sizeof(interesting_16) >> 1)]);
-          break;
-
-      }
-
-      break;
-
-    }
-
-    case 3: {
-
-      /* Set dword to interesting value, randomly choosing endian. */
-
-      if (end - begin < 4) break;
-
-      s32 byte_idx = (RAND_BELOW(end - begin) + begin);
-
-      if (byte_idx >= end - 3) break;
-
-      switch (RAND_BELOW(2)) {
-
-        case 0:
-          *(u32 *)(out_buf + byte_idx) =
-              interesting_32[RAND_BELOW(sizeof(interesting_32) >> 2)];
-          break;
-        case 1:
-          *(u32 *)(out_buf + byte_idx) =
-              SWAP32(interesting_32[RAND_BELOW(sizeof(interesting_32) >> 2)]);
-          break;
-
-      }
-
-      break;
-
-    }
-
-    case 4: {
-
-      /* Set qword to interesting value, randomly choosing endian. */
-
-      if (end - begin < 8) break;
-
-      s32 byte_idx = (RAND_BELOW(end - begin) + begin);
-
-      if (byte_idx >= end - 7) break;
-
-      switch (RAND_BELOW(2)) {
-
-        case 0:
-          *(u64 *)(out_buf + byte_idx) =
-              (s64)interesting_32[RAND_BELOW(sizeof(interesting_32) >> 2)];
-          break;
-        case 1:
-          *(u64 *)(out_buf + byte_idx) = SWAP64(
-              (s64)interesting_32[RAND_BELOW(sizeof(interesting_32) >> 2)]);
-          break;
-
-      }
-
-      break;
-
-    }
-
-    case 5: {
-
-      /* Randomly subtract from byte. */
-
-      out_buf[(RAND_BELOW(end - begin) + begin)] -= 1 + RAND_BELOW(ARITH_MAX);
-
-      break;
-
-    }
-
-    case 6: {
-
-      /* Randomly add to byte. */
-
-      out_buf[(RAND_BELOW(end - begin) + begin)] += 1 + RAND_BELOW(ARITH_MAX);
-
-      break;
-
-    }
-
-    case 7: {
-
-      /* Randomly subtract from word, random endian. */
-
-      if (end - begin < 2) break;
-
-      s32 byte_idx = (RAND_BELOW(end - begin) + begin);
-
-      if (byte_idx >= end - 1) break;
-
-      if (RAND_BELOW(2)) {
-
-        *(u16 *)(out_buf + byte_idx) -= 1 + RAND_BELOW(ARITH_MAX);
-
-      } else {
-
-        u16 num = 1 + RAND_BELOW(ARITH_MAX);
-
-        *(u16 *)(out_buf + byte_idx) =
-            SWAP16(SWAP16(*(u16 *)(out_buf + byte_idx)) - num);
-
-      }
-
-      break;
-
-    }
-
-    case 8: {
-
-      /* Randomly add to word, random endian. */
-
-      if (end - begin < 2) break;
-
-      s32 byte_idx = (RAND_BELOW(end - begin) + begin);
-
-      if (byte_idx >= end - 1) break;
-
-      if (RAND_BELOW(2)) {
-
-        *(u16 *)(out_buf + byte_idx) += 1 + RAND_BELOW(ARITH_MAX);
-
-      } else {
-
-        u16 num = 1 + RAND_BELOW(ARITH_MAX);
-
-        *(u16 *)(out_buf + byte_idx) =
-            SWAP16(SWAP16(*(u16 *)(out_buf + byte_idx)) + num);
-
-      }
-
-      break;
-
-    }
-
-    case 9: {
-
-      /* Randomly subtract from dword, random endian. */
-
-      if (end - begin < 4) break;
-
-      s32 byte_idx = (RAND_BELOW(end - begin) + begin);
-
-      if (byte_idx >= end - 3) break;
-
-      if (RAND_BELOW(2)) {
-
-        *(u32 *)(out_buf + byte_idx) -= 1 + RAND_BELOW(ARITH_MAX);
-
-      } else {
-
-        u32 num = 1 + RAND_BELOW(ARITH_MAX);
-
-        *(u32 *)(out_buf + byte_idx) =
-            SWAP32(SWAP32(*(u32 *)(out_buf + byte_idx)) - num);
-
-      }
-
-      break;
-
-    }
-
-    case 10: {
-
-      /* Randomly add to dword, random endian. */
-
-      if (end - begin < 4) break;
-
-      s32 byte_idx = (RAND_BELOW(end - begin) + begin);
-
-      if (byte_idx >= end - 3) break;
-
-      if (RAND_BELOW(2)) {
-
-        *(u32 *)(out_buf + byte_idx) += 1 + RAND_BELOW(ARITH_MAX);
-
-      } else {
-
-        u32 num = 1 + RAND_BELOW(ARITH_MAX);
-
-        *(u32 *)(out_buf + byte_idx) =
-            SWAP32(SWAP32(*(u32 *)(out_buf + byte_idx)) + num);
-
-      }
-
-      break;
-
-    }
-
-    case 11: {
-
-      /* Just set a random byte to a random value. Because,
-         why not. We use XOR with 1-255 to eliminate the
-         possibility of a no-op. */
-
-      out_buf[(RAND_BELOW(end - begin) + begin)] ^= 1 + RAND_BELOW(255);
-
-      break;
-
-    }
-
-  }
-
-}
-
-/* This function calculates the next power of 2 greater or equal its argument.
- @return The rounded up power of 2 (if no overflow) or 0 on overflow.
-*/
-static inline size_t next_pow2(size_t in) {
-
-  if (in == 0 || in > (size_t)-1)
-    return 0;                  /* avoid undefined behaviour under-/overflow */
-  size_t out = in - 1;
-  out |= out >> 1;
-  out |= out >> 2;
-  out |= out >> 4;
-  out |= out >> 8;
-  out |= out >> 16;
-  return out + 1;
-
-}
-
-/* This function makes sure *size is > size_needed after call.
- It will realloc *buf otherwise.
- *size will grow exponentially as per:
- https://blog.mozilla.org/nnethercote/2014/11/04/please-grow-your-buffers-exponentially/
- Will return NULL and free *buf if size_needed is <1 or realloc failed.
- @return For convenience, this function returns *buf.
- */
-static inline void *maybe_grow(void **buf, size_t *size, size_t size_needed) {
-
-  /* No need to realloc */
-  if (likely(size_needed && *size >= size_needed)) return *buf;
-
-  /* No initial size was set */
-  if (size_needed < INITIAL_GROWTH_SIZE) size_needed = INITIAL_GROWTH_SIZE;
-
-  /* grow exponentially */
-  size_t next_size = next_pow2(size_needed);
-
-  /* handle overflow */
-  if (!next_size) { next_size = size_needed; }
-
-  /* alloc */
-  *buf = realloc(*buf, next_size);
-  *size = *buf ? next_size : 0;
-
-  return *buf;
-
-}
-
-/* Swaps buf1 ptr and buf2 ptr, as well as their sizes */
-static inline void afl_swap_bufs(void **buf1, size_t *size1, void **buf2,
-                                 size_t *size2) {
-
-  void * scratch_buf = *buf1;
-  size_t scratch_size = *size1;
-  *buf1 = *buf2;
-  *size1 = *size2;
-  *buf2 = scratch_buf;
-  *size2 = scratch_size;
-
-}
-
-#undef INITIAL_GROWTH_SIZE
-
-#endif
-
diff --git a/custom_mutators/examples/custom_send.c b/custom_mutators/examples/custom_send.c
new file mode 100644
index 00000000..9cc4b160
--- /dev/null
+++ b/custom_mutators/examples/custom_send.c
@@ -0,0 +1,63 @@
+//
+// This is an example on how to use afl_custom_send
+// It writes each mutated data set to /tmp/foo
+// You can modify this to send to IPC, shared memory, etc.
+//
+// cc -O3 -fPIC -shared -g -o custom_send.so -I../../include custom_send.c
+// cd ../..
+// afl-cc -o test-instr test-instr.c
+// AFL_CUSTOM_MUTATOR_LIBRARY=custom_mutators/examples/custom_send.so \
+//   afl-fuzz -i in -o out -- ./test-instr -f /tmp/foo
+//
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "afl-fuzz.h"
+
+typedef struct my_mutator {
+
+  afl_state_t *afl;
+
+} my_mutator_t;
+
+my_mutator_t *afl_custom_init(afl_state_t *afl, unsigned int seed) {
+
+  my_mutator_t *data = calloc(1, sizeof(my_mutator_t));
+  if (!data) {
+
+    perror("afl_custom_init alloc");
+    return NULL;
+
+  }
+
+  data->afl = afl;
+
+  return data;
+
+}
+
+void afl_custom_fuzz_send(my_mutator_t *data, uint8_t *buf, size_t buf_size) {
+
+  int fd = open("/tmp/foo", O_CREAT | O_NOFOLLOW | O_TRUNC | O_RDWR, 0644);
+
+  if (fd >= 0) {
+
+    (void)write(fd, buf, buf_size);
+    close(fd);
+
+  }
+
+  return;
+
+}
+
+void afl_custom_deinit(my_mutator_t *data) {
+
+  free(data);
+
+}
+
diff --git a/custom_mutators/examples/example.c b/custom_mutators/examples/example.c
index 3f299508..42c7469c 100644
--- a/custom_mutators/examples/example.c
+++ b/custom_mutators/examples/example.c
@@ -6,8 +6,8 @@
              Dominik Maier <mail@dmnk.co>
 */
 
-// You need to use -I /path/to/AFLplusplus/include
-#include "custom_mutator_helpers.h"
+// You need to use -I/path/to/AFLplusplus/include -I.
+#include "afl-fuzz.h"
 
 #include <stdint.h>
 #include <stdlib.h>
@@ -26,19 +26,14 @@ static const char *commands[] = {
 
 typedef struct my_mutator {
 
-  afl_t *afl;
+  afl_state_t *afl;
 
   // any additional data here!
   size_t trim_size_current;
   int    trimmming_steps;
   int    cur_step;
 
-  // Reused buffers:
-  BUF_VAR(u8, fuzz);
-  BUF_VAR(u8, data);
-  BUF_VAR(u8, havoc);
-  BUF_VAR(u8, trim);
-  BUF_VAR(u8, post_process);
+  u8 *mutated_out, *post_process_buf, *trim_buf;
 
 } my_mutator_t;
 
@@ -53,7 +48,7 @@ typedef struct my_mutator {
  *         There may be multiple instances of this mutator in one afl-fuzz run!
  *         Return NULL on error.
  */
-my_mutator_t *afl_custom_init(afl_t *afl, unsigned int seed) {
+my_mutator_t *afl_custom_init(afl_state_t *afl, unsigned int seed) {
 
   srand(seed);  // needed also by surgical_havoc_mutate()
 
@@ -65,6 +60,27 @@ my_mutator_t *afl_custom_init(afl_t *afl, unsigned int seed) {
 
   }
 
+  if ((data->mutated_out = (u8 *)malloc(MAX_FILE)) == NULL) {
+
+    perror("afl_custom_init malloc");
+    return NULL;
+
+  }
+
+  if ((data->post_process_buf = (u8 *)malloc(MAX_FILE)) == NULL) {
+
+    perror("afl_custom_init malloc");
+    return NULL;
+
+  }
+
+  if ((data->trim_buf = (u8 *)malloc(MAX_FILE)) == NULL) {
+
+    perror("afl_custom_init malloc");
+    return NULL;
+
+  }
+
   data->afl = afl;
 
   return data;
@@ -96,29 +112,14 @@ size_t afl_custom_fuzz(my_mutator_t *data, uint8_t *buf, size_t buf_size,
   // the fuzzer
   size_t mutated_size = DATA_SIZE <= max_size ? DATA_SIZE : max_size;
 
-  // maybe_grow is optimized to be quick for reused buffers.
-  u8 *mutated_out = maybe_grow(BUF_PARAMS(data, fuzz), mutated_size);
-  if (!mutated_out) {
-
-    *out_buf = NULL;
-    perror("custom mutator allocation (maybe_grow)");
-    return 0;            /* afl-fuzz will very likely error out after this. */
-
-  }
+  memcpy(data->mutated_out, buf, buf_size);
 
   // Randomly select a command string to add as a header to the packet
-  memcpy(mutated_out, commands[rand() % 3], 3);
+  memcpy(data->mutated_out, commands[rand() % 3], 3);
 
-  // Mutate the payload of the packet
-  int i;
-  for (i = 0; i < 8; ++i) {
+  if (mutated_size > max_size) { mutated_size = max_size; }
 
-    // Randomly perform one of the (no len modification) havoc mutations
-    surgical_havoc_mutate(mutated_out, 3, mutated_size);
-
-  }
-
-  *out_buf = mutated_out;
+  *out_buf = data->mutated_out;
   return mutated_size;
 
 }
@@ -142,24 +143,16 @@ size_t afl_custom_fuzz(my_mutator_t *data, uint8_t *buf, size_t buf_size,
 size_t afl_custom_post_process(my_mutator_t *data, uint8_t *buf,
                                size_t buf_size, uint8_t **out_buf) {
 
-  uint8_t *post_process_buf =
-      maybe_grow(BUF_PARAMS(data, post_process), buf_size + 5);
-  if (!post_process_buf) {
+  if (buf_size + 5 > MAX_FILE) { buf_size = MAX_FILE - 5; }
 
-    perror("custom mutator realloc failed.");
-    *out_buf = NULL;
-    return 0;
-
-  }
+  memcpy(data->post_process_buf + 5, buf, buf_size);
+  data->post_process_buf[0] = 'A';
+  data->post_process_buf[1] = 'F';
+  data->post_process_buf[2] = 'L';
+  data->post_process_buf[3] = '+';
+  data->post_process_buf[4] = '+';
 
-  memcpy(post_process_buf + 5, buf, buf_size);
-  post_process_buf[0] = 'A';
-  post_process_buf[1] = 'F';
-  post_process_buf[2] = 'L';
-  post_process_buf[3] = '+';
-  post_process_buf[4] = '+';
-
-  *out_buf = post_process_buf;
+  *out_buf = data->post_process_buf;
 
   return buf_size + 5;
 
@@ -195,13 +188,6 @@ int32_t afl_custom_init_trim(my_mutator_t *data, uint8_t *buf,
 
   data->cur_step = 0;
 
-  if (!maybe_grow(BUF_PARAMS(data, trim), buf_size)) {
-
-    perror("init_trim grow");
-    return -1;
-
-  }
-
   memcpy(data->trim_buf, buf, buf_size);
 
   data->trim_size_current = buf_size;
@@ -282,27 +268,11 @@ int32_t afl_custom_post_trim(my_mutator_t *data, int success) {
 size_t afl_custom_havoc_mutation(my_mutator_t *data, u8 *buf, size_t buf_size,
                                  u8 **out_buf, size_t max_size) {
 
-  if (buf_size == 0) {
-
-    *out_buf = maybe_grow(BUF_PARAMS(data, havoc), 1);
-    if (!*out_buf) {
-
-      perror("custom havoc: maybe_grow");
-      return 0;
-
-    }
+  *out_buf = buf;  // in-place mutation
 
-    **out_buf = rand() % 256;
-    buf_size = 1;
-
-  } else {
-
-    // We reuse buf here. It's legal and faster.
-    *out_buf = buf;
-
-  }
+  if (buf_size <= sizeof(size_t)) { return buf_size; }
 
-  size_t victim = rand() % buf_size;
+  size_t victim = rand() % (buf_size - sizeof(size_t));
   (*out_buf)[victim] += rand() % 10;
 
   return buf_size;
@@ -369,9 +339,7 @@ uint8_t afl_custom_queue_new_entry(my_mutator_t  *data,
 void afl_custom_deinit(my_mutator_t *data) {
 
   free(data->post_process_buf);
-  free(data->havoc_buf);
-  free(data->data_buf);
-  free(data->fuzz_buf);
+  free(data->mutated_out);
   free(data->trim_buf);
   free(data);
 
diff --git a/custom_mutators/examples/post_library_gif.so.c b/custom_mutators/examples/post_library_gif.so.c
index 9cd224f4..6737c627 100644
--- a/custom_mutators/examples/post_library_gif.so.c
+++ b/custom_mutators/examples/post_library_gif.so.c
@@ -45,9 +45,8 @@
    1) If you don't want to modify the test case, simply set `*out_buf = in_buf`
       and return the original `len`.
 
-   NOTE: the following is currently NOT true, we abort in this case!
    2) If you want to skip this test case altogether and have AFL generate a
-      new one, return 0 or set `*out_buf = NULL`.
+      new one, return 0.
       Use this sparingly - it's faster than running the target program
       with patently useless inputs, but still wastes CPU time.
 
@@ -59,8 +58,6 @@
       Note that the buffer will *not* be freed for you. To avoid memory leaks,
       you need to free it or reuse it on subsequent calls (as shown below).
 
-      *** Feel free to reuse the original 'in_buf' BUFFER and return it. ***
-
     Alright. The example below shows a simple postprocessor that tries to make
     sure that all input files start with "GIF89a".
 
@@ -72,7 +69,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
-#include "alloc-inl.h"
+#include "afl-fuzz.h"
 
 /* Header that must be present at the beginning of every test case: */
 
@@ -80,8 +77,7 @@
 
 typedef struct post_state {
 
-  unsigned char *buf;
-  size_t         size;
+  size_t size;
 
 } post_state_t;
 
@@ -95,15 +91,6 @@ void *afl_custom_init(void *afl) {
 
   }
 
-  state->buf = calloc(sizeof(unsigned char), 4096);
-  if (!state->buf) {
-
-    free(state);
-    perror("calloc");
-    return NULL;
-
-  }
-
   return state;
 
 }
@@ -113,6 +100,10 @@ void *afl_custom_init(void *afl) {
 size_t afl_custom_post_process(post_state_t *data, unsigned char *in_buf,
                                unsigned int len, unsigned char **out_buf) {
 
+  /* we do in-place modification as we do not increase the size */
+
+  *out_buf = in_buf;
+
   /* Skip execution altogether for buffers shorter than 6 bytes (just to
      show how it's done). We can trust len to be sane. */
 
@@ -120,34 +111,7 @@ size_t afl_custom_post_process(post_state_t *data, unsigned char *in_buf,
 
   /* Do nothing for buffers that already start with the expected header. */
 
-  if (!memcmp(in_buf, HEADER, strlen(HEADER))) {
-
-    *out_buf = in_buf;
-    return len;
-
-  }
-
-  /* Allocate memory for new buffer, reusing previous allocation if
-     possible. Note we have to use afl-fuzz's own realloc!
-     Note that you should only do this if you need to grow the buffer,
-     otherwise work with in_buf, and assign it to *out_buf instead. */
-
-  *out_buf = afl_realloc(out_buf, len);
-
-  /* If we're out of memory, the most graceful thing to do is to return the
-     original buffer and give up on modifying it. Let AFL handle OOM on its
-     own later on. */
-
-  if (!*out_buf) {
-
-    *out_buf = in_buf;
-    return len;
-
-  }
-
-  if (len > strlen(HEADER))
-    memcpy(*out_buf + strlen(HEADER), in_buf + strlen(HEADER),
-           len - strlen(HEADER));
+  if (!memcmp(in_buf, HEADER, strlen(HEADER))) { return len; }
 
   /* Insert the new header. */
 
@@ -162,7 +126,6 @@ size_t afl_custom_post_process(post_state_t *data, unsigned char *in_buf,
 /* Gets called afterwards */
 void afl_custom_deinit(post_state_t *data) {
 
-  free(data->buf);
   free(data);
 
 }
diff --git a/custom_mutators/examples/post_library_png.so.c b/custom_mutators/examples/post_library_png.so.c
index cd65b1bc..652da497 100644
--- a/custom_mutators/examples/post_library_png.so.c
+++ b/custom_mutators/examples/post_library_png.so.c
@@ -30,7 +30,7 @@
 #include <string.h>
 #include <zlib.h>
 #include <arpa/inet.h>
-#include "alloc-inl.h"
+#include "afl-fuzz.h"
 
 /* A macro to round an integer up to 4 kB. */
 
@@ -53,7 +53,7 @@ void *afl_custom_init(void *afl) {
 
   }
 
-  state->buf = calloc(sizeof(unsigned char), 4096);
+  state->buf = calloc(sizeof(unsigned char), MAX_FILE);
   if (!state->buf) {
 
     free(state);
@@ -80,21 +80,7 @@ size_t afl_custom_post_process(post_state_t *data, const unsigned char *in_buf,
 
   }
 
-  /* This is not a good way to do it, if you do not need to grow the buffer
-     then just work with in_buf instead for speed reasons.
-     But we want to show how to grow a buffer, so this is how it's done: */
-
-  unsigned int   pos = 8;
-  unsigned char *new_buf = afl_realloc(out_buf, UP4K(len));
-
-  if (!new_buf) {
-
-    *out_buf = in_buf;
-    return len;
-
-  }
-
-  memcpy(new_buf, in_buf, len);
+  unsigned int pos = 8;
 
   /* Minimum size of a zero-length PNG chunk is 12 bytes; if we
      don't have that, we can bail out. */
@@ -124,7 +110,7 @@ size_t afl_custom_post_process(post_state_t *data, const unsigned char *in_buf,
 
     if (real_cksum != file_cksum) {
 
-      *(uint32_t *)(new_buf + pos + 8 + chunk_len) = real_cksum;
+      *(uint32_t *)(data->buf + pos + 8 + chunk_len) = real_cksum;
 
     }
 
@@ -134,7 +120,7 @@ size_t afl_custom_post_process(post_state_t *data, const unsigned char *in_buf,
 
   }
 
-  *out_buf = new_buf;
+  *out_buf = data->buf;
   return len;
 
 }
diff --git a/custom_mutators/examples/simple_example.c b/custom_mutators/examples/simple_example.c
index d888ec1f..2c0abe29 100644
--- a/custom_mutators/examples/simple_example.c
+++ b/custom_mutators/examples/simple_example.c
@@ -1,6 +1,6 @@
 // This simple example just creates random buffer <= 100 filled with 'A'
 // needs -I /path/to/AFLplusplus/include
-#include "custom_mutator_helpers.h"
+#include "afl-fuzz.h"
 
 #include <stdint.h>
 #include <stdlib.h>
@@ -13,14 +13,14 @@
 
 typedef struct my_mutator {
 
-  afl_t *afl;
+  afl_state_t *afl;
 
   // Reused buffers:
-  BUF_VAR(u8, fuzz);
+  u8 *fuzz_buf;
 
 } my_mutator_t;
 
-my_mutator_t *afl_custom_init(afl_t *afl, unsigned int seed) {
+my_mutator_t *afl_custom_init(afl_state_t *afl, unsigned int seed) {
 
   srand(seed);
   my_mutator_t *data = calloc(1, sizeof(my_mutator_t));
@@ -31,6 +31,14 @@ my_mutator_t *afl_custom_init(afl_t *afl, unsigned int seed) {
 
   }
 
+  data->fuzz_buf = (u8 *)malloc(MAX_FILE);
+  if (!data->fuzz_buf) {
+
+    perror("afl_custom_init malloc");
+    return NULL;
+
+  }
+
   data->afl = afl;
 
   return data;
@@ -44,18 +52,10 @@ size_t afl_custom_fuzz(my_mutator_t *data, uint8_t *buf, size_t buf_size,
 
   int size = (rand() % 100) + 1;
   if (size > max_size) size = max_size;
-  u8 *mutated_out = maybe_grow(BUF_PARAMS(data, fuzz), size);
-  if (!mutated_out) {
-
-    *out_buf = NULL;
-    perror("custom mutator allocation (maybe_grow)");
-    return 0;            /* afl-fuzz will very likely error out after this. */
-
-  }
 
-  memset(mutated_out, _FIXED_CHAR, size);
+  memset(data->fuzz_buf, _FIXED_CHAR, size);
 
-  *out_buf = mutated_out;
+  *out_buf = data->fuzz_buf;
   return size;
 
 }
diff --git a/custom_mutators/gramatron/build_gramatron_mutator.sh b/custom_mutators/gramatron/build_gramatron_mutator.sh
index 9952e7f5..c830329e 100755
--- a/custom_mutators/gramatron/build_gramatron_mutator.sh
+++ b/custom_mutators/gramatron/build_gramatron_mutator.sh
@@ -11,7 +11,7 @@
 # Adapted for AFLplusplus by Dominik Maier <mail@dmnk.co>
 #
 # Copyright 2017 Battelle Memorial Institute. All rights reserved.
-# Copyright 2019-2022 AFLplusplus Project. All rights reserved.
+# Copyright 2019-2023 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.
@@ -125,7 +125,7 @@ else
   }
 fi
 
-test -d json-c/.git || { echo "[-] not checked out, please install git or check your internet connection." ; exit 1 ; }
+test -e json-c/.git || { echo "[-] not checked out, please install git or check your internet connection." ; exit 1 ; }
 echo "[+] Got json-c."
 
 test -e json-c/.libs/libjson-c.a || {
diff --git a/custom_mutators/grammar_mutator/build_grammar_mutator.sh b/custom_mutators/grammar_mutator/build_grammar_mutator.sh
index 5121b07f..593cd2dc 100755
--- a/custom_mutators/grammar_mutator/build_grammar_mutator.sh
+++ b/custom_mutators/grammar_mutator/build_grammar_mutator.sh
@@ -14,7 +14,7 @@
 #                                <andreafioraldi@gmail.com>
 #
 # Copyright 2017 Battelle Memorial Institute. All rights reserved.
-# Copyright 2019-2022 AFLplusplus Project. All rights reserved.
+# Copyright 2019-2023 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.
@@ -119,7 +119,7 @@ else
   }
 fi
 
-test -f grammar_mutator/.git || { echo "[-] not checked out, please install git or check your internet connection." ; exit 1 ; }
+test -e grammar_mutator/.git || { echo "[-] not checked out, please install git or check your internet connection." ; exit 1 ; }
 echo "[+] Got grammar mutator."
 
 cd "grammar_mutator" || exit 1
diff --git a/custom_mutators/libafl_base/Cargo.toml b/custom_mutators/libafl_base/Cargo.toml
index 6e40fc39..ac6b0c8f 100644
--- a/custom_mutators/libafl_base/Cargo.toml
+++ b/custom_mutators/libafl_base/Cargo.toml
@@ -6,7 +6,7 @@ edition = "2021"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-libafl = { git = "https://github.com/AFLplusplus/LibAFL.git", rev = "62614ce1016c86e3f00f35b56399292ceabd486b" }
+libafl = { git = "https://github.com/AFLplusplus/LibAFL.git", rev = "266677bb88abe75165430f34e7de897c35560504" }
 custom_mutator = { path = "../rust/custom_mutator", features = ["afl_internals"] }
 serde = { version = "1.0", default-features = false, features = ["alloc"] } # serialization lib
 
diff --git a/custom_mutators/libafl_base/src/lib.rs b/custom_mutators/libafl_base/src/lib.rs
index 6f2db8ca..bae11e1f 100644
--- a/custom_mutators/libafl_base/src/lib.rs
+++ b/custom_mutators/libafl_base/src/lib.rs
@@ -1,5 +1,4 @@
 #![cfg(unix)]
-#![allow(unused_variables)]
 
 use serde::{Deserialize, Deserializer, Serialize, Serializer};
 use std::{
@@ -18,10 +17,12 @@ use libafl::{
         scheduled::{havoc_mutations, tokens_mutations, StdScheduledMutator, Tokens},
         Mutator,
     },
-    state::{HasCorpus, HasMaxSize, HasMetadata, HasRand, State},
+    prelude::UsesInput,
+    state::{HasCorpus, HasMaxSize, HasMetadata, HasRand, State, UsesState},
     Error,
 };
 
+#[allow(clippy::identity_op)]
 const MAX_FILE: usize = 1 * 1024 * 1024;
 
 static mut AFL: Option<&'static afl_state> = None;
@@ -64,24 +65,32 @@ impl<'de> Deserialize<'de> for AFLCorpus {
     }
 }
 
-impl Corpus<BytesInput> for AFLCorpus {
+impl UsesState for AFLCorpus {
+    type State = AFLState;
+}
+
+impl Corpus for AFLCorpus {
     #[inline]
     fn count(&self) -> usize {
         afl().queued_items as usize
     }
 
     #[inline]
-    fn add(&mut self, testcase: Testcase<BytesInput>) -> Result<usize, Error> {
+    fn add(&mut self, _testcase: Testcase<BytesInput>) -> Result<usize, Error> {
         unimplemented!();
     }
 
     #[inline]
-    fn replace(&mut self, idx: usize, testcase: Testcase<BytesInput>) -> Result<(), Error> {
+    fn replace(
+        &mut self,
+        _idx: usize,
+        _testcase: Testcase<BytesInput>,
+    ) -> Result<Testcase<Self::Input>, Error> {
         unimplemented!();
     }
 
     #[inline]
-    fn remove(&mut self, idx: usize) -> Result<Option<Testcase<BytesInput>>, Error> {
+    fn remove(&mut self, _idx: usize) -> Result<Option<Testcase<BytesInput>>, Error> {
         unimplemented!();
     }
 
@@ -92,7 +101,7 @@ impl Corpus<BytesInput> for AFLCorpus {
             entries.entry(idx).or_insert_with(|| {
                 let queue_buf = std::slice::from_raw_parts_mut(afl().queue_buf, self.count());
                 let entry = queue_buf[idx].as_mut().unwrap();
-                let fname = CStr::from_ptr((entry.fname as *mut i8).as_ref().unwrap())
+                let fname = CStr::from_ptr((entry.fname.cast::<i8>()).as_ref().unwrap())
                     .to_str()
                     .unwrap()
                     .to_owned();
@@ -127,9 +136,10 @@ pub struct AFLState {
 }
 
 impl AFLState {
+    #[must_use]
     pub fn new(seed: u32) -> Self {
         Self {
-            rand: StdRand::with_seed(seed as u64),
+            rand: StdRand::with_seed(u64::from(seed)),
             corpus: AFLCorpus::default(),
             metadata: SerdeAnyMap::new(),
             max_size: MAX_FILE,
@@ -153,7 +163,11 @@ impl HasRand for AFLState {
     }
 }
 
-impl HasCorpus<BytesInput> for AFLState {
+impl UsesInput for AFLState {
+    type Input = BytesInput;
+}
+
+impl HasCorpus for AFLState {
     type Corpus = AFLCorpus;
 
     #[inline]
@@ -208,7 +222,7 @@ impl CustomMutator for LibAFLBaseCustomMutator {
                 tokens.push(data.to_vec());
             }
             if !tokens.is_empty() {
-                state.add_metadata(Tokens::new(tokens));
+                state.add_metadata(Tokens::from(tokens));
             }
             Ok(Self {
                 state,
@@ -220,7 +234,7 @@ impl CustomMutator for LibAFLBaseCustomMutator {
     fn fuzz<'b, 's: 'b>(
         &'s mut self,
         buffer: &'b mut [u8],
-        add_buff: Option<&[u8]>,
+        _add_buff: Option<&[u8]>,
         max_size: usize,
     ) -> Result<Option<&'b [u8]>, Self::Error> {
         self.state.set_max_size(max_size);
diff --git a/custom_mutators/rust/custom_mutator-sys/Cargo.toml b/custom_mutators/rust/custom_mutator-sys/Cargo.toml
index 104f7df0..e38c972e 100644
--- a/custom_mutators/rust/custom_mutator-sys/Cargo.toml
+++ b/custom_mutators/rust/custom_mutator-sys/Cargo.toml
@@ -1,12 +1,12 @@
 [package]
 name = "custom_mutator-sys"
-version = "0.1.0"
+version = "0.1.1"
 authors = ["Julius Hohnerlein <julihoh@users.noreply.github.com>"]
-edition = "2018"
+edition = "2021"
 
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
 
 [build-dependencies]
-bindgen = "0.56"
+bindgen = "0.63"
diff --git a/custom_mutators/rust/custom_mutator-sys/build.rs b/custom_mutators/rust/custom_mutator-sys/build.rs
index 3c88a90d..ba4390ff 100644
--- a/custom_mutators/rust/custom_mutator-sys/build.rs
+++ b/custom_mutators/rust/custom_mutator-sys/build.rs
@@ -15,8 +15,8 @@ fn main() {
         // The input header we would like to generate
         // bindings for.
         .header("wrapper.h")
-        .whitelist_type("afl_state_t")
-        .blacklist_type(r"u\d+")
+        .allowlist_type("afl_state_t")
+        .blocklist_type(r"u\d+")
         .opaque_type(r"_.*")
         .opaque_type("FILE")
         .opaque_type("in_addr(_t)?")
diff --git a/custom_mutators/rust/custom_mutator-sys/src/lib.rs b/custom_mutators/rust/custom_mutator-sys/src/lib.rs
index a38a13a8..719ac994 100644
--- a/custom_mutators/rust/custom_mutator-sys/src/lib.rs
+++ b/custom_mutators/rust/custom_mutator-sys/src/lib.rs
@@ -1,5 +1,7 @@
 #![allow(non_upper_case_globals)]
 #![allow(non_camel_case_types)]
 #![allow(non_snake_case)]
+#![allow(clippy::too_many_lines)]
+#![allow(clippy::used_underscore_binding)]
 
 include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
diff --git a/custom_mutators/rust/custom_mutator/Cargo.toml b/custom_mutators/rust/custom_mutator/Cargo.toml
index 2d3cdbfa..30f764dc 100644
--- a/custom_mutators/rust/custom_mutator/Cargo.toml
+++ b/custom_mutators/rust/custom_mutator/Cargo.toml
@@ -2,7 +2,7 @@
 name = "custom_mutator"
 version = "0.1.0"
 authors = ["Julius Hohnerlein <julihoh@users.noreply.github.com>"]
-edition = "2018"
+edition = "2021"
 
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
diff --git a/custom_mutators/rust/custom_mutator/src/lib.rs b/custom_mutators/rust/custom_mutator/src/lib.rs
index f872241e..3b635eb5 100644
--- a/custom_mutators/rust/custom_mutator/src/lib.rs
+++ b/custom_mutators/rust/custom_mutator/src/lib.rs
@@ -20,7 +20,7 @@
 //! This binding is panic-safe in that it will prevent panics from unwinding into AFL++. Any panic will `abort` at the boundary between the custom mutator and AFL++.
 //!
 //! # Access to AFL++ internals
-//! This crate has an optional feature "afl_internals", which gives access to AFL++'s internal state.
+//! This crate has an optional feature "`afl_internals`", which gives access to AFL++'s internal state.
 //! The state is passed to [`CustomMutator::init`], when the feature is activated.
 //!
 //! _This is completely unsafe and uses automatically generated types extracted from the AFL++ source._
@@ -115,7 +115,7 @@ pub mod wrappers {
     impl<M: RawCustomMutator> FFIContext<M> {
         fn from(ptr: *mut c_void) -> ManuallyDrop<Box<Self>> {
             assert!(!ptr.is_null());
-            ManuallyDrop::new(unsafe { Box::from_raw(ptr as *mut Self) })
+            ManuallyDrop::new(unsafe { Box::from_raw(ptr.cast::<Self>()) })
         }
 
         fn into_ptr(self: Box<Self>) -> *const c_void {
@@ -141,27 +141,28 @@ pub mod wrappers {
     }
 
     /// panic handler called for every panic
-    fn panic_handler(method: &str, panic_info: Box<dyn Any + Send + 'static>) -> ! {
+    fn panic_handler(method: &str, panic_info: &Box<dyn Any + Send + 'static>) -> ! {
         use std::ops::Deref;
-        let cause = panic_info
-            .downcast_ref::<String>()
-            .map(String::deref)
-            .unwrap_or_else(|| {
+        let cause = panic_info.downcast_ref::<String>().map_or_else(
+            || {
                 panic_info
                     .downcast_ref::<&str>()
                     .copied()
                     .unwrap_or("<cause unknown>")
-            });
-        eprintln!("A panic occurred at {}: {}", method, cause);
+            },
+            String::deref,
+        );
+        eprintln!("A panic occurred at {method}: {cause}");
         abort()
     }
 
     /// Internal function used in the macro
     #[cfg(not(feature = "afl_internals"))]
+    #[must_use]
     pub fn afl_custom_init_<M: RawCustomMutator>(seed: u32) -> *const c_void {
         match catch_unwind(|| FFIContext::<M>::new(seed).into_ptr()) {
             Ok(ret) => ret,
-            Err(err) => panic_handler("afl_custom_init", err),
+            Err(err) => panic_handler("afl_custom_init", &err),
         }
     }
 
@@ -176,7 +177,7 @@ pub mod wrappers {
             FFIContext::<M>::new(afl, seed).into_ptr()
         }) {
             Ok(ret) => ret,
-            Err(err) => panic_handler("afl_custom_init", err),
+            Err(err) => panic_handler("afl_custom_init", &err),
         }
     }
 
@@ -196,32 +197,27 @@ pub mod wrappers {
     ) -> usize {
         match catch_unwind(|| {
             let mut context = FFIContext::<M>::from(data);
-            if buf.is_null() {
-                panic!("null buf passed to afl_custom_fuzz")
-            }
-            if out_buf.is_null() {
-                panic!("null out_buf passed to afl_custom_fuzz")
-            }
+
+            assert!(!buf.is_null(), "null buf passed to afl_custom_fuzz");
+            assert!(!out_buf.is_null(), "null out_buf passed to afl_custom_fuzz");
+
             let buff_slice = slice::from_raw_parts_mut(buf, buf_size);
             let add_buff_slice = if add_buf.is_null() {
                 None
             } else {
                 Some(slice::from_raw_parts(add_buf, add_buf_size))
             };
-            match context.mutator.fuzz(buff_slice, add_buff_slice, max_size) {
-                Some(buffer) => {
-                    *out_buf = buffer.as_ptr();
-                    buffer.len()
-                }
-                None => {
-                    // return the input buffer with 0-length to let AFL skip this mutation attempt
-                    *out_buf = buf;
-                    0
-                }
+            if let Some(buffer) = context.mutator.fuzz(buff_slice, add_buff_slice, max_size) {
+                *out_buf = buffer.as_ptr();
+                buffer.len()
+            } else {
+                // return the input buffer with 0-length to let AFL skip this mutation attempt
+                *out_buf = buf;
+                0
             }
         }) {
             Ok(ret) => ret,
-            Err(err) => panic_handler("afl_custom_fuzz", err),
+            Err(err) => panic_handler("afl_custom_fuzz", &err),
         }
     }
 
@@ -237,9 +233,8 @@ pub mod wrappers {
     ) -> u32 {
         match catch_unwind(|| {
             let mut context = FFIContext::<M>::from(data);
-            if buf.is_null() {
-                panic!("null buf passed to afl_custom_fuzz")
-            }
+            assert!(!buf.is_null(), "null buf passed to afl_custom_fuzz");
+
             let buf_slice = slice::from_raw_parts(buf, buf_size);
             // see https://doc.rust-lang.org/nomicon/borrow-splitting.html
             let ctx = &mut **context;
@@ -247,37 +242,39 @@ pub mod wrappers {
             mutator.fuzz_count(buf_slice)
         }) {
             Ok(ret) => ret,
-            Err(err) => panic_handler("afl_custom_fuzz_count", err),
+            Err(err) => panic_handler("afl_custom_fuzz_count", &err),
         }
     }
 
     /// Internal function used in the macro
-    pub fn afl_custom_queue_new_entry_<M: RawCustomMutator>(
+    pub unsafe fn afl_custom_queue_new_entry_<M: RawCustomMutator>(
         data: *mut c_void,
         filename_new_queue: *const c_char,
         filename_orig_queue: *const c_char,
     ) -> bool {
         match catch_unwind(|| {
             let mut context = FFIContext::<M>::from(data);
-            if filename_new_queue.is_null() {
-                panic!("received null filename_new_queue in afl_custom_queue_new_entry");
-            }
+            assert!(
+                !filename_new_queue.is_null(),
+                "received null filename_new_queue in afl_custom_queue_new_entry"
+            );
+
             let filename_new_queue = Path::new(OsStr::from_bytes(
                 unsafe { CStr::from_ptr(filename_new_queue) }.to_bytes(),
             ));
-            let filename_orig_queue = if !filename_orig_queue.is_null() {
+            let filename_orig_queue = if filename_orig_queue.is_null() {
+                None
+            } else {
                 Some(Path::new(OsStr::from_bytes(
                     unsafe { CStr::from_ptr(filename_orig_queue) }.to_bytes(),
                 )))
-            } else {
-                None
             };
             context
                 .mutator
                 .queue_new_entry(filename_new_queue, filename_orig_queue)
         }) {
             Ok(ret) => ret,
-            Err(err) => panic_handler("afl_custom_queue_new_entry", err),
+            Err(err) => panic_handler("afl_custom_queue_new_entry", &err),
         }
     }
 
@@ -292,7 +289,7 @@ pub mod wrappers {
             ManuallyDrop::into_inner(FFIContext::<M>::from(data));
         }) {
             Ok(ret) => ret,
-            Err(err) => panic_handler("afl_custom_deinit", err),
+            Err(err) => panic_handler("afl_custom_deinit", &err),
         }
     }
 
@@ -306,13 +303,13 @@ pub mod wrappers {
                 buf.extend_from_slice(res.as_bytes());
                 buf.push(0);
                 // unwrapping here, as the error case should be extremely rare
-                CStr::from_bytes_with_nul(&buf).unwrap().as_ptr()
+                CStr::from_bytes_with_nul(buf).unwrap().as_ptr()
             } else {
                 null()
             }
         }) {
             Ok(ret) => ret,
-            Err(err) => panic_handler("afl_custom_introspection", err),
+            Err(err) => panic_handler("afl_custom_introspection", &err),
         }
     }
 
@@ -329,18 +326,18 @@ pub mod wrappers {
                 buf.extend_from_slice(res.as_bytes());
                 buf.push(0);
                 // unwrapping here, as the error case should be extremely rare
-                CStr::from_bytes_with_nul(&buf).unwrap().as_ptr()
+                CStr::from_bytes_with_nul(buf).unwrap().as_ptr()
             } else {
                 null()
             }
         }) {
             Ok(ret) => ret,
-            Err(err) => panic_handler("afl_custom_describe", err),
+            Err(err) => panic_handler("afl_custom_describe", &err),
         }
     }
 
     /// Internal function used in the macro
-    pub fn afl_custom_queue_get_<M: RawCustomMutator>(
+    pub unsafe fn afl_custom_queue_get_<M: RawCustomMutator>(
         data: *mut c_void,
         filename: *const c_char,
     ) -> u8 {
@@ -348,12 +345,12 @@ pub mod wrappers {
             let mut context = FFIContext::<M>::from(data);
             assert!(!filename.is_null());
 
-            context.mutator.queue_get(Path::new(OsStr::from_bytes(
+            u8::from(context.mutator.queue_get(Path::new(OsStr::from_bytes(
                 unsafe { CStr::from_ptr(filename) }.to_bytes(),
-            ))) as u8
+            ))))
         }) {
             Ok(ret) => ret,
-            Err(err) => panic_handler("afl_custom_queue_get", err),
+            Err(err) => panic_handler("afl_custom_queue_get", &err),
         }
     }
 }
@@ -373,7 +370,7 @@ macro_rules! _define_afl_custom_init {
     };
 }
 
-/// An exported macro to defined afl_custom_init meant for insternal usage
+/// An exported macro to defined `afl_custom_init` meant for internal usage
 #[cfg(not(feature = "afl_internals"))]
 #[macro_export]
 macro_rules! _define_afl_custom_init {
@@ -444,7 +441,7 @@ macro_rules! export_mutator {
         }
 
         #[no_mangle]
-        pub extern "C" fn afl_custom_queue_new_entry(
+        pub unsafe extern "C" fn afl_custom_queue_new_entry(
             data: *mut ::std::os::raw::c_void,
             filename_new_queue: *const ::std::os::raw::c_char,
             filename_orig_queue: *const ::std::os::raw::c_char,
@@ -457,7 +454,7 @@ macro_rules! export_mutator {
         }
 
         #[no_mangle]
-        pub extern "C" fn afl_custom_queue_get(
+        pub unsafe extern "C" fn afl_custom_queue_get(
             data: *mut ::std::os::raw::c_void,
             filename: *const ::std::os::raw::c_char,
         ) -> u8 {
@@ -520,9 +517,10 @@ mod sanity_test {
     export_mutator!(ExampleMutator);
 }
 
-#[allow(unused_variables)]
 /// A custom mutator.
 /// [`CustomMutator::handle_error`] will be called in case any method returns an [`Result::Err`].
+#[allow(unused_variables)]
+#[allow(clippy::missing_errors_doc)]
 pub trait CustomMutator {
     /// The error type. All methods must return the same error type.
     type Error: Debug;
@@ -537,7 +535,7 @@ pub trait CustomMutator {
             .map(|v| !v.is_empty())
             .unwrap_or(false)
         {
-            eprintln!("Error in custom mutator: {:?}", err)
+            eprintln!("Error in custom mutator: {err:?}");
         }
     }
 
@@ -759,8 +757,7 @@ mod truncate_test {
             let actual_output = truncate_str_unicode_safe(input, *max_len);
             assert_eq!(
                 &actual_output, expected_output,
-                "{:#?} truncated to {} bytes should be {:#?}, but is {:#?}",
-                input, max_len, expected_output, actual_output
+                "{input:#?} truncated to {max_len} bytes should be {expected_output:#?}, but is {actual_output:#?}"
             );
         }
     }
diff --git a/custom_mutators/rust/example/Cargo.toml b/custom_mutators/rust/example/Cargo.toml
index 070d23b1..9d53ebe5 100644
--- a/custom_mutators/rust/example/Cargo.toml
+++ b/custom_mutators/rust/example/Cargo.toml
@@ -2,7 +2,7 @@
 name = "example_mutator"
 version = "0.1.0"
 authors = ["Julius Hohnerlein <julihoh@users.noreply.github.com>"]
-edition = "2018"
+edition = "2021"
 
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
diff --git a/custom_mutators/rust/example_lain/Cargo.toml b/custom_mutators/rust/example_lain/Cargo.toml
index 29d606a4..c52bf86f 100644
--- a/custom_mutators/rust/example_lain/Cargo.toml
+++ b/custom_mutators/rust/example_lain/Cargo.toml
@@ -2,7 +2,7 @@
 name = "example_lain"
 version = "0.1.0"
 authors = ["Julius Hohnerlein <julihoh@users.noreply.github.com>"]
-edition = "2018"
+edition = "2021"
 
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html