aboutsummaryrefslogtreecommitdiff
path: root/test/fuzz
diff options
context:
space:
mode:
Diffstat (limited to 'test/fuzz')
-rw-r--r--test/fuzz/.gitignore4
-rw-r--r--test/fuzz/Makefile47
-rw-r--r--test/fuzz/README.md30
-rw-r--r--test/fuzz/buffer-fuzzer.c106
-rw-r--r--test/fuzz/dictionaries/buffer-fuzzer.dict15
-rw-r--r--test/fuzz/dictionaries/text-fuzzer.dict20
l---------test/fuzz/dictionaries/text-libfuzzer.dict1
-rw-r--r--test/fuzz/fuzzer.h18
-rw-r--r--test/fuzz/inputs/buffer-fuzzer/buffer-fuzzer.in9
-rw-r--r--test/fuzz/inputs/text-fuzzer/text-fuzzer.in13
l---------test/fuzz/inputs/text-libfuzzer/text-libfuzzer.in1
-rw-r--r--test/fuzz/text-fuzzer.c317
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 */