All of lore.kernel.org
 help / color / mirror / Atom feed
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
> 
> 

      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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.