From 32c994f84904e9854d527217ececf0b97d89410d Mon Sep 17 00:00:00 2001 From: Florian Westphal Date: Mon, 17 Nov 2025 23:13:49 +0100 Subject: [PATCH] src: move fuzzer functionality to separate tool 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 Acked-by: Pablo Neira Ayuso --- 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 d2cae2a3..e278a193 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 a23bcef1..69858295 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 79925952..00000000 --- a/src/afl++.c +++ /dev/null @@ -1,219 +0,0 @@ -/* - * Copyright (c) Red Hat GmbH. Author: Florian Westphal - * - * 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 -#include - -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include - -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 c2e909d2..29b0533d 100644 --- a/src/main.c +++ b/src/main.c @@ -21,7 +21,6 @@ #include #include #include -#include 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 9ff7e648..ef3219ff 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 + +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 + -- 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 ee9d98fe..9638bfb0 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 2d06c498..56dd2226 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 00000000..62034de7 --- /dev/null +++ b/tools/nft-afl.c @@ -0,0 +1,437 @@ +/* + * Copyright (c) Red Hat GmbH. Author: Florian Westphal + * + * 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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include +#include + +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(" ", 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.47.3