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>
This commit is contained in:
Florian Westphal 2025-11-17 23:13:49 +01:00
parent 6cee2d0e7b
commit 32c994f849
8 changed files with 479 additions and 360 deletions

View File

@ -31,6 +31,7 @@ LDADD =
lib_LTLIBRARIES = lib_LTLIBRARIES =
noinst_LTLIBRARIES = noinst_LTLIBRARIES =
sbin_PROGRAMS = sbin_PROGRAMS =
noinst_PROGRAMS =
check_PROGRAMS = check_PROGRAMS =
dist_man_MANS = dist_man_MANS =
CLEANFILES = CLEANFILES =
@ -294,10 +295,6 @@ sbin_PROGRAMS += src/nft
src_nft_SOURCES = src/main.c src_nft_SOURCES = src/main.c
if BUILD_AFL
src_nft_SOURCES += src/afl++.c
endif
if BUILD_CLI if BUILD_CLI
src_nft_SOURCES += src/cli.c src_nft_SOURCES += src/cli.c
endif 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 if BUILD_MAN
dist_man_MANS += \ dist_man_MANS += \

View File

@ -20,29 +20,4 @@ enum nft_afl_fuzzer_stage {
NFT_AFL_FUZZER_NETLINK_RW, 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 #endif

View File

@ -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;
}

View File

@ -21,7 +21,6 @@
#include <nftables/libnftables.h> #include <nftables/libnftables.h>
#include <utils.h> #include <utils.h>
#include <cli.h> #include <cli.h>
#include <afl++.h>
static struct nft_ctx *nft; static struct nft_ctx *nft;
@ -87,11 +86,6 @@ enum opt_vals {
OPT_TERSE = 't', OPT_TERSE = 't',
OPT_OPTIMIZE = 'o', OPT_OPTIMIZE = 'o',
OPT_INVALID = '?', OPT_INVALID = '?',
#if HAVE_FUZZER_BUILD
/* keep last */
OPT_FUZZER = 254
#endif
}; };
struct nft_opt { 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)"), "Specify debugging level (scanner, parser, eval, netlink, mnl, proto-ctx, segtree, all)"),
[IDX_OPTIMIZE] = NFT_OPT("optimize", OPT_OPTIMIZE, NULL, [IDX_OPTIMIZE] = NFT_OPT("optimize", OPT_OPTIMIZE, NULL,
"Optimize ruleset"), "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])) #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]); print_option(&nft_options[i]);
fputs("\n", stdout); fputs("\n", stdout);
nft_afl_print_build_info(stdout);
} }
static void show_version(void) static void show_version(void)
@ -286,7 +275,6 @@ static void show_version(void)
PACKAGE_NAME, PACKAGE_VERSION, RELEASE_NAME, PACKAGE_NAME, PACKAGE_VERSION, RELEASE_NAME,
cli, json, minigmp, xt); cli, json, minigmp, xt);
nft_afl_print_build_info(stdout);
} }
static const struct { 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) static void nft_options_error(int argc, char * const argv[], int pos)
{ {
int i; int i;
@ -407,7 +363,6 @@ static bool nft_options_check(int argc, char * const argv[])
int main(int argc, char * const *argv) int main(int argc, char * const *argv)
{ {
const struct option *options = get_options(); const struct option *options = get_options();
enum nft_afl_fuzzer_stage fuzzer_stage = 0;
bool interactive = false, define = false; bool interactive = false, define = false;
const char *optstring = get_optstring(); const char *optstring = get_optstring();
unsigned int output_flags = 0; unsigned int output_flags = 0;
@ -549,26 +504,6 @@ int main(int argc, char * const *argv)
case OPT_OPTIMIZE: case OPT_OPTIMIZE:
nft_ctx_set_optimize(nft, 0x1); nft_ctx_set_optimize(nft, 0x1);
break; 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: case OPT_INVALID:
goto out_fail; goto out_fail;
} }
@ -581,38 +516,6 @@ int main(int argc, char * const *argv)
nft_ctx_output_set_flags(nft, output_flags); 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) { if (optind != argc) {
char *buf; char *buf;

View File

@ -17,15 +17,26 @@ Important options are:
--disable-shared, so that libnftables is instrumented too. --disable-shared, so that libnftables is instrumented too.
--enable-fuzzer --enable-fuzzer
--enable-fuzzer is not strictly required, you can run normal nft builds under --enable-fuzzer provides tools/nft-afl that allows more fine-grained control
afl-fuzz too. But the execution speed will be much slower. 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 --check
more fine-grained control over what code paths should be covered by the fuzzing
process.
When fuzzing in this mode, then each new input passes through the following Prevents the fuzzer-generated rulesets from being committed to the kernel.
processing stages:
--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': 1: 'parser':
Only run / exercise the flex/bison parser. Only run / exercise the flex/bison parser.
@ -38,6 +49,7 @@ processing stages:
3: 'netlink-ro': 3: 'netlink-ro':
Also build/serialize the ruleset into netlink-commands to send to the 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. kernel, but omit the final write so the kernel will not see the message.
This is the default mode.
4: 'netlink-rw': 4: 'netlink-rw':
Same as 3 but the message will be sent to the kernel. 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 All --fuzzer modes EXCEPT 'netlink-rw' do imply --check as these modes never
alter state in the kernel. 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. 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 \ unshare -n \
afl-fuzz -g 16 -G 2000 -t 5000 -i tests/afl++/in -o tests/afl++/out \ 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) arg should be either "netlink-ro" (if you only want to exercise libnftables
or "netlink-rw" (if you want to test kernel code paths too). 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 Its also a good idea to do this from tmux/screen so you can disconnect/reattach
later. You can also spawn multiple instances. later. You can also spawn multiple instances.

View File

@ -3,7 +3,7 @@
set -e set -e
ME=$(dirname $0) ME=$(dirname $0)
SRC_NFT="$(dirname $0)/../../src/nft" SRC_NFT="$(dirname $0)/../../tools/nft-afl"
cd $ME/../.. cd $ME/../..
@ -43,5 +43,5 @@ done
echo "built initial set of inputs to fuzz from shell test case dump files." echo "built initial set of inputs to fuzz from shell test case dump files."
echo "sample invocations:" 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 -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-json -o tests/afl++/out -- "$SRC_NFT" -j --check --fuzzer netlink-rw"

4
tools/.gitignore vendored
View File

@ -1 +1,5 @@
nftables.service nftables.service
*.o
.deps/
.libs/
nft-afl

437
tools/nft-afl.c Normal file
View File

@ -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;
}