From: Pablo Neira Ayuso <pablo@netfilter.org>
To: Florian Westphal <fw@strlen.de>
Cc: netfilter-devel@vger.kernel.org
Subject: Re: [PATCH nft] src: move fuzzer functionality to separate tool
Date: Thu, 20 Nov 2025 22:09:00 +0100 [thread overview]
Message-ID: <aR-DbAhL4i1pnum3@calendula> (raw)
In-Reply-To: <20251120113332.10687-1-fw@strlen.de>
On Thu, Nov 20, 2025 at 12:33:29PM +0100, Florian Westphal wrote:
> 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>
Thanks!
> ---
> Makefile.am | 15 +-
> include/afl++.h | 25 ---
> src/afl++.c | 219 ---------------------
> src/main.c | 97 ---------
> tests/afl++/README | 36 ++--
> tests/afl++/run-afl.sh | 6 +-
> tools/.gitignore | 4 +
> tools/nft-afl.c | 437 +++++++++++++++++++++++++++++++++++++++++
> 8 files changed, 479 insertions(+), 360 deletions(-)
> delete mode 100644 src/afl++.c
> create mode 100644 tools/nft-afl.c
>
> diff --git a/Makefile.am b/Makefile.am
> index d2cae2a31aaa..e278a193942c 100644
> --- a/Makefile.am
> +++ b/Makefile.am
> @@ -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 += \
> diff --git a/include/afl++.h b/include/afl++.h
> index a23bcef1bd0d..69858295ed7c 100644
> --- a/include/afl++.h
> +++ b/include/afl++.h
> @@ -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
> index 79925952cd6f..000000000000
> --- a/src/afl++.c
> +++ /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;
> -}
> diff --git a/src/main.c b/src/main.c
> index c2e909d2841a..29b0533dee7c 100644
> --- a/src/main.c
> +++ b/src/main.c
> @@ -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;
>
> diff --git a/tests/afl++/README b/tests/afl++/README
> index 9ff7e6485987..ef3219ff0c2c 100644
> --- a/tests/afl++/README
> +++ b/tests/afl++/README
> @@ -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.
> diff --git a/tests/afl++/run-afl.sh b/tests/afl++/run-afl.sh
> index ee9d98fee3c5..9638bfb0a88b 100755
> --- a/tests/afl++/run-afl.sh
> +++ b/tests/afl++/run-afl.sh
> @@ -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"
> diff --git a/tools/.gitignore b/tools/.gitignore
> index 2d06c49835b1..56dd2226c1bd 100644
> --- a/tools/.gitignore
> +++ b/tools/.gitignore
> @@ -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
> index 000000000000..62034de7ea27
> --- /dev/null
> +++ b/tools/nft-afl.c
> @@ -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;
> +}
> --
> 2.51.0
>
>
prev parent reply other threads:[~2025-11-20 21:09 UTC|newest]
Thread overview: 2+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-11-20 11:33 [PATCH nft] src: move fuzzer functionality to separate tool Florian Westphal
2025-11-20 21:09 ` Pablo Neira Ayuso [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=aR-DbAhL4i1pnum3@calendula \
--to=pablo@netfilter.org \
--cc=fw@strlen.de \
--cc=netfilter-devel@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).