Message ID | 20200220041118.23264-16-alxndr@bu.edu |
---|---|
State | New |
Headers | show |
Series | Add virtual device fuzzing support | expand |
On Wed, Feb 19, 2020 at 11:11:11PM -0500, Alexander Bulekov wrote: >fork() is a simple way to ensure that state does not leak in between >fuzzing runs. Unfortunately, the fuzzer mutation engine relies on >bitmaps which contain coverage information for each fuzzing run, and >these bitmaps should be copied from the child to the parent(where the >mutation occurs). These bitmaps are created through compile-time >instrumentation and they are not shared with fork()-ed processes, by >default. To address this, we create a shared memory region, adjust its >size and map it _over_ the counter region. Furthermore, libfuzzer >doesn't generally expose the globals that specify the location of the >counters/coverage bitmap. As a workaround, we rely on a custom linker >script which forces all of the bitmaps we care about to be placed in a >contiguous region, which is easy to locate and mmap over. > >Signed-off-by: Alexander Bulekov <alxndr@bu.edu> >Reviewed-by: Stefan Hajnoczi <stefanha@redhat.com> Reviewed-by: Darren Kenny <darren.kenny@oracle.com> >--- > tests/qtest/fuzz/Makefile.include | 5 +++ > tests/qtest/fuzz/fork_fuzz.c | 55 +++++++++++++++++++++++++++++++ > tests/qtest/fuzz/fork_fuzz.h | 23 +++++++++++++ > tests/qtest/fuzz/fork_fuzz.ld | 37 +++++++++++++++++++++ > 4 files changed, 120 insertions(+) > create mode 100644 tests/qtest/fuzz/fork_fuzz.c > create mode 100644 tests/qtest/fuzz/fork_fuzz.h > create mode 100644 tests/qtest/fuzz/fork_fuzz.ld > >diff --git a/tests/qtest/fuzz/Makefile.include b/tests/qtest/fuzz/Makefile.include >index 8632bb89f4..a90915d56d 100644 >--- a/tests/qtest/fuzz/Makefile.include >+++ b/tests/qtest/fuzz/Makefile.include >@@ -2,5 +2,10 @@ QEMU_PROG_FUZZ=qemu-fuzz-$(TARGET_NAME)$(EXESUF) > > fuzz-obj-y += tests/qtest/libqtest.o > fuzz-obj-y += tests/qtest/fuzz/fuzz.o # Fuzzer skeleton >+fuzz-obj-y += tests/qtest/fuzz/fork_fuzz.o > > FUZZ_CFLAGS += -I$(SRC_PATH)/tests -I$(SRC_PATH)/tests/qtest >+ >+# Linker Script to force coverage-counters into known regions which we can mark >+# shared >+FUZZ_LDFLAGS += -Xlinker -T$(SRC_PATH)/tests/qtest/fuzz/fork_fuzz.ld >diff --git a/tests/qtest/fuzz/fork_fuzz.c b/tests/qtest/fuzz/fork_fuzz.c >new file mode 100644 >index 0000000000..2bd0851903 >--- /dev/null >+++ b/tests/qtest/fuzz/fork_fuzz.c >@@ -0,0 +1,55 @@ >+/* >+ * Fork-based fuzzing helpers >+ * >+ * Copyright Red Hat Inc., 2019 >+ * >+ * Authors: >+ * Alexander Bulekov <alxndr@bu.edu> >+ * >+ * This work is licensed under the terms of the GNU GPL, version 2 or later. >+ * See the COPYING file in the top-level directory. >+ * >+ */ >+ >+#include "qemu/osdep.h" >+#include "fork_fuzz.h" >+ >+ >+void counter_shm_init(void) >+{ >+ char *shm_path = g_strdup_printf("/qemu-fuzz-cntrs.%d", getpid()); >+ int fd = shm_open(shm_path, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); >+ g_free(shm_path); >+ >+ if (fd == -1) { >+ perror("Error: "); >+ exit(1); >+ } >+ if (ftruncate(fd, &__FUZZ_COUNTERS_END - &__FUZZ_COUNTERS_START) == -1) { >+ perror("Error: "); >+ exit(1); >+ } >+ /* Copy what's in the counter region to the shm.. */ >+ void *rptr = mmap(NULL , >+ &__FUZZ_COUNTERS_END - &__FUZZ_COUNTERS_START, >+ PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); >+ memcpy(rptr, >+ &__FUZZ_COUNTERS_START, >+ &__FUZZ_COUNTERS_END - &__FUZZ_COUNTERS_START); >+ >+ munmap(rptr, &__FUZZ_COUNTERS_END - &__FUZZ_COUNTERS_START); >+ >+ /* And map the shm over the counter region */ >+ rptr = mmap(&__FUZZ_COUNTERS_START, >+ &__FUZZ_COUNTERS_END - &__FUZZ_COUNTERS_START, >+ PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, fd, 0); >+ >+ close(fd); >+ >+ if (!rptr) { >+ perror("Error: "); >+ exit(1); >+ } >+} >+ >+ >diff --git a/tests/qtest/fuzz/fork_fuzz.h b/tests/qtest/fuzz/fork_fuzz.h >new file mode 100644 >index 0000000000..9ecb8b58ef >--- /dev/null >+++ b/tests/qtest/fuzz/fork_fuzz.h >@@ -0,0 +1,23 @@ >+/* >+ * Fork-based fuzzing helpers >+ * >+ * Copyright Red Hat Inc., 2019 >+ * >+ * Authors: >+ * Alexander Bulekov <alxndr@bu.edu> >+ * >+ * This work is licensed under the terms of the GNU GPL, version 2 or later. >+ * See the COPYING file in the top-level directory. >+ * >+ */ >+ >+#ifndef FORK_FUZZ_H >+#define FORK_FUZZ_H >+ >+extern uint8_t __FUZZ_COUNTERS_START; >+extern uint8_t __FUZZ_COUNTERS_END; >+ >+void counter_shm_init(void); >+ >+#endif >+ >diff --git a/tests/qtest/fuzz/fork_fuzz.ld b/tests/qtest/fuzz/fork_fuzz.ld >new file mode 100644 >index 0000000000..b23a59f194 >--- /dev/null >+++ b/tests/qtest/fuzz/fork_fuzz.ld >@@ -0,0 +1,37 @@ >+/* We adjust linker script modification to place all of the stuff that needs to >+ * persist across fuzzing runs into a contiguous seciton of memory. Then, it is >+ * easy to re-map the counter-related memory as shared. >+*/ >+ >+SECTIONS >+{ >+ .data.fuzz_start : ALIGN(4K) >+ { >+ __FUZZ_COUNTERS_START = .; >+ __start___sancov_cntrs = .; >+ *(_*sancov_cntrs); >+ __stop___sancov_cntrs = .; >+ >+ /* Lowest stack counter */ >+ *(__sancov_lowest_stack); >+ } >+ .data.fuzz_ordered : >+ { >+ /* Coverage counters. They're not necessary for fuzzing, but are useful >+ * for analyzing the fuzzing performance >+ */ >+ __start___llvm_prf_cnts = .; >+ *(*llvm_prf_cnts); >+ __stop___llvm_prf_cnts = .; >+ >+ /* Internal Libfuzzer TracePC object which contains the ValueProfileMap */ >+ FuzzerTracePC*(.bss*); >+ } >+ .data.fuzz_end : ALIGN(4K) >+ { >+ __FUZZ_COUNTERS_END = .; >+ } >+} >+/* Dont overwrite the SECTIONS in the default linker script. Instead insert the >+ * above into the default script */ >+INSERT AFTER .data; >-- >2.25.0 >
diff --git a/tests/qtest/fuzz/Makefile.include b/tests/qtest/fuzz/Makefile.include index 8632bb89f4..a90915d56d 100644 --- a/tests/qtest/fuzz/Makefile.include +++ b/tests/qtest/fuzz/Makefile.include @@ -2,5 +2,10 @@ QEMU_PROG_FUZZ=qemu-fuzz-$(TARGET_NAME)$(EXESUF) fuzz-obj-y += tests/qtest/libqtest.o fuzz-obj-y += tests/qtest/fuzz/fuzz.o # Fuzzer skeleton +fuzz-obj-y += tests/qtest/fuzz/fork_fuzz.o FUZZ_CFLAGS += -I$(SRC_PATH)/tests -I$(SRC_PATH)/tests/qtest + +# Linker Script to force coverage-counters into known regions which we can mark +# shared +FUZZ_LDFLAGS += -Xlinker -T$(SRC_PATH)/tests/qtest/fuzz/fork_fuzz.ld diff --git a/tests/qtest/fuzz/fork_fuzz.c b/tests/qtest/fuzz/fork_fuzz.c new file mode 100644 index 0000000000..2bd0851903 --- /dev/null +++ b/tests/qtest/fuzz/fork_fuzz.c @@ -0,0 +1,55 @@ +/* + * Fork-based fuzzing helpers + * + * Copyright Red Hat Inc., 2019 + * + * Authors: + * Alexander Bulekov <alxndr@bu.edu> + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + */ + +#include "qemu/osdep.h" +#include "fork_fuzz.h" + + +void counter_shm_init(void) +{ + char *shm_path = g_strdup_printf("/qemu-fuzz-cntrs.%d", getpid()); + int fd = shm_open(shm_path, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR); + g_free(shm_path); + + if (fd == -1) { + perror("Error: "); + exit(1); + } + if (ftruncate(fd, &__FUZZ_COUNTERS_END - &__FUZZ_COUNTERS_START) == -1) { + perror("Error: "); + exit(1); + } + /* Copy what's in the counter region to the shm.. */ + void *rptr = mmap(NULL , + &__FUZZ_COUNTERS_END - &__FUZZ_COUNTERS_START, + PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); + memcpy(rptr, + &__FUZZ_COUNTERS_START, + &__FUZZ_COUNTERS_END - &__FUZZ_COUNTERS_START); + + munmap(rptr, &__FUZZ_COUNTERS_END - &__FUZZ_COUNTERS_START); + + /* And map the shm over the counter region */ + rptr = mmap(&__FUZZ_COUNTERS_START, + &__FUZZ_COUNTERS_END - &__FUZZ_COUNTERS_START, + PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, fd, 0); + + close(fd); + + if (!rptr) { + perror("Error: "); + exit(1); + } +} + + diff --git a/tests/qtest/fuzz/fork_fuzz.h b/tests/qtest/fuzz/fork_fuzz.h new file mode 100644 index 0000000000..9ecb8b58ef --- /dev/null +++ b/tests/qtest/fuzz/fork_fuzz.h @@ -0,0 +1,23 @@ +/* + * Fork-based fuzzing helpers + * + * Copyright Red Hat Inc., 2019 + * + * Authors: + * Alexander Bulekov <alxndr@bu.edu> + * + * This work is licensed under the terms of the GNU GPL, version 2 or later. + * See the COPYING file in the top-level directory. + * + */ + +#ifndef FORK_FUZZ_H +#define FORK_FUZZ_H + +extern uint8_t __FUZZ_COUNTERS_START; +extern uint8_t __FUZZ_COUNTERS_END; + +void counter_shm_init(void); + +#endif + diff --git a/tests/qtest/fuzz/fork_fuzz.ld b/tests/qtest/fuzz/fork_fuzz.ld new file mode 100644 index 0000000000..b23a59f194 --- /dev/null +++ b/tests/qtest/fuzz/fork_fuzz.ld @@ -0,0 +1,37 @@ +/* We adjust linker script modification to place all of the stuff that needs to + * persist across fuzzing runs into a contiguous seciton of memory. Then, it is + * easy to re-map the counter-related memory as shared. +*/ + +SECTIONS +{ + .data.fuzz_start : ALIGN(4K) + { + __FUZZ_COUNTERS_START = .; + __start___sancov_cntrs = .; + *(_*sancov_cntrs); + __stop___sancov_cntrs = .; + + /* Lowest stack counter */ + *(__sancov_lowest_stack); + } + .data.fuzz_ordered : + { + /* Coverage counters. They're not necessary for fuzzing, but are useful + * for analyzing the fuzzing performance + */ + __start___llvm_prf_cnts = .; + *(*llvm_prf_cnts); + __stop___llvm_prf_cnts = .; + + /* Internal Libfuzzer TracePC object which contains the ValueProfileMap */ + FuzzerTracePC*(.bss*); + } + .data.fuzz_end : ALIGN(4K) + { + __FUZZ_COUNTERS_END = .; + } +} +/* Dont overwrite the SECTIONS in the default linker script. Instead insert the + * above into the default script */ +INSERT AFTER .data;