diff options
| author | Florian Westphal <fw@strlen.de> | 2025-11-17 23:13:49 +0100 |
|---|---|---|
| committer | Florian Westphal <fw@strlen.de> | 2025-11-20 22:16:43 +0100 |
| commit | 32c994f84904e9854d527217ececf0b97d89410d (patch) | |
| tree | db2a1a6468fc1cf9c9c15b08f27dccb6074a2d44 /tools | |
| parent | 6cee2d0e7b4dc3274728ae6681d87e356ddcf31a (diff) | |
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 <fw@strlen.de>
Acked-by: Pablo Neira Ayuso <pablo@netfilter.org>
Diffstat (limited to 'tools')
| -rw-r--r-- | tools/.gitignore | 4 | ||||
| -rw-r--r-- | tools/nft-afl.c | 437 |
2 files changed, 441 insertions, 0 deletions
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 <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; +} |
