diff options
Diffstat (limited to 'src/fuzz.c')
-rw-r--r-- | src/fuzz.c | 541 |
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; +} |