]> git.ipfire.org Git - thirdparty/nftables.git/commitdiff
src: move fuzzer functionality to separate tool
authorFlorian Westphal <fw@strlen.de>
Mon, 17 Nov 2025 22:13:49 +0000 (23:13 +0100)
committerFlorian Westphal <fw@strlen.de>
Thu, 20 Nov 2025 21:16:43 +0000 (22:16 +0100)
This means some loss of functionality since you can no longer combine
--fuzzer with options like --debug, --define, --include.

On the upside, this adds new --random-outflags mode which will randomly
switch --terse, --numeric, --echo ... on/off.

Update README to reflect this change.

Signed-off-by: Florian Westphal <fw@strlen.de>
Acked-by: Pablo Neira Ayuso <pablo@netfilter.org>
Makefile.am
include/afl++.h
src/afl++.c [deleted file]
src/main.c
tests/afl++/README
tests/afl++/run-afl.sh
tools/.gitignore
tools/nft-afl.c [new file with mode: 0644]

index d2cae2a31aaa0b6ba79e8f7428c23339f1c13a01..e278a193942c812d87c5891cc5e846f8ce697562 100644 (file)
@@ -31,6 +31,7 @@ LDADD =
 lib_LTLIBRARIES =
 noinst_LTLIBRARIES =
 sbin_PROGRAMS =
+noinst_PROGRAMS =
 check_PROGRAMS =
 dist_man_MANS =
 CLEANFILES =
@@ -294,10 +295,6 @@ sbin_PROGRAMS += src/nft
 
 src_nft_SOURCES = src/main.c
 
-if BUILD_AFL
-src_nft_SOURCES += src/afl++.c
-endif
-
 if BUILD_CLI
 src_nft_SOURCES += src/cli.c
 endif
@@ -318,6 +315,16 @@ examples_nft_json_file_LDADD = src/libnftables.la
 
 ###############################################################################
 
+if BUILD_AFL
+noinst_PROGRAMS += tools/nft-afl
+
+tools_nft_afl_SOURCES = tools/nft-afl.c
+tools_nft_afl_SOURCES += -I$(srcdir)/include
+tools_nft_afl_LDADD = src/libnftables.la
+endif
+
+###############################################################################
+
 if BUILD_MAN
 
 dist_man_MANS += \
index a23bcef1bd0d6a4dac482e553d4d44158d96ceca..69858295ed7c1014c0981a1559e0ca54e7e87392 100644 (file)
@@ -20,29 +20,4 @@ enum nft_afl_fuzzer_stage {
        NFT_AFL_FUZZER_NETLINK_RW,
 };
 
-static inline void nft_afl_print_build_info(FILE *fp)
-{
-#if HAVE_FUZZER_BUILD && defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
-       fprintf(fp, "\nWARNING: BUILT WITH FUZZER SUPPORT AND AFL INSTRUMENTATION\n");
-#elif defined(FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION)
-       fprintf(fp, "\nWARNING: BUILT WITH AFL INSTRUMENTATION\n");
-#elif HAVE_FUZZER_BUILD
-       fprintf(fp, "\nWARNING: BUILT WITH FUZZER SUPPORT BUT NO AFL INSTRUMENTATION\n");
-#endif
-}
-
-#if HAVE_FUZZER_BUILD
-extern int nft_afl_init(struct nft_ctx *nft, enum nft_afl_fuzzer_stage s);
-extern int nft_afl_main(struct nft_ctx *nft);
-#else
-static inline int nft_afl_main(struct nft_ctx *ctx)
-{
-        return -1;
-}
-static inline int nft_afl_init(struct nft_ctx *nft, enum nft_afl_fuzzer_stage s){ return -1; }
-#endif
-
-#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
-#define __AFL_INIT() do { } while (0)
-#endif
 #endif
