summaryrefslogtreecommitdiffstats
path: root/src/fuzz.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/fuzz.c')
-rw-r--r--src/fuzz.c541
1 files changed, 541 insertions, 0 deletions
diff --git a/src/fuzz.c b/src/fuzz.c
new file mode 100644
index 0000000..28da976
--- /dev/null
+++ b/src/fuzz.c
@@ -0,0 +1,541 @@
+/*
+ * Copyright (c) 2024-2025 Pablo Neira Ayuso <pablo@netfilter.org>
+ *
+ * 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.
+ */
+
+/* Funded through the NGI0 Entrust established by NLnet (https://nlnet.nl)
+ * with support from the European Commission's Next Generation Internet
+ * programme.
+ */
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/time.h>
+
+#include "fuzz.h"
+
+static const char *fuzz_type_name[] = {
+ [FUZZ_DEL_TABLE] = "deltable",
+ [FUZZ_DEL_BASECHAIN] = "delbasechain",
+ [FUZZ_DEL_CHAIN] = "delchain",
+ [FUZZ_DEL_RULE] = "delrule",
+ [FUZZ_DEL_SET] = "delset",
+ [FUZZ_FLUSH_SET] = "flushset",
+ [FUZZ_DEL_ELEM] = "delelem",
+ [FUZZ_DEL_OBJ] = "delobj",
+ [FUZZ_DUP] = "dup",
+ [FUZZ_REVERSE_COMMIT] = "reverse-commit",
+ [FUZZ_REVERSE_ABORT] = "reverse-abort",
+ [FUZZ_TABLE_DORMANT] = "table-dormant",
+ [FUZZ_TABLE_WAKEUP] = "table-wakeup",
+ [FUZZ_SWAP] = "swap",
+ [FUZZ_BOGUS] = "bogus",
+};
+
+void print_fuzz_modes(void)
+{
+ int i;
+
+ for (i = 0; i < FUZZ_MAX; i++)
+ printf("\t%s\n", fuzz_type_name[i]);
+}
+
+const char *fuzz_type_to_name(uint32_t type)
+{
+ if (type >= FUZZ_MAX)
+ return "unknown";
+
+ return fuzz_type_name[type];
+}
+
+int fuzz_type_to_value(const char *str)
+{
+ int i;
+
+ for (i = 0; i < FUZZ_MAX; i++) {
+ if (!strcmp(fuzz_type_name[i], str))
+ return i;
+ }
+
+ return -1;
+}
+
+struct line_args {
+ char n1[1024];
+ char n2[1024];
+ char n3[1024];
+ char n4[1024];
+ char n5[1024];
+ char n6[1024];
+ char n7[1024];
+ char n8[1024];
+ char n9[1024];
+ char n10[1024];
+ char n11[1024];
+ char n12[1024];
+ char n13[1024];
+ char n14[1024];
+ char n15[1024];
+};
+
+#define BUF_SIZE 1024000
+
+struct fuzz_state_ctx {
+ char buf[1024000];
+ uint32_t len;
+
+ char saved_line[1024];
+
+ uint32_t state;
+ uint32_t line;
+ uint32_t line_from;
+
+ struct line_args saved_args;
+
+ uint32_t more;
+ bool found;
+ bool found2;
+ bool done;
+};
+
+static void add_line(struct fuzz_state_ctx *ctx, const char *line)
+{
+ snprintf(&ctx->buf[ctx->len], BUF_SIZE - ctx->len, "%s", line);
+ ctx->len += strlen(line);
+}
+
+static void reverse_cmd(struct fuzz_state_ctx *ctx, const char *line, int type)
+{
+ struct line_args *args = &ctx->saved_args;
+ int ret;
+
+ add_line(ctx, line);
+
+ if (!strcmp(line, "commit();\n") ||
+ !strcmp(line, "abort();\n"))
+ return;
+
+ if (ctx->done) {
+ ctx->more++;
+ return;
+ }
+
+ switch (type) {
+ case FUZZ_DEL_TABLE:
+ case FUZZ_TABLE_DORMANT:
+ case FUZZ_TABLE_WAKEUP:
+ if (sscanf(line, "add_table(%[^,],%[^,],%[^,],%[^,],%[^)]);",
+ args->n1, args->n2, args->n3, args->n4, args->n5) == 5) {
+ ctx->found2 = 1;
+ }
+ break;
+ case FUZZ_DEL_BASECHAIN:
+ if (sscanf(line, "add_basechain(%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^)]);",
+ args->n1, args->n2, args->n3, args->n4, args->n5, args->n6, args->n7, args->n8, args->n9, args->n10, args->n11, args->n12, args->n13) == 13) {
+ ctx->found2 = 1;
+ }
+ break;
+ case FUZZ_DEL_CHAIN:
+ if (sscanf(line, "add_chain(%[^,],%[^,],%[^,],%[^,],%[^)]);",
+ args->n1, args->n2, args->n3, args->n4, args->n5) == 5) {
+ ctx->found2 = 1;
+ }
+ break;
+ case FUZZ_DEL_RULE:
+ if (sscanf(line, "add_rule(%[^,],%[^,],%[^,],%[^,],%[^)]);",
+ args->n1, args->n2, args->n3, args->n4, args->n5) == 5) {
+ ctx->found2 = 1;
+ }
+ break;
+ case FUZZ_DEL_SET:
+ case FUZZ_FLUSH_SET:
+ if (sscanf(line, "add_set(%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^)]);",
+ args->n1, args->n2, args->n3, args->n4, args->n5, args->n6, args->n7, args->n8, args->n9, args->n10, args->n11, args->n12, args->n13, args->n14, args->n15) == 15) {
+ ctx->found2 = 1;
+ }
+ break;
+ case FUZZ_DEL_OBJ:
+ if (sscanf(line, "add_obj_counter(%[^,],%[^,],%[^,],%[^,],%[^)]);",
+ args->n1, args->n2, args->n3, args->n4, args->n5) == 5) {
+ ctx->found2 = 1;
+ } else if (sscanf(line, "add_obj_quota(%[^,],%[^,],%[^,],%[^,],%[^,],%[^)]);",
+ args->n1, args->n2, args->n3, args->n4, args->n5, args->n6) == 6) {
+ ctx->found2 = 1;
+ } else if (sscanf(line, "add_obj_connlimit(%[^,],%[^,],%[^,],%[^,],%[^)]);",
+ args->n1, args->n2, args->n3, args->n4, args->n5) == 5) {
+ ctx->found2 = 1;
+ } else if (sscanf(line, "add_obj_secmark(%[^,],%[^,],%[^,],%[^)]);",
+ args->n1, args->n2, args->n3, args->n4) == 4) {
+ ctx->found2 = 1;
+ } else if (sscanf(line, "add_obj_synproxy(%[^,],%[^,],%[^,],%[^,],%[^,],%[^)]);",
+ args->n1, args->n2, args->n3, args->n4, args->n5, args->n6) == 6) {
+ ctx->found2 = 1;
+ } else if (sscanf(line, "add_obj_ct_timeout(%[^,],%[^,],%[^,],%[^,],%[^,],%[^)]);",
+ args->n1, args->n2, args->n3, args->n4, args->n5, args->n6) == 6) {
+ ctx->found2 = 1;
+ } else if (sscanf(line, "add_obj_ct_helper(%[^,],%[^,],%[^,],%[^,],%[^,],%[^)]);",
+ args->n1, args->n2, args->n3, args->n4, args->n5, args->n6) == 6) {
+ ctx->found2 = 1;
+ } else if (sscanf(line, "add_obj_ct_expect(%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^)]);",
+ args->n1, args->n2, args->n3, args->n4, args->n5, args->n6, args->n7, args->n8) == 8) {
+ ctx->found2 = 1;
+ } else if (sscanf(line, "add_obj_limit(%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^)]);",
+ args->n1, args->n2, args->n3, args->n4, args->n5, args->n6, args->n7, args->n8) == 8) {
+ ctx->found2 = 1;
+ } else if (sscanf(line, "add_obj_tunnel(%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^)]);",
+ args->n1, args->n2, args->n3, args->n4, args->n5, args->n6, args->n7, args->n8, args->n9, args->n10, args->n11, args->n12, args->n13, args->n14) == 14) {
+ ctx->found2 = 1;
+ }
+ break;
+ }
+
+ if (!ctx->found2)
+ return;
+
+ switch (type) {
+ case FUZZ_DEL_TABLE:
+ ctx->len += snprintf(&ctx->buf[ctx->len], BUF_SIZE - ctx->len, "del_table(%s,%s, NULL);\n", args->n1, args->n2);
+ break;
+ case FUZZ_DEL_BASECHAIN:
+ ctx->len += snprintf(&ctx->buf[ctx->len], BUF_SIZE - ctx->len, "del_basechain(%s,%s, NULL, NULL, NULL, NULL, NULL, NULL);\n", args->n1, args->n2);
+ break;
+ case FUZZ_DEL_CHAIN:
+ ctx->len += snprintf(&ctx->buf[ctx->len], BUF_SIZE - ctx->len, "del_chain(%s);\n", args->n1);
+ break;
+ case FUZZ_DEL_RULE:
+ ctx->len += snprintf(&ctx->buf[ctx->len], BUF_SIZE - ctx->len, "del_rule(%s,%s, NULL);\n", args->n1, args->n2);
+ break;
+ case FUZZ_DEL_SET:
+ ctx->len += snprintf(&ctx->buf[ctx->len], BUF_SIZE - ctx->len, "del_set(%s, NULL);\n", args->n1);
+ break;
+ case FUZZ_FLUSH_SET:
+ ctx->len += snprintf(&ctx->buf[ctx->len], BUF_SIZE - ctx->len, "flush_set(%s, NULL);\n", args->n1);
+ break;
+ case FUZZ_TABLE_DORMANT:
+ ctx->len += snprintf(&ctx->buf[ctx->len], BUF_SIZE - ctx->len, "add_table(%s,%s, \"0x1\",%s,%s);\n", args->n1, args->n2, args->n4, args->n5);
+ break;
+ case FUZZ_TABLE_WAKEUP:
+ ctx->len += snprintf(&ctx->buf[ctx->len], BUF_SIZE - ctx->len, "add_table(%s,%s, \"0x0\",%s,%s);\n", args->n1, args->n2, args->n4, args->n5);
+ break;
+ case FUZZ_DEL_OBJ:
+ ctx->len += snprintf(&ctx->buf[ctx->len], BUF_SIZE - ctx->len, "del_obj_unknown(%s, NULL, NULL, \"1\");\n", args->n1);
+ break;
+ }
+ ctx->line_from = ctx->line;
+ ctx->done = 1;
+}
+
+static void del_elem(struct fuzz_state_ctx *ctx, const char *line)
+{
+ struct line_args args = {};
+
+ add_line(ctx, line);
+
+ if (sscanf(line, "add_elem(%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^)]);",
+ args.n1, args.n2, args.n3, args.n4, args.n5, args.n6, args.n7, args.n8, args.n9) != 9)
+ return;
+
+ if (ctx->found) {
+ ctx->more++;
+ return;
+ }
+
+ ctx->len += snprintf(&ctx->buf[ctx->len], BUF_SIZE - ctx->len, "del_elem(%s, NULL, NULL);\n", args.n1);
+ ctx->line_from = ctx->line;
+ ctx->found = 1;
+}
+
+static void dup_line(struct fuzz_state_ctx *ctx, const char *line)
+{
+ add_line(ctx, line);
+
+ if (!strcmp(line, "commit();\n") ||
+ !strcmp(line, "abort();\n"))
+ return;
+
+ if (ctx->found) {
+ ctx->more++;
+ return;
+ }
+
+ snprintf(&ctx->buf[ctx->len], BUF_SIZE - ctx->len, "%s", line);
+ ctx->len += strlen(line);
+
+ ctx->line_from = ctx->line;
+ ctx->found = 1;
+}
+
+static void reverse_abort(struct fuzz_state_ctx *ctx, const char *line)
+{
+ if (strcmp(line, "abort();\n")) {
+ add_line(ctx, line);
+ return;
+ }
+
+ if (ctx->found) {
+ add_line(ctx, line);
+ ctx->more++;
+ return;
+ }
+
+ ctx->len += snprintf(&ctx->buf[ctx->len], BUF_SIZE - ctx->len, "commit();\n");
+ ctx->line_from = ctx->line;
+ ctx->found = 1;
+}
+
+static void reverse_commit(struct fuzz_state_ctx *ctx, const char *line)
+{
+ if (strcmp(line, "commit();\n")) {
+ add_line(ctx, line);
+ return;
+ }
+
+ if (ctx->found) {
+ add_line(ctx, line);
+ ctx->more++;
+ return;
+ }
+
+ ctx->len += snprintf(&ctx->buf[ctx->len], BUF_SIZE - ctx->len, "abort();\n");
+ ctx->line_from = ctx->line;
+ ctx->found = 1;
+}
+
+static void swap(struct fuzz_state_ctx *ctx, const char *line)
+{
+ if (!strcmp(line, "commit();\n") ||
+ !strcmp(line, "abort();\n")) {
+ if (ctx->done) {
+ if (!ctx->found) {
+ add_line(ctx, ctx->saved_line);
+ ctx->found = 1;
+ }
+ }
+ add_line(ctx, line);
+ return;
+ }
+
+ if (!strncmp(line, "add_table(", strlen("add_table(")) ||
+ !strncmp(line, "set_table(", strlen("set_table("))) {
+ add_line(ctx, line);
+ return;
+ }
+
+ if (ctx->done) {
+ add_line(ctx, line);
+ if (!ctx->found) {
+ add_line(ctx, ctx->saved_line);
+ ctx->found = 1;
+ }
+ ctx->more++;
+ return;
+ }
+
+ snprintf(ctx->saved_line, sizeof(ctx->saved_line), "%s", line);
+ ctx->line_from = ctx->line;
+ ctx->done = 1;
+}
+
+static void bogus(struct fuzz_state_ctx *ctx, const char *line)
+{
+ struct line_args args = {};
+
+ add_line(ctx, line);
+
+ if (!strcmp(line, "commit();\n") ||
+ !strcmp(line, "abort();\n"))
+ return;
+
+ if (ctx->done) {
+ ctx->more++;
+ return;
+ }
+
+ ctx->len += snprintf(&ctx->buf[ctx->len], BUF_SIZE - ctx->len, "del_table(NFPROTO_IPV4, \"bogus123456unknown\", NULL);\n");
+ ctx->line_from = ctx->line;
+ ctx->done = 1;
+}
+
+static void fuzz_step_cb(struct fuzz_state_ctx *ctx, const char *line)
+{
+ if (ctx->line++ < ctx->line_from) {
+ add_line(ctx, line);
+ return;
+ }
+
+ switch (ctx->state) {
+ case FUZZ_DEL_TABLE:
+ case FUZZ_DEL_BASECHAIN:
+ case FUZZ_DEL_CHAIN:
+ case FUZZ_DEL_RULE:
+ case FUZZ_DEL_SET:
+ case FUZZ_FLUSH_SET:
+ case FUZZ_DEL_OBJ:
+ case FUZZ_TABLE_DORMANT:
+ case FUZZ_TABLE_WAKEUP:
+ reverse_cmd(ctx, line, ctx->state);
+ break;
+ case FUZZ_DEL_ELEM:
+ del_elem(ctx, line);
+ break;
+ case FUZZ_DUP:
+ dup_line(ctx, line);
+ break;
+ case FUZZ_REVERSE_COMMIT:
+ reverse_commit(ctx, line);
+ break;
+ case FUZZ_REVERSE_ABORT:
+ reverse_abort(ctx, line);
+ break;
+ case FUZZ_SWAP:
+ swap(ctx, line);
+ break;
+ case FUZZ_BOGUS:
+ bogus(ctx, line);
+ break;
+ }
+}
+
+static FILE *fuzz_step(struct fuzz_state_ctx *ctx, FILE *fp,
+ void (*fuzz_step_cb)(struct fuzz_state_ctx *ctx, const char *line))
+{
+ const char *line;
+ char buf[1024];
+ FILE *new_fp;
+
+ ctx->line = 0;
+ ctx->len = 0;
+ ctx->more = 0;
+ ctx->found = 0;
+ ctx->done = 0;
+
+ line = fgets(buf, sizeof(buf), fp);
+ while (line) {
+ fuzz_step_cb(ctx, line);
+ line = fgets(buf, sizeof(buf), fp);
+ }
+
+ new_fp = fmemopen(ctx->buf, ctx->len, "r");
+ if (!new_fp) {
+ fprintf(stderr, "cannot open memfile\n");
+ return NULL;
+ }
+
+ return new_fp;
+}
+
+static int fuzz_print(FILE *fp)
+{
+ const char *line;
+ char buf[1024];
+ FILE *fp2;
+
+ line = fgets(buf, sizeof(buf), fp);
+ while (line) {
+ fprintf(stdout, "%s", line);
+ line = fgets(buf, sizeof(buf), fp);
+ }
+ rewind(fp);
+
+ return 0;
+}
+
+int fuzz_loop(struct fuzz_ctx *ctx, FILE *fp, int (*fuzz_cb)(FILE *fp))
+{
+ struct fuzz_state_ctx state_ctx = {
+ .state = ctx->type[ctx->index],
+ };
+ struct timeval tv_cur, tv_diff;
+ FILE *new_fp;
+ int ret = 0;
+
+ if (ctx->index >= ctx->num_types) {
+ if (ctx->debug)
+ printf("<<<< fuzz_loop backtrack STACK limit reached\n");
+
+ ctx->index--;
+ return 0;
+ }
+
+ if (ctx->debug) {
+ printf(">>>> fuzz_loop at index %d in state=%x\n",
+ ctx->index, ctx->type[ctx->index]);
+ }
+
+ while (1) {
+ new_fp = fuzz_step(&state_ctx, fp, fuzz_step_cb);
+ if (!new_fp)
+ break;
+
+ ctx->fuzz_steps++;
+ ctx->total_fuzz_steps++;
+ if (ctx->fuzz_steps % 100 == 0) {
+ gettimeofday(&tv_cur, NULL);
+ timersub(&tv_cur, &ctx->tv_start, &tv_diff);
+ printf("... fuzz steps: %u in %u.%u seconds\n",
+ ctx->fuzz_steps, tv_diff.tv_sec, tv_diff.tv_usec);
+ }
+
+ if (ctx->debug)
+ fuzz_print(new_fp);
+
+ if (fuzz_cb(new_fp) < 0) {
+ ret = -1;
+ fclose(new_fp);
+ break;
+ }
+
+ if (ctx->index < ctx->num_types) {
+ ctx->index++;
+ if (fuzz_loop(ctx, new_fp, fuzz_cb) < 0) {
+ ret = -1;
+ fclose(new_fp);
+ break;
+ }
+ }
+
+ fclose(new_fp);
+
+ if (!state_ctx.more) {
+ if (ctx->debug) {
+ printf("<<<< fuzz_loop backtrack NO MORE at index %u in state=%x\n",
+ ctx->index, ctx->type[ctx->index]);
+ }
+ if (ctx->index > 0)
+ ctx->index--;
+ break;
+ } else {
+ if (ctx->debug) {
+ printf("==== still more tries at index %u in state=%x\n",
+ ctx->index, ctx->type[ctx->index]);
+ }
+ }
+
+ rewind(fp);
+ }
+
+ return ret;
+}
+
+int fuzz(struct fuzz_ctx *ctx, FILE *fp, int (*fuzz_cb)(FILE *fp))
+{
+ struct timeval tv_cur;
+ int ret;
+
+ gettimeofday(&ctx->tv_start, NULL);
+ ctx->fuzz_steps = 0;
+
+ ret = fuzz_loop(ctx, fp, fuzz_cb);
+
+ gettimeofday(&tv_cur, NULL);
+ timersub(&tv_cur, &ctx->tv_start, &ctx->tv_delta);
+
+ return ret;
+}