diff options
| author | Randy Palamar <randy@rnpnr.xyz> | 2024-05-21 19:53:22 -0600 |
|---|---|---|
| committer | Randy Palamar <randy@rnpnr.xyz> | 2024-05-21 19:53:22 -0600 |
| commit | b7074021b7bfb0932b889b9560dd22df31cef818 (patch) | |
| tree | 0295b18de8fb8ea5289cbda95675687ae06025ff /test/fuzz | |
| parent | b7f8018a00be930e3f2b864949aec1f91291309c (diff) | |
| parent | efafa3c178268a4149fc3e432bc1174a013c16de (diff) | |
| download | vis-b7074021b7bfb0932b889b9560dd22df31cef818.tar.gz vis-b7074021b7bfb0932b889b9560dd22df31cef818.tar.xz | |
Merge vis-tests into test directory
Going forward all tests should be submitted here directly.
Diffstat (limited to 'test/fuzz')
| -rw-r--r-- | test/fuzz/.gitignore | 4 | ||||
| -rw-r--r-- | test/fuzz/Makefile | 47 | ||||
| -rw-r--r-- | test/fuzz/README.md | 30 | ||||
| -rw-r--r-- | test/fuzz/buffer-fuzzer.c | 106 | ||||
| -rw-r--r-- | test/fuzz/dictionaries/buffer-fuzzer.dict | 15 | ||||
| -rw-r--r-- | test/fuzz/dictionaries/text-fuzzer.dict | 20 | ||||
| l--------- | test/fuzz/dictionaries/text-libfuzzer.dict | 1 | ||||
| -rw-r--r-- | test/fuzz/fuzzer.h | 18 | ||||
| -rw-r--r-- | test/fuzz/inputs/buffer-fuzzer/buffer-fuzzer.in | 9 | ||||
| -rw-r--r-- | test/fuzz/inputs/text-fuzzer/text-fuzzer.in | 13 | ||||
| l--------- | test/fuzz/inputs/text-libfuzzer/text-libfuzzer.in | 1 | ||||
| -rw-r--r-- | test/fuzz/text-fuzzer.c | 317 |
12 files changed, 581 insertions, 0 deletions
diff --git a/test/fuzz/.gitignore b/test/fuzz/.gitignore new file mode 100644 index 0000000..17b5c17 --- /dev/null +++ b/test/fuzz/.gitignore @@ -0,0 +1,4 @@ +/results +/text-fuzzer +/text-libfuzzer +/buffer-fuzzer diff --git a/test/fuzz/Makefile b/test/fuzz/Makefile new file mode 100644 index 0000000..9aade50 --- /dev/null +++ b/test/fuzz/Makefile @@ -0,0 +1,47 @@ +-include ../../config.mk + +ALL = text-fuzzer text-libfuzzer buffer-fuzzer +CC = afl-gcc +CFLAGS += -I. -I../.. -DBUFFER_SIZE=4 -DBLOCK_SIZE=4 + +TEXT_SRC = ../../text.c ../../text-common.c ../../text-io.c ../../text-iterator.c ../../text-util.c ../../text-motions.c ../../text-objects.c ../../text-regex.c ../../array.c + +test: $(ALL) + +text-fuzzer: text-fuzzer.c fuzzer.h $(TEXT_SRC) + @echo Compiling $@ binary + @${CC} ${CFLAGS} ${CFLAGS_STD} ${CFLAGS_LIBC} ${CFLAGS_EXTRA} ${filter %.c, $^} ${LDFLAGS} -o $@ + +text-libfuzzer: text-fuzzer.c fuzzer.h $(TEXT_SRC) + @echo Compiling $@ binary + @${CC} ${CFLAGS} ${CFLAGS_STD} ${CFLAGS_LIBC} ${CFLAGS_EXTRA} -DLIBFUZZER ${filter %.c, $^} -fsanitize=fuzzer,address,undefined ${LDFLAGS} -o $@ + +buffer-fuzzer: buffer-fuzzer.c fuzzer.h ../../buffer.c + @echo Compiling $@ binary + @${CC} ${CFLAGS} ${CFLAGS_STD} ${CFLAGS_LIBC} ${CFLAGS_EXTRA} ${filter %.c, $^} ${LDFLAGS} -o $@ + +debug: clean + $(MAKE) CFLAGS_EXTRA='${CFLAGS_EXTRA} ${CFLAGS_DEBUG}' + +afl-fuzz-text: text-fuzzer + @mkdir -p "results/$<" + @afl-fuzz -i - -x "dictionaries/$<.dict" -o "results/$<" -- "./$<" || \ + afl-fuzz -i "inputs/$<" -x "dictionaries/$<.dict" -o "results/$<" -- "./$<" + +libfuzzer-text: text-libfuzzer + @mkdir -p "results/$<" + @./$< -close_fd_mask=1 -only_ascii=1 -print_final_stats=1 "-dict=dictionaries/$<.dict" "inputs/$<" "results/$<" + +afl-fuzz-buffer: buffer-fuzzer + @mkdir -p "results/$<" + @afl-fuzz -i - -x "dictionaries/$<.dict" -o "results/$<" -- "./$<" || \ + afl-fuzz -i "inputs/$<" -x "dictionaries/$<.dict" -o "results/$<" -- "./$<" + +clean: + @echo cleaning + @rm -f $(ALL) + +distclean: clean + @rm -rf results/ + +.PHONY: clean distclean debug afl-fuzz-text libfuzzer-text afl-fuzz-buffer diff --git a/test/fuzz/README.md b/test/fuzz/README.md new file mode 100644 index 0000000..3eabb7a --- /dev/null +++ b/test/fuzz/README.md @@ -0,0 +1,30 @@ +Fuzzing infrastructure for low level code used by vis +----------------------------------------------------- + +This directory contains some simple command line applications +which expose core library interfaces through the standard I/O +streams. They are intended to be used as test drivers for +fuzzers like [AFL](http://lcamtuf.coredump.cx/afl/). + +Run one of the `make afl-fuzz-*` targets to start fuzzing a +specific instrumented binary using `afl-fuzz(1)`. By default +it will try to resume a previous fuzzing session, before +starting a new one if that fails. + +The following files are used: + + * `$APP-fuzzer.c` application exposing a simple text interface + * `fuzzer.h` common code used among different fuzzing drivers + * `./input/$APP/` intial test input, one file per test + * `./dictionaries/$APP.dict` a dictionary with valid syntax tokens + * `./results/$APP/` the fuzzing results are stored here + +See the AFL documentation for further information. + +In the future we might also use [libFuzzer](http://llvm.org/docs/LibFuzzer.html) +for further fuzzing. + +Quick start example: + + $ make afl-fuzz-text + diff --git a/test/fuzz/buffer-fuzzer.c b/test/fuzz/buffer-fuzzer.c new file mode 100644 index 0000000..fea3ec8 --- /dev/null +++ b/test/fuzz/buffer-fuzzer.c @@ -0,0 +1,106 @@ +#include <stddef.h> +#include <stdbool.h> +#include <string.h> +#include <errno.h> +#include <stdio.h> +#include <unistd.h> +#include "fuzzer.h" +#include "buffer.h" +#include "util.h" + +#ifndef BUFSIZ +#define BUFSIZ 1024 +#endif + +typedef enum CmdStatus (*Cmd)(Buffer *buf, const char *cmd); + +static enum CmdStatus cmd_insert(Buffer *buf, const char *cmd) { + char data[BUFSIZ]; + size_t pos; + if (sscanf(cmd, "%zu %s\n", &pos, data) != 2) + return CMD_ERR; + return buffer_insert0(buf, pos, data); +} + +static enum CmdStatus cmd_set(Buffer *buf, const char *cmd) { + char data[BUFSIZ]; + if (sscanf(cmd, "%s\n", data) != 1) + return CMD_ERR; + return buffer_put0(buf, data); +} + +static enum CmdStatus cmd_delete(Buffer *buf, const char *cmd) { + size_t pos, len; + if (sscanf(cmd, "%zu %zu", &pos, &len) != 2) + return CMD_ERR; + return buffer_remove(buf, pos, len); +} + +static enum CmdStatus cmd_clear(Buffer *buf, const char *cmd) { + buffer_clear(buf); + return CMD_OK; +} + +static enum CmdStatus cmd_size(Buffer *buf, const char *cmd) { + printf("%zu bytes\n", buffer_length(buf)); + return CMD_OK; +} + +static enum CmdStatus cmd_capacity(Buffer *buf, const char *cmd) { + printf("%zu bytes\n", buffer_capacity(buf)); + return CMD_OK; +} + +static enum CmdStatus cmd_print(Buffer *buf, const char *cmd) { + size_t len = buffer_length(buf); + const char *data = buffer_content(buf); + if (data && fwrite(data, len, 1, stdout) != 1) + return CMD_ERR; + if (data) + puts(""); + return CMD_OK; +} + +static enum CmdStatus cmd_quit(Buffer *buf, const char *cmd) { + return CMD_QUIT; +} + +static Cmd commands[] = { + ['?'] = cmd_capacity, + ['='] = cmd_set, + ['#'] = cmd_size, + ['c'] = cmd_clear, + ['d'] = cmd_delete, + ['i'] = cmd_insert, + ['p'] = cmd_print, + ['q'] = cmd_quit, +}; + +int main(int argc, char *argv[]) { + char line[BUFSIZ]; + Buffer buf; + buffer_init(&buf); + + for (;;) { + printf("> "); + if (!fgets(line, sizeof(line), stdin)) + break; + if (!isatty(0)) + printf("%s", line); + if (line[0] == '\n') + continue; + size_t idx = line[0]; + if (idx < LENGTH(commands) && commands[idx]) { + enum CmdStatus ret = commands[idx](&buf, line+1); + printf("%s", cmd_status_msg[ret]); + if (ret == CMD_QUIT) + break; + } else { + puts("Invalid command"); + } + } + + buffer_release(&buf); + + return 0; +} diff --git a/test/fuzz/dictionaries/buffer-fuzzer.dict b/test/fuzz/dictionaries/buffer-fuzzer.dict new file mode 100644 index 0000000..4497343 --- /dev/null +++ b/test/fuzz/dictionaries/buffer-fuzzer.dict @@ -0,0 +1,15 @@ +# AFL dictionary for buffer-fuzzer +# +# Not sure whether it makes sense to specify a dictionary, +# the syntax is quite simple? +# +cmd_capacity="?" +cmd_set="=" +cmd_size="#" +cmd_clear="c" +# cmd_delete="d 0 1" +cmd_delete="d" +# cmd_insert="i 0 text" +cmd_insert="i" +cmd_print="p" +cmd_quit="q" diff --git a/test/fuzz/dictionaries/text-fuzzer.dict b/test/fuzz/dictionaries/text-fuzzer.dict new file mode 100644 index 0000000..389141b --- /dev/null +++ b/test/fuzz/dictionaries/text-fuzzer.dict @@ -0,0 +1,20 @@ +# AFL dictionary for text-fuzzer +# +# Not sure whether it makes sense to specify a dictionary, +# the syntax is quite simple? +# +cmd_earlier="-" +cmd_later="+" +cmd_mark_get="?" +# cmd_mark_set="= 0" +cmd_mark_set="=" +cmd_size="#" +# cmd_delete="d 0 1" +cmd_delete="d" +# cmd_insert="i 0 text" +cmd_insert="i" +cmd_print="p" +cmd_quit="q" +cmd_redo="r" +cmd_snapshot="s" +cmd_undo="u" diff --git a/test/fuzz/dictionaries/text-libfuzzer.dict b/test/fuzz/dictionaries/text-libfuzzer.dict new file mode 120000 index 0000000..f5e49a7 --- /dev/null +++ b/test/fuzz/dictionaries/text-libfuzzer.dict @@ -0,0 +1 @@ +text-fuzzer.dict
\ No newline at end of file diff --git a/test/fuzz/fuzzer.h b/test/fuzz/fuzzer.h new file mode 100644 index 0000000..3265532 --- /dev/null +++ b/test/fuzz/fuzzer.h @@ -0,0 +1,18 @@ +#ifndef FUZZER_H +#define FUZZER_H + +enum CmdStatus { + CMD_FAIL = false, + CMD_OK = true, + CMD_ERR, /* syntax error */ + CMD_QUIT, /* quit, accept no further commands */ +}; + +static const char *cmd_status_msg[] = { + [CMD_FAIL] = "Fail\n", + [CMD_OK] = "", + [CMD_ERR] = "Syntax error\n", + [CMD_QUIT] = "Bye\n", +}; + +#endif diff --git a/test/fuzz/inputs/buffer-fuzzer/buffer-fuzzer.in b/test/fuzz/inputs/buffer-fuzzer/buffer-fuzzer.in new file mode 100644 index 0000000..20120c7 --- /dev/null +++ b/test/fuzz/inputs/buffer-fuzzer/buffer-fuzzer.in @@ -0,0 +1,9 @@ +i 0 text +d 1 2 +p +i 1 ex +p += data +p +c +q diff --git a/test/fuzz/inputs/text-fuzzer/text-fuzzer.in b/test/fuzz/inputs/text-fuzzer/text-fuzzer.in new file mode 100644 index 0000000..11e910c --- /dev/null +++ b/test/fuzz/inputs/text-fuzzer/text-fuzzer.in @@ -0,0 +1,13 @@ +i 0 text +d 1 2 +s +i 1 ex +u +r +- ++ +p +# += 2 +? +q diff --git a/test/fuzz/inputs/text-libfuzzer/text-libfuzzer.in b/test/fuzz/inputs/text-libfuzzer/text-libfuzzer.in new file mode 120000 index 0000000..e351471 --- /dev/null +++ b/test/fuzz/inputs/text-libfuzzer/text-libfuzzer.in @@ -0,0 +1 @@ +../text-fuzzer/text-fuzzer.in
\ No newline at end of file diff --git a/test/fuzz/text-fuzzer.c b/test/fuzz/text-fuzzer.c new file mode 100644 index 0000000..52dd31f --- /dev/null +++ b/test/fuzz/text-fuzzer.c @@ -0,0 +1,317 @@ +#include <stddef.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <stdio.h> +#include <unistd.h> +#include <inttypes.h> +#include "fuzzer.h" +#include "text.h" +#include "text-util.h" +#include "util.h" + +#ifndef BUFSIZ +#define BUFSIZ 1024 +#endif + +typedef enum CmdStatus (*Cmd)(Text *txt, const char *cmd); + +static Mark mark = EMARK; + +static char data[BUFSIZ]; + +static uint64_t bench(void) { + struct timespec ts; + + if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) + return (uint64_t)(ts.tv_sec * 1000000 + ts.tv_nsec / 1000); + else + return 0; +} + +static size_t pos_start(Text *txt) { + return 0; +} + +static size_t pos_middle(Text *txt) { + return text_size(txt) / 2; +} + +static size_t pos_end(Text *txt) { + return text_size(txt); +} + +static size_t pos_random(Text *txt) { + return rand() % (text_size(txt) + 1); +} + +static size_t pos_prev(Text *txt) { + static size_t pos = EPOS; + size_t max = text_size(txt); + if (pos > max) + pos = max; + return pos-- % (max + 1); +} + +static size_t pos_next(Text *txt) { + static size_t pos = 0; + return pos++ % (text_size(txt) + 1); +} + +static size_t pos_stripe(Text *txt) { + static size_t pos = 0; + return pos+=1024 % (text_size(txt) + 1); +} + +static enum CmdStatus bench_insert(Text *txt, size_t pos, const char *cmd) { + return text_insert(txt, pos, data, sizeof data); +} + +static enum CmdStatus bench_delete(Text *txt, size_t pos, const char *cmd) { + return text_delete(txt, pos, 1); +} + +static enum CmdStatus bench_replace(Text *txt, size_t pos, const char *cmd) { + text_delete(txt, pos, 1); + text_insert(txt, pos, "-", 1); + return CMD_OK; +} + +static enum CmdStatus bench_mark(Text *txt, size_t pos, const char *cmd) { + Mark mark = text_mark_set(txt, pos); + if (mark == EMARK) + return CMD_FAIL; + if (text_mark_get(txt, mark) != pos) + return CMD_FAIL; + return CMD_OK; +} + +static enum CmdStatus cmd_bench(Text *txt, const char *cmd) { + + static enum CmdStatus (*bench_cmd[])(Text*, size_t, const char*) = { + ['i'] = bench_insert, + ['d'] = bench_delete, + ['r'] = bench_replace, + ['m'] = bench_mark, + }; + + static size_t (*bench_pos[])(Text*) = { + ['^'] = pos_start, + ['|'] = pos_middle, + ['$'] = pos_end, + ['%'] = pos_random, + ['-'] = pos_prev, + ['+'] = pos_next, + ['~'] = pos_stripe, + }; + + if (!data[0]) { + // make `p` command output more readable + int len = snprintf(data, sizeof data, "[ ... %zu bytes ... ]\n", sizeof data); + memset(data+len, '\r', sizeof(data) - len); + } + + const char *params = cmd; + while (*params == ' ') + params++; + + size_t idx_cmd = params[0]; + if (idx_cmd >= LENGTH(bench_cmd) || !bench_cmd[idx_cmd]) { + puts("Invalid bench command"); + return CMD_ERR; + } + + for (params++; *params == ' '; params++); + + size_t idx_pos = params[0]; + if (idx_pos >= LENGTH(bench_pos) || !bench_pos[idx_pos]) { + puts("Invalid bench position"); + return CMD_ERR; + } + + size_t iter = 1; + sscanf(params+1, "%zu\n", &iter); + + for (size_t i = 1; i <= iter; i++) { + size_t pos = bench_pos[idx_pos](txt); + uint64_t s = bench(); + enum CmdStatus ret = bench_cmd[idx_cmd](txt, pos, NULL); + uint64_t e = bench(); + if (ret != CMD_OK) + return ret; + printf("%zu: %" PRIu64 "us\n", i, e-s); + } + return CMD_OK; +} + +static enum CmdStatus cmd_insert(Text *txt, const char *cmd) { + char data[BUFSIZ]; + size_t pos; + if (sscanf(cmd, "%zu %s\n", &pos, data) != 2) + return CMD_ERR; + size_t len = strlen(data); + return text_insert(txt, pos, data, len); +} + +static enum CmdStatus cmd_delete(Text *txt, const char *cmd) { + size_t pos, len; + if (sscanf(cmd, "%zu %zu", &pos, &len) != 2) + return CMD_ERR; + return text_delete(txt, pos, len); +} + +static enum CmdStatus cmd_size(Text *txt, const char *cmd) { + printf("%zu bytes\n", text_size(txt)); + return CMD_OK; +} + +static enum CmdStatus cmd_snapshot(Text *txt, const char *cmd) { + text_snapshot(txt); + return CMD_OK; +} + +static enum CmdStatus cmd_undo(Text *txt, const char *cmd) { + return text_undo(txt) != EPOS; +} + +static enum CmdStatus cmd_redo(Text *txt, const char *cmd) { + return text_redo(txt) != EPOS; +} + +static enum CmdStatus cmd_earlier(Text *txt, const char *cmd) { + return text_earlier(txt) != EPOS; +} + +static enum CmdStatus cmd_later(Text *txt, const char *cmd) { + return text_later(txt) != EPOS; +} + +static enum CmdStatus cmd_mark_set(Text *txt, const char *cmd) { + size_t pos; + if (sscanf(cmd, "%zu\n", &pos) != 1) + return CMD_ERR; + Mark m = text_mark_set(txt, pos); + if (m != EMARK) + mark = m; + return m != EMARK; +} + +static enum CmdStatus cmd_mark_get(Text *txt, const char *cmd) { + size_t pos = text_mark_get(txt, mark); + if (pos != EPOS) + printf("%zu\n", pos); + return pos != EPOS; +} + +static enum CmdStatus cmd_print(Text *txt, const char *cmd) { + size_t start = 0, size = text_size(txt), rem = size; + for (Iterator it = text_iterator_get(txt, start); + rem > 0 && text_iterator_valid(&it); + text_iterator_next(&it)) { + size_t prem = it.end - it.text; + if (prem > rem) + prem = rem; + if (fwrite(it.text, prem, 1, stdout) != 1) + return CMD_ERR; + rem -= prem; + } + if (rem != size) + puts(""); + return rem == 0; +} + +static enum CmdStatus cmd_info(Text *txt, const char *cmd) { +#ifdef text_info + TextInfo info = text_info(txt); + printf("meta data: %zu\nblocks: %zu\ndata: %zu\nrevisions: %zu\n" + "changes: %zu\nchanges total: %zu\npieces: %zu\npieces total: %zu\n", + info.metadata, info.blocks, info.data, info.revisions, + info.changes, info.changes_total, info.pieces, info.pieces_total); +#endif + return CMD_OK; +} + +static enum CmdStatus cmd_dump(Text *txt, const char *cmd) { +#ifdef text_dump + text_dump(txt, stdout); +#endif + return CMD_OK; +} + +static enum CmdStatus cmd_quit(Text *txt, const char *cmd) { + return CMD_QUIT; +} + +static Cmd commands[] = { + ['%'] = cmd_info, + ['@'] = cmd_dump, + ['-'] = cmd_earlier, + ['+'] = cmd_later, + ['?'] = cmd_mark_get, + ['='] = cmd_mark_set, + ['#'] = cmd_size, + ['b'] = cmd_bench, + ['d'] = cmd_delete, + ['i'] = cmd_insert, + ['p'] = cmd_print, + ['q'] = cmd_quit, + ['r'] = cmd_redo, + ['s'] = cmd_snapshot, + ['u'] = cmd_undo, +}; + +static int repl(const char *name, FILE *input) { + Text *txt = text_load(name); + if (!name) + name = "-"; + if (!txt) { + fprintf(stderr, "Failed to load text from `%s'\n", name); + return 1; + } + + printf("Loaded %zu bytes from `%s'\n", text_size(txt), name); + + char line[BUFSIZ]; + for (;;) { + printf("> "); + if (!fgets(line, sizeof(line), input)) + break; + if (!isatty(0)) + printf("%s", line); + if (line[0] == '\n') + continue; + size_t idx = line[0]; + if (idx < LENGTH(commands) && commands[idx]) { + enum CmdStatus ret = commands[idx](txt, line+1); + printf("%s", cmd_status_msg[ret]); + if (ret == CMD_QUIT) + break; + } else { + puts("Invalid command"); + } + } + + text_free(txt); + + return 0; +} + +#ifdef LIBFUZZER + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t len) { + FILE *input = fmemopen((void*)data, len, "r"); + if (!input) + return 1; + int r = repl(NULL, input); + fclose(input); + return r; +} + +#else + +int main(int argc, char *argv[]) { + return repl(argc == 1 ? NULL : argv[1], stdin); +} + +#endif /* LIBFUZZER */ |