diff --git a/src/afl++.c b/src/afl++.c
deleted file mode 100644 (file)
index 7992595..0000000
+++ /dev/null
@@ -1,219 +0,0 @@
-/*
- * Copyright (c) Red Hat GmbH. Author: Florian Westphal <fw@strlen.de>
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2 (or any
- * later) as published by the Free Software Foundation.
- */
-
-#include <nft.h>
-#include <stdio.h>
-
-#include <errno.h>
-#include <ctype.h>
-#include <limits.h>
-#include <fcntl.h>
-#include <unistd.h>
-#include <time.h>
-
-#include <sys/stat.h>
-#include <sys/wait.h>
-
-#include <afl++.h>
-#include <nftables.h>
-
-static const char self_fault_inject_file[] = "/proc/self/make-it-fail";
-
-#ifdef __AFL_FUZZ_TESTCASE_LEN
-/* the below macro gets passed via afl-cc, declares prototypes
- * depending on the afl-cc flavor.
- */
-__AFL_FUZZ_INIT();
-#else
-/* this lets the source compile without afl-clang-fast/lto */
-static unsigned char fuzz_buf[4096];
-static ssize_t fuzz_len;
-
-#define __AFL_FUZZ_TESTCASE_LEN fuzz_len
-#define __AFL_FUZZ_TESTCASE_BUF fuzz_buf
-#define __AFL_FUZZ_INIT() do { } while (0)
-#define __AFL_LOOP(x) \
-   ((fuzz_len = read(0, fuzz_buf, sizeof(fuzz_buf))) > 0 ? 1 : 0)
-#endif
-
-struct nft_afl_state {
-       FILE *make_it_fail_fp;
-};
-
-static struct nft_afl_state state;
-
-static char *preprocess(unsigned char *input, ssize_t len)
-{
-       ssize_t real_len = strnlen((char *)input, len);
-
-       if (real_len == 0)
-               return NULL;
-
-       if (real_len >= len)
-               input[len - 1] = 0;
-
-       return (char *)input;
-}
-
-static bool kernel_is_tainted(void)
-{
-       FILE *fp = fopen("/proc/sys/kernel/tainted", "r");
-       unsigned int taint;
-       bool ret = false;
-
-       if (fp) {
-               if (fscanf(fp, "%u", &taint) == 1 && taint) {
-                       fprintf(stderr, "Kernel is tainted: 0x%x\n", taint);
-                       sleep(3);       /* in case we run under fuzzer, don't restart right away */
-                       ret = true;
-               }
-
-               fclose(fp);
-       }
-
-       return ret;
-}
-
-static void fault_inject_write(FILE *fp, unsigned int v)
-{
-       rewind(fp);
-       fprintf(fp, "%u\n", v);
-       fflush(fp);
-}
-
-static void fault_inject_enable(const struct nft_afl_state *state)
-{
-       if (state->make_it_fail_fp)
-               fault_inject_write(state->make_it_fail_fp, 1);
-}
-
-static void fault_inject_disable(const struct nft_afl_state *state)
-{
-       if (state->make_it_fail_fp)
-               fault_inject_write(state->make_it_fail_fp, 0);
-}
-
-static bool nft_afl_run_cmd(struct nft_ctx *ctx, const char *input_cmd)
-{
-       if (kernel_is_tainted())
-               return false;
-
-       switch (ctx->afl_ctx_stage) {
-       case NFT_AFL_FUZZER_PARSER:
-       case NFT_AFL_FUZZER_EVALUATION:
-       case NFT_AFL_FUZZER_NETLINK_RO:
-               nft_run_cmd_from_buffer(ctx, input_cmd);
-               return true;
-       case NFT_AFL_FUZZER_NETLINK_RW:
-               break;
-       }
-
-       fault_inject_enable(&state);
-       nft_run_cmd_from_buffer(ctx, input_cmd);
-       fault_inject_disable(&state);
-
-       return kernel_is_tainted();
-}
-
-static FILE *fault_inject_open(void)
-{
-       return fopen(self_fault_inject_file, "r+");
-}
-
-static bool nft_afl_state_init(struct nft_afl_state *state)
-{
-       state->make_it_fail_fp = fault_inject_open();
-       return true;
-}
-
-int nft_afl_init(struct nft_ctx *ctx, enum nft_afl_fuzzer_stage stage)
-{
-#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
-       const char instrumented[] = "afl instrumented";
-#else
-       const char instrumented[] = "no afl instrumentation";
-#endif
-       nft_afl_print_build_info(stderr);
-
-       if (!nft_afl_state_init(&state))
-               return -1;
-
-       ctx->afl_ctx_stage = stage;
-
-       if (state.make_it_fail_fp) {
-               unsigned int value;
-               int ret;
-
-               rewind(state.make_it_fail_fp);
-               ret = fscanf(state.make_it_fail_fp, "%u", &value);
-               if (ret != 1 || value != 1) {
-                       fclose(state.make_it_fail_fp);
-                       state.make_it_fail_fp = NULL;
-               }
-
-               /* if its enabled, disable and then re-enable ONLY
-                * when submitting data to the kernel.
-                *
-                * Otherwise even libnftables memory allocations could fail
-                * which is not what we want.
-                */
-               fault_inject_disable(&state);
-       }
-
-       fprintf(stderr, "starting (%s, %s fault injection)", instrumented, state.make_it_fail_fp ? "with" : "no");
-       return 0;
-}
-
-int nft_afl_main(struct nft_ctx *ctx)
-{
-       unsigned char *buf;
-       ssize_t len;
-
-       if (kernel_is_tainted())
-               return -1;
-
-       if (state.make_it_fail_fp) {
-               FILE *fp = fault_inject_open();
-
-               /* reopen is needed because /proc/self is a symlink, i.e.
-                * fp refers to parent process, not "us".
-                */
-               if (!fp) {
-                       fprintf(stderr, "Could not reopen %s: %s", self_fault_inject_file, strerror(errno));
-                       return -1;
-               }
-
-               fclose(state.make_it_fail_fp);
-               state.make_it_fail_fp = fp;
-       }
-
-       buf = __AFL_FUZZ_TESTCASE_BUF;
-
-       while (__AFL_LOOP(UINT_MAX)) {
-               char *input;
-
-               len = __AFL_FUZZ_TESTCASE_LEN;  // do not use the macro directly in a call!
-
-               input = preprocess(buf, len);
-               if (!input)
-                       continue;
-
-               /* buf is null terminated at this point */
-               if (!nft_afl_run_cmd(ctx, input))
-                       continue;
-
-               /* Kernel is tainted.
-                * exit() will cause a restart from afl-fuzz.
-                * Avoid burning cpu cycles in this case.
-                */
-               sleep(1);
-       }
-
-       /* afl-fuzz will restart us. */
-       return 0;
-}
index c2e909d2841afc0cc4c50afaa210ddc0fdc77196..29b0533dee7c9325cda78654d1a210f26f8aea24 100644 (file)
@@ -21,7 +21,6 @@
 #include <nftables/libnftables.h>
 #include <utils.h>
 #include <cli.h>
-#include <afl++.h>
 
 static struct nft_ctx *nft;
 
@@ -87,11 +86,6 @@ enum opt_vals {
        OPT_TERSE               = 't',
        OPT_OPTIMIZE            = 'o',
        OPT_INVALID             = '?',
-
-#if HAVE_FUZZER_BUILD
-       /* keep last */
-        OPT_FUZZER             = 254
-#endif
 };
 
 struct nft_opt {
@@ -149,10 +143,6 @@ static const struct nft_opt nft_options[] = {
                                     "Specify debugging level (scanner, parser, eval, netlink, mnl, proto-ctx, segtree, all)"),
        [IDX_OPTIMIZE]      = NFT_OPT("optimize",               OPT_OPTIMIZE,           NULL,
                                     "Optimize ruleset"),
-#if HAVE_FUZZER_BUILD
-       [IDX_FUZZER]        = NFT_OPT("fuzzer",                 OPT_FUZZER,             "stage",
-                                     "fuzzer stage to run (parser, eval, netlink-ro, netlink-rw)"),
-#endif
 };
 
 #define NR_NFT_OPTIONS (sizeof(nft_options) / sizeof(nft_options[0]))
@@ -243,7 +233,6 @@ static void show_help(const char *name)
                print_option(&nft_options[i]);
 
        fputs("\n", stdout);
-       nft_afl_print_build_info(stdout);
 }
 
 static void show_version(void)
@@ -286,7 +275,6 @@ static void show_version(void)
               PACKAGE_NAME, PACKAGE_VERSION, RELEASE_NAME,
               cli, json, minigmp, xt);
 
-       nft_afl_print_build_info(stdout);
 }
 
 static const struct {
@@ -327,38 +315,6 @@ static const struct {
        },
 };
 
-#if HAVE_FUZZER_BUILD
-static const struct {
-       const char                      *name;
-       enum nft_afl_fuzzer_stage       stage;
-} fuzzer_stage_param[] = {
-       {
-               .name           = "parser",
-               .stage          = NFT_AFL_FUZZER_PARSER,
-       },
-       {
-               .name           = "eval",
-               .stage          = NFT_AFL_FUZZER_EVALUATION,
-       },
-       {
-               .name           = "netlink-ro",
-               .stage          = NFT_AFL_FUZZER_NETLINK_RO,
-       },
-       {
-               .name           = "netlink-rw",
-               .stage          = NFT_AFL_FUZZER_NETLINK_RW,
-       },
-};
-static void afl_exit(const char *err)
-{
-       fprintf(stderr, "Error: fuzzer: %s\n", err);
-       sleep(60);      /* assume we're running under afl-fuzz and would be restarted right away */
-       exit(EXIT_FAILURE);
-}
-#else
-static inline void afl_exit(const char *err) { }
-#endif
-
 static void nft_options_error(int argc, char * const argv[], int pos)
 {
        int i;
@@ -407,7 +363,6 @@ static bool nft_options_check(int argc, char * const argv[])
 int main(int argc, char * const *argv)
 {
        const struct option *options = get_options();
-       enum nft_afl_fuzzer_stage fuzzer_stage = 0;
        bool interactive = false, define = false;
        const char *optstring = get_optstring();
        unsigned int output_flags = 0;
@@ -549,26 +504,6 @@ int main(int argc, char * const *argv)
                case OPT_OPTIMIZE:
                        nft_ctx_set_optimize(nft, 0x1);
                        break;
-#if HAVE_FUZZER_BUILD
-               case OPT_FUZZER:
-                       {
-                               unsigned int i;
-
-                               for (i = 0; i < array_size(fuzzer_stage_param); i++) {
-                                       if (strcmp(fuzzer_stage_param[i].name, optarg))
-                                               continue;
-                                       fuzzer_stage = fuzzer_stage_param[i].stage;
-                                       break;
-                               }
-
-                               if (i == array_size(fuzzer_stage_param)) {
-                                       fprintf(stderr, "invalid fuzzer stage `%s'\n",
-                                               optarg);
-                                       goto out_fail;
-                               }
-                       }
-                       break;
-#endif
                case OPT_INVALID:
                        goto out_fail;
                }
@@ -581,38 +516,6 @@ int main(int argc, char * const *argv)
 
        nft_ctx_output_set_flags(nft, output_flags);
 
-       if (fuzzer_stage) {
-               unsigned int input_flags;
-
-               if (filename || define || interactive)
-                       afl_exit("-D/--define, -f/--filename and -i/--interactive are incompatible options");
-
-               rc = nft_afl_init(nft, fuzzer_stage);
-               if (rc != 0)
-                       afl_exit("cannot initialize");
-
-               input_flags = nft_ctx_input_get_flags(nft);
-
-               /* DNS lookups can result in severe fuzzer slowdown */
-               input_flags |= NFT_CTX_INPUT_NO_DNS;
-               nft_ctx_input_set_flags(nft, input_flags);
-
-               if (fuzzer_stage < NFT_AFL_FUZZER_NETLINK_RW)
-                       nft_ctx_set_dry_run(nft, true);
-
-               fprintf(stderr, "Awaiting fuzzer-generated inputs\n");
-       }
-
-       __AFL_INIT();
-
-       if (fuzzer_stage) {
-               rc = nft_afl_main(nft);
-               if (rc != 0)
-                       afl_exit("fatal error");
-
-               return EXIT_SUCCESS;
-       }
-
        if (optind != argc) {
                char *buf;
 
index 9ff7e6485987e469fbae0eda6aa760ee88d8d86e..ef3219ff0c2c0c46893124ed03dd02797639903a 100644 (file)
@@ -17,15 +17,26 @@ Important options are:
 --disable-shared, so that libnftables is instrumented too.
 --enable-fuzzer
 
---enable-fuzzer is not strictly required, you can run normal nft builds under
-afl-fuzz too.  But the execution speed will be much slower.
+--enable-fuzzer provides tools/nft-afl that allows more fine-grained control
+nft-afl provides a few options to guide the fuzzing process, some are shared
+with nft binary:
 
---enable-fuzzer also provides the nft --fuzzer command line option that allows
-more fine-grained control over what code paths should be covered by the fuzzing
-process.
+--check
 
-When fuzzing in this mode, then each new input passes through the following
-processing stages:
+Prevents the fuzzer-generated rulesets from being committed to the kernel.
+
+--random-outflags
+
+Periodically alter output behaviour by enabling or disabling --terse,
+--numeric, --echo and so on.
+
+--json
+
+Format output in JSON
+
+--fuzzer <stage>
+
+Instruct nft-afl to stop fuzzing after reaching the given stage.  Stages are:
 
 1: 'parser':
     Only run / exercise the flex/bison parser.
@@ -38,6 +49,7 @@ processing stages:
 3: 'netlink-ro':
     Also build/serialize the ruleset into netlink-commands to send to the
     kernel, but omit the final write so the kernel will not see the message.
+    This is the default mode.
 
 4: 'netlink-rw':
     Same as 3 but the message will be sent to the kernel.
@@ -56,17 +68,17 @@ and its libraries.
 All --fuzzer modes EXCEPT 'netlink-rw' do imply --check as these modes never
 alter state in the kernel.
 
-In rw mode, before each input, nft checks the kernel "taint" status as provided
+Before each input, nft-afl checks the kernel "taint" status as provided
 by "/proc/sys/kernel/tainted".  If this is non-zero, fuzzing stops.
 
-To run nftables under afl++, run nftables like this:
+To run libnftables under afl++, run nft-afl like this:
 
 unshare -n \
   afl-fuzz -g 16 -G 2000 -t 5000 -i tests/afl++/in -o tests/afl++/out \
-  -- src/nft --fuzzer <arg>
+  -- tools/nft-afl
 
-arg should be either "netlink-ro" (if you only want to exercise nft userspace)
-or "netlink-rw" (if you want to test kernel code paths too).
+arg should be either "netlink-ro" (if you only want to exercise libnftables
+userspace) or "netlink-rw" (if you want to test kernel code paths too).
 
 Its also a good idea to do this from tmux/screen so you can disconnect/reattach
 later.  You can also spawn multiple instances.
index ee9d98fee3c57a6edaaf6d9a4fc7797b30672eea..9638bfb0a88b82b749b795ae28d4e433a5a86882 100755 (executable)
@@ -3,7 +3,7 @@
 set -e
 
 ME=$(dirname $0)
-SRC_NFT="$(dirname $0)/../../src/nft"
+SRC_NFT="$(dirname $0)/../../tools/nft-afl"
 
 cd $ME/../..
 
@@ -43,5 +43,5 @@ done
 
 echo "built initial set of inputs to fuzz from shell test case dump files."
 echo "sample invocations:"
-echo "unshare -n afl-fuzz -g 16 -G 2000 -t 5000 -i tests/afl++/in -o tests/afl++/out -- src/nft --fuzzer netlink-ro"
-echo "unshare -n afl-fuzz -g 16 -G 2000 -t 5000 -i tests/afl++/in-json -o tests/afl++/out -- src/nft -j --check --fuzzer netlink-rw"
+echo "unshare -n afl-fuzz -g 16 -G 2000 -t 5000 -i tests/afl++/in -o tests/afl++/out -- "$SRC_NFT" --fuzzer netlink-ro"
+echo "unshare -n afl-fuzz -g 16 -G 2000 -t 5000 -i tests/afl++/in-json -o tests/afl++/out -- "$SRC_NFT" -j --check --fuzzer netlink-rw"
index 2d06c49835b15cd5859480715f455a35b669d5b0..56dd2226c1bd06e36e2ee69554ffe47e192302fd 100644 (file)
@@ -1 +1,5 @@
 nftables.service
+*.o
+.deps/
+.libs/
+nft-afl
diff --git a/tools/nft-afl.c b/tools/nft-afl.c
new file mode 100644 (file)
index 0000000..62034de
--- /dev/null
@@ -0,0 +1,437 @@
+/*
+ * Copyright (c) Red Hat GmbH. Author: Florian Westphal <fw@strlen.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 (or any
+ * later) as published by the Free Software Foundation.
+ */
+
+#include <nft.h>
+#include <stdio.h>
+#include <stdbool.h>
+
+#include <errno.h>
+#include <ctype.h>
+#include <limits.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <unistd.h>
+#include <time.h>
+
+#include <sys/random.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+#include <afl++.h>
+#include <nftables.h>
+
+static const char self_fault_inject_file[] = "/proc/self/make-it-fail";
+
+#ifdef __AFL_FUZZ_TESTCASE_LEN
+/* the below macro gets passed via afl-cc, declares prototypes
+ * depending on the afl-cc flavor.
+ */
+__AFL_FUZZ_INIT();
+#else
+/* this lets the source compile without afl-clang-fast/lto */
+static unsigned char fuzz_buf[4096];
+static ssize_t fuzz_len;
+
+#define __AFL_INIT() do { } while (0)
+#define __AFL_FUZZ_TESTCASE_LEN fuzz_len
+#define __AFL_FUZZ_TESTCASE_BUF fuzz_buf
+#define __AFL_FUZZ_INIT() do { } while (0)
+#define __AFL_LOOP(x) \
+   ((fuzz_len = read(0, fuzz_buf, sizeof(fuzz_buf))) > 0 ? 1 : 0)
+#endif
+
+struct nft_afl_state {
+       FILE *make_it_fail_fp;
+};
+
+static struct nft_afl_state state;
+
+enum nft_fuzzer_opts {
+       OPT_HELP = 'h',
+       OPT_CHECK = 'c',
+       OPT_JSON = 'j',
+       OPT_INVALID = '?',
+
+       /* --long only */
+       OPT_FUZZER = 1,
+       OPT_RANDOUTFLAGS = 2,
+};
+
+static const char optstring[] = "hcj";
+
+static struct option options[] = {
+       {
+               .name = "help",
+               .val = OPT_HELP,
+       }, {
+               .name = "check",
+               .val = OPT_CHECK,
+       }, {
+               .name = "json",
+               .val = OPT_JSON,
+       }, {
+               .name = "fuzzer",
+               .val = OPT_FUZZER,
+               .has_arg = 1,
+       }, {
+               .name = "random-outflags",
+               .val = OPT_RANDOUTFLAGS,
+       }, {
+       }
+};
+
+static const struct {
+       const char                      *name;
+       enum nft_afl_fuzzer_stage       stage;
+} fuzzer_stage_param[] = {
+       {
+               .name           = "parser",
+               .stage          = NFT_AFL_FUZZER_PARSER,
+       },
+       {
+               .name           = "eval",
+               .stage          = NFT_AFL_FUZZER_EVALUATION,
+       },
+       {
+               .name           = "netlink-ro",
+               .stage          = NFT_AFL_FUZZER_NETLINK_RO,
+       },
+       {
+               .name           = "netlink-rw",
+               .stage          = NFT_AFL_FUZZER_NETLINK_RW,
+       },
+};
+
+static void nft_afl_print_build_info(FILE *fp)
+{
+#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
+       fprintf(fp, "\nWARNING: BUILT WITH FUZZER SUPPORT AND AFL INSTRUMENTATION\n");
+#else
+       fprintf(fp, "\nWARNING: BUILT WITH FUZZER SUPPORT BUT NO AFL INSTRUMENTATION\n");
+#endif
+}
+
+static void nft_afl_exit(const char *err)
+{
+       fprintf(stderr, "Error: fuzzer: %s\n", err);
+       sleep(60);      /* assume we're running under afl-fuzz and would be restarted right away */
+       exit(EXIT_FAILURE);
+}
+
+static char *preprocess(unsigned char *input, ssize_t len)
+{
+       ssize_t real_len = strnlen((char *)input, len);
+
+       if (real_len == 0)
+               return NULL;
+
+       if (real_len >= len)
+               input[len - 1] = 0;
+
+       return (char *)input;
+}
+
+static bool kernel_is_tainted(void)
+{
+       FILE *fp = fopen("/proc/sys/kernel/tainted", "r");
+       unsigned int taint;
+       bool ret = false;
+
+       if (fp) {
+               if (fscanf(fp, "%u", &taint) == 1 && taint) {
+                       fprintf(stderr, "Kernel is tainted: 0x%x\n", taint);
+                       sleep(3);       /* in case we run under fuzzer, don't restart right away */
+                       ret = true;
+               }
+
+               fclose(fp);
+       }
+
+       return ret;
+}
+
+static void fault_inject_write(FILE *fp, unsigned int v)
+{
+       rewind(fp);
+       fprintf(fp, "%u\n", v);
+       fflush(fp);
+}
+
+static void fault_inject_enable(const struct nft_afl_state *state)
+{
+       if (state->make_it_fail_fp)
+               fault_inject_write(state->make_it_fail_fp, 1);
+}
+
+static void fault_inject_disable(const struct nft_afl_state *state)
+{
+       if (state->make_it_fail_fp)
+               fault_inject_write(state->make_it_fail_fp, 0);
+}
+
+static void nft_afl_run_cmd(struct nft_ctx *ctx, const char *input_cmd)
+{
+       if (kernel_is_tainted())
+               return;
+
+       switch (ctx->afl_ctx_stage) {
+       case NFT_AFL_FUZZER_PARSER:
+       case NFT_AFL_FUZZER_EVALUATION:
+       case NFT_AFL_FUZZER_NETLINK_RO:
+               nft_run_cmd_from_buffer(ctx, input_cmd);
+               return;
+       case NFT_AFL_FUZZER_NETLINK_RW:
+               break;
+       }
+
+       fault_inject_enable(&state);
+       nft_run_cmd_from_buffer(ctx, input_cmd);
+       fault_inject_disable(&state);
+
+       kernel_is_tainted();
+}
+
+static FILE *fault_inject_open(void)
+{
+       return fopen(self_fault_inject_file, "r+");
+}
+
+static bool nft_afl_state_init(struct nft_afl_state *state)
+{
+       state->make_it_fail_fp = fault_inject_open();
+       return true;
+}
+
+static int nft_afl_init(struct nft_ctx *ctx, enum nft_afl_fuzzer_stage stage)
+{
+#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
+       const char instrumented[] = "afl instrumented";
+#else
+       const char instrumented[] = "no afl instrumentation";
+#endif
+       unsigned int input_flags;
+
+       nft_afl_print_build_info(stderr);
+
+       if (!nft_afl_state_init(&state))
+               return -1;
+
+       ctx->afl_ctx_stage = stage;
+
+       if (state.make_it_fail_fp) {
+               unsigned int value;
+               int ret;
+
+               rewind(state.make_it_fail_fp);
+               ret = fscanf(state.make_it_fail_fp, "%u", &value);
+               if (ret != 1 || value != 1) {
+                       fclose(state.make_it_fail_fp);
+                       state.make_it_fail_fp = NULL;
+               }
+
+               /* if its enabled, disable and then re-enable ONLY
+                * when submitting data to the kernel.
+                *
+                * Otherwise even libnftables memory allocations could fail
+                * which is not what we want.
+                */
+               fault_inject_disable(&state);
+       }
+
+       input_flags = nft_ctx_input_get_flags(ctx);
+       input_flags |= NFT_CTX_INPUT_NO_DNS;
+       nft_ctx_input_set_flags(ctx, input_flags);
+
+       if (stage < NFT_AFL_FUZZER_NETLINK_RW)
+               nft_ctx_set_dry_run(ctx, true);
+
+       fprintf(stderr, "starting (%s, %s fault injection)", instrumented, state.make_it_fail_fp ? "with" : "no");
+       return 0;
+}
+
+static uint32_t random_u32(void)
+{
+       uint32_t v;
+
+       if (getrandom(&v, sizeof(v), GRND_NONBLOCK) == (ssize_t)sizeof(v))
+               return v;
+
+       v = (uint32_t)time(NULL) + (uint32_t)getpid();
+       srandom(v + random());
+
+       v = random();
+       v += random();
+
+       return v;
+}
+
+static uint32_t random_outflags(void)
+{
+       uint32_t random_value;
+
+       random_value = random_u32();
+
+       /* never enable json automatically, rely on command line for this */
+       return random_value & ~NFT_CTX_OUTPUT_JSON;
+}
+
+static void show_help(const char *name)
+{
+       int i;
+
+       printf("Usage: %s [ options ]\n\nOptions\n", name);
+
+       for (i = 0; i < (int)(sizeof(options) / sizeof(options[0])) - 1; i++) {
+               printf("--%s", options[i].name);
+
+               if (options[i].has_arg)
+                       fputs(" <arg>", stdout);
+
+               puts("");
+       }
+
+       puts("");
+       puts("Also see \"nft --help\" for more information on common command line options.");
+}
+
+static void show_help_fuzzer(const char *name)
+{
+       int i;
+
+       show_help(name);
+       puts("");
+
+       for (i = 0; i < (int)(sizeof(fuzzer_stage_param) / sizeof(fuzzer_stage_param[0])); i++)
+               printf("--fuzzer %s\n", fuzzer_stage_param[i].name);
+
+       puts("Hint: combine \"--fuzzer netlink-rw\" with \"--check\" to not apply changes\n");
+}
+
+static int nft_afl_main(struct nft_ctx *ctx)
+{
+       unsigned char *buf;
+       ssize_t len;
+
+       if (kernel_is_tainted())
+               return -1;
+
+       if (state.make_it_fail_fp) {
+               FILE *fp = fault_inject_open();
+
+               /* reopen is needed because /proc/self is a symlink, i.e.
+                * fp refers to parent process, not "us".
+                */
+               if (!fp) {
+                       fprintf(stderr, "Could not reopen %s: %s", self_fault_inject_file, strerror(errno));
+                       return -1;
+               }
+
+               fclose(state.make_it_fail_fp);
+               state.make_it_fail_fp = fp;
+       }
+
+       buf = __AFL_FUZZ_TESTCASE_BUF;
+
+       while (__AFL_LOOP(UINT_MAX)) {
+               char *input;
+
+               len = __AFL_FUZZ_TESTCASE_LEN;  // do not use the macro directly in a call!
+
+               input = preprocess(buf, len);
+               if (!input)
+                       continue;
+
+               /* buf is null terminated at this point */
+               nft_afl_run_cmd(ctx, input);
+       }
+
+       /* afl-fuzz will restart us. */
+       return 0;
+}
+
+int main(int argc, char *argv[])
+{
+       enum nft_afl_fuzzer_stage fuzzer_stage = NFT_AFL_FUZZER_NETLINK_RO;
+       unsigned int json_output_flag = 0;
+       bool random_output_flags = false;
+       int ret = EXIT_SUCCESS;
+       struct nft_ctx *nft;
+       unsigned int i;
+
+       nft = nft_ctx_new(NFT_CTX_DEFAULT);
+
+       while (1) {
+               int val = getopt_long(argc, argv, optstring, options, NULL);
+               if (val == -1)
+                       break;
+
+               switch (val) {
+               case OPT_HELP:
+                       show_help(argv[0]);
+                       goto out;
+               case OPT_CHECK:
+                       nft_ctx_set_dry_run(nft, true);
+                       break;
+               case OPT_FUZZER:
+                       for (i = 0; i < array_size(fuzzer_stage_param); i++) {
+                               if (strcmp(fuzzer_stage_param[i].name, optarg))
+                                       continue;
+                               fuzzer_stage = fuzzer_stage_param[i].stage;
+                               break;
+                       }
+
+                       if (!strcmp(optarg, "help")) {
+                               show_help_fuzzer(argv[0]);
+                               goto out;
+                       }
+
+                       if (i == array_size(fuzzer_stage_param)) {
+                               fprintf(stderr, "invalid fuzzer stage `%s'\n",
+                                       optarg);
+                               show_help_fuzzer(argv[0]);
+                               goto out_fail;
+                       }
+                       break;
+               case OPT_RANDOUTFLAGS:
+                       random_output_flags = true;
+                       break;
+               case OPT_JSON:
+#ifdef HAVE_LIBJANSSON
+                       json_output_flag = NFT_CTX_OUTPUT_JSON;
+#else
+                       fprintf(stderr, "Error: JSON support not compiled-in\n");
+                       goto out_fail;
+#endif
+               case OPT_INVALID:
+                       nft_afl_exit("Unknown option");
+                       goto out_fail;
+               }
+       }
+
+       ret = nft_afl_init(nft, fuzzer_stage);
+       if (ret != 0)
+               nft_afl_exit("cannot initialize");
+
+       __AFL_INIT();
+
+       if (random_output_flags) {
+               unsigned int output_flags = random_outflags();
+
+               nft_ctx_output_set_flags(nft, output_flags | json_output_flag);
+       }
+
+       ret = nft_afl_main(nft);
+       if (ret != 0)
+               nft_afl_exit("fatal error");
+out:
+       nft_ctx_free(nft);
+       return ret;
+out_fail:
+       nft_ctx_free(nft);
+       return EXIT_FAILURE;
+}