From 66169c3c60af5014c1940de7491fdf090e5a090a Mon Sep 17 00:00:00 2001 From: Alexander Bulekov Date: Sat, 4 Feb 2023 23:29:42 -0500 Subject: [PATCH 01/10] hw/sparse-mem: clear memory on reset MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We use sparse-mem for fuzzing. For long-running fuzzing processes, we eventually end up with many allocated sparse-mem pages. To avoid this, clear the allocated pages on system-reset. Signed-off-by: Alexander Bulekov Reviewed-by: Darren Kenny Reviewed-by: Philippe Mathieu-Daudé --- hw/mem/sparse-mem.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/hw/mem/sparse-mem.c b/hw/mem/sparse-mem.c index e6640eb8e722..72f038d47daa 100644 --- a/hw/mem/sparse-mem.c +++ b/hw/mem/sparse-mem.c @@ -77,6 +77,13 @@ static void sparse_mem_write(void *opaque, hwaddr addr, uint64_t v, } +static void sparse_mem_enter_reset(Object *obj, ResetType type) +{ + SparseMemState *s = SPARSE_MEM(obj); + g_hash_table_remove_all(s->mapped); + return; +} + static const MemoryRegionOps sparse_mem_ops = { .read = sparse_mem_read, .write = sparse_mem_write, @@ -123,7 +130,8 @@ static void sparse_mem_realize(DeviceState *dev, Error **errp) assert(s->baseaddr + s->length > s->baseaddr); - s->mapped = g_hash_table_new(NULL, NULL); + s->mapped = g_hash_table_new_full(NULL, NULL, NULL, + (GDestroyNotify)g_free); memory_region_init_io(&s->mmio, OBJECT(s), &sparse_mem_ops, s, "sparse-mem", s->length); sysbus_init_mmio(sbd, &s->mmio); @@ -131,12 +139,15 @@ static void sparse_mem_realize(DeviceState *dev, Error **errp) static void sparse_mem_class_init(ObjectClass *klass, void *data) { + ResettableClass *rc = RESETTABLE_CLASS(klass); DeviceClass *dc = DEVICE_CLASS(klass); device_class_set_props(dc, sparse_mem_properties); dc->desc = "Sparse Memory Device"; dc->realize = sparse_mem_realize; + + rc->phases.enter = sparse_mem_enter_reset; } static const TypeInfo sparse_mem_types[] = { From 8d1e76b35b420a6ecf3f69730a7588279031d617 Mon Sep 17 00:00:00 2001 From: Alexander Bulekov Date: Sat, 4 Feb 2023 23:29:43 -0500 Subject: [PATCH 02/10] fuzz: add fuzz_reset API As we are converting most fuzzers to rely on reboots to reset state, introduce an API to make sure reboots are invoked in a consistent manner. Signed-off-by: Alexander Bulekov --- tests/qtest/fuzz/fuzz.c | 6 ++++++ tests/qtest/fuzz/fuzz.h | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/qtest/fuzz/fuzz.c b/tests/qtest/fuzz/fuzz.c index eb7520544b80..3bedb81b32b2 100644 --- a/tests/qtest/fuzz/fuzz.c +++ b/tests/qtest/fuzz/fuzz.c @@ -51,6 +51,12 @@ void flush_events(QTestState *s) } } +void fuzz_reset(QTestState *s) +{ + qemu_system_reset(SHUTDOWN_CAUSE_GUEST_RESET); + main_loop_wait(true); +} + static QTestState *qtest_setup(void) { qtest_server_set_send_handler(&qtest_client_inproc_recv, &fuzz_qts); diff --git a/tests/qtest/fuzz/fuzz.h b/tests/qtest/fuzz/fuzz.h index 327c1c5a55b2..21d1362d655e 100644 --- a/tests/qtest/fuzz/fuzz.h +++ b/tests/qtest/fuzz/fuzz.h @@ -103,7 +103,7 @@ typedef struct FuzzTarget { } FuzzTarget; void flush_events(QTestState *); -void reboot(QTestState *); +void fuzz_reset(QTestState *); /* Use the QTest ASCII protocol or call address_space API directly?*/ void fuzz_qtest_set_serialize(bool option); From 1375104370fc80bbcaa55430d2fbc0b1d8fc158b Mon Sep 17 00:00:00 2001 From: Alexander Bulekov Date: Sat, 4 Feb 2023 23:29:44 -0500 Subject: [PATCH 03/10] fuzz/generic-fuzz: use reboots instead of forks to reset state Signed-off-by: Alexander Bulekov Reviewed-by: Darren Kenny --- tests/qtest/fuzz/generic_fuzz.c | 114 ++++++-------------------------- 1 file changed, 22 insertions(+), 92 deletions(-) diff --git a/tests/qtest/fuzz/generic_fuzz.c b/tests/qtest/fuzz/generic_fuzz.c index 7326f6840b4f..f4acfa45cccf 100644 --- a/tests/qtest/fuzz/generic_fuzz.c +++ b/tests/qtest/fuzz/generic_fuzz.c @@ -18,7 +18,6 @@ #include "tests/qtest/libqtest.h" #include "tests/qtest/libqos/pci-pc.h" #include "fuzz.h" -#include "fork_fuzz.h" #include "string.h" #include "exec/memory.h" #include "exec/ramblock.h" @@ -29,6 +28,8 @@ #include "generic_fuzz_configs.h" #include "hw/mem/sparse-mem.h" +static void pci_enum(gpointer pcidev, gpointer bus); + /* * SEPARATOR is used to separate "operations" in the fuzz input */ @@ -47,7 +48,6 @@ enum cmds { OP_CLOCK_STEP, }; -#define DEFAULT_TIMEOUT_US 100000 #define USEC_IN_SEC 1000000000 #define MAX_DMA_FILL_SIZE 0x10000 @@ -60,8 +60,6 @@ typedef struct { ram_addr_t size; /* The number of bytes until the end of the I/O region */ } address_range; -static useconds_t timeout = DEFAULT_TIMEOUT_US; - static bool qtest_log_enabled; MemoryRegion *sparse_mem_mr; @@ -589,30 +587,6 @@ static void op_disable_pci(QTestState *s, const unsigned char *data, size_t len) pci_disabled = true; } -static void handle_timeout(int sig) -{ - if (qtest_log_enabled) { - fprintf(stderr, "[Timeout]\n"); - fflush(stderr); - } - - /* - * If there is a crash, libfuzzer/ASAN forks a child to run an - * "llvm-symbolizer" process for printing out a pretty stacktrace. It - * communicates with this child using a pipe. If we timeout+Exit, while - * libfuzzer is still communicating with the llvm-symbolizer child, we will - * be left with an orphan llvm-symbolizer process. Sometimes, this appears - * to lead to a deadlock in the forkserver. Use waitpid to check if there - * are any waitable children. If so, exit out of the signal-handler, and - * let libfuzzer finish communicating with the child, and exit, on its own. - */ - if (waitpid(-1, NULL, WNOHANG) == 0) { - return; - } - - _Exit(0); -} - /* * Here, we interpret random bytes from the fuzzer, as a sequence of commands. * Some commands can be variable-width, so we use a separator, SEPARATOR, to @@ -669,64 +643,32 @@ static void generic_fuzz(QTestState *s, const unsigned char *Data, size_t Size) size_t cmd_len; uint8_t op; - if (fork() == 0) { - struct sigaction sact; - struct itimerval timer; - sigset_t set; - /* - * Sometimes the fuzzer will find inputs that take quite a long time to - * process. Often times, these inputs do not result in new coverage. - * Even if these inputs might be interesting, they can slow down the - * fuzzer, overall. Set a timeout for each command to avoid hurting - * performance, too much - */ - if (timeout) { - - sigemptyset(&sact.sa_mask); - sact.sa_flags = SA_NODEFER; - sact.sa_handler = handle_timeout; - sigaction(SIGALRM, &sact, NULL); + op_clear_dma_patterns(s, NULL, 0); + pci_disabled = false; - sigemptyset(&set); - sigaddset(&set, SIGALRM); - pthread_sigmask(SIG_UNBLOCK, &set, NULL); - - memset(&timer, 0, sizeof(timer)); - timer.it_value.tv_sec = timeout / USEC_IN_SEC; - timer.it_value.tv_usec = timeout % USEC_IN_SEC; - } - - op_clear_dma_patterns(s, NULL, 0); - pci_disabled = false; - - while (cmd && Size) { - /* Reset the timeout, each time we run a new command */ - if (timeout) { - setitimer(ITIMER_REAL, &timer, NULL); - } + QPCIBus *pcibus = qpci_new_pc(s, NULL); + g_ptr_array_foreach(fuzzable_pci_devices, pci_enum, pcibus); + qpci_free_pc(pcibus); - /* Get the length until the next command or end of input */ - nextcmd = memmem(cmd, Size, SEPARATOR, strlen(SEPARATOR)); - cmd_len = nextcmd ? nextcmd - cmd : Size; + while (cmd && Size) { + /* Get the length until the next command or end of input */ + nextcmd = memmem(cmd, Size, SEPARATOR, strlen(SEPARATOR)); + cmd_len = nextcmd ? nextcmd - cmd : Size; - if (cmd_len > 0) { - /* Interpret the first byte of the command as an opcode */ - op = *cmd % (sizeof(ops) / sizeof((ops)[0])); - ops[op](s, cmd + 1, cmd_len - 1); + if (cmd_len > 0) { + /* Interpret the first byte of the command as an opcode */ + op = *cmd % (sizeof(ops) / sizeof((ops)[0])); + ops[op](s, cmd + 1, cmd_len - 1); - /* Run the main loop */ - flush_events(s); - } - /* Advance to the next command */ - cmd = nextcmd ? nextcmd + sizeof(SEPARATOR) - 1 : nextcmd; - Size = Size - (cmd_len + sizeof(SEPARATOR) - 1); - g_array_set_size(dma_regions, 0); + /* Run the main loop */ + flush_events(s); } - _Exit(0); - } else { - flush_events(s); - wait(0); + /* Advance to the next command */ + cmd = nextcmd ? nextcmd + sizeof(SEPARATOR) - 1 : nextcmd; + Size = Size - (cmd_len + sizeof(SEPARATOR) - 1); + g_array_set_size(dma_regions, 0); } + fuzz_reset(s); } static void usage(void) @@ -738,8 +680,6 @@ static void usage(void) printf("Optionally: QEMU_AVOID_DOUBLE_FETCH= " "Try to avoid racy DMA double fetch bugs? %d by default\n", avoid_double_fetches); - printf("Optionally: QEMU_FUZZ_TIMEOUT= Specify a custom timeout (us). " - "0 to disable. %d by default\n", timeout); exit(0); } @@ -825,7 +765,6 @@ static void generic_pre_fuzz(QTestState *s) { GHashTableIter iter; MemoryRegion *mr; - QPCIBus *pcibus; char **result; GString *name_pattern; @@ -838,9 +777,6 @@ static void generic_pre_fuzz(QTestState *s) if (getenv("QEMU_AVOID_DOUBLE_FETCH")) { avoid_double_fetches = 1; } - if (getenv("QEMU_FUZZ_TIMEOUT")) { - timeout = g_ascii_strtoll(getenv("QEMU_FUZZ_TIMEOUT"), NULL, 0); - } qts_global = s; /* @@ -883,12 +819,6 @@ static void generic_pre_fuzz(QTestState *s) printf("No fuzzable memory regions found...\n"); exit(1); } - - pcibus = qpci_new_pc(s, NULL); - g_ptr_array_foreach(fuzzable_pci_devices, pci_enum, pcibus); - qpci_free_pc(pcibus); - - counter_shm_init(); } /* From b8b52178e2d84bfcda91b00d55fa05ed895badbf Mon Sep 17 00:00:00 2001 From: Alexander Bulekov Date: Sat, 4 Feb 2023 23:29:45 -0500 Subject: [PATCH 04/10] fuzz/generic-fuzz: add a limit on DMA bytes written MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As we have repplaced fork-based fuzzing, with reboots - we can no longer use a timeout+exit() to avoid slow inputs. Libfuzzer has its own timer that it uses to catch slow inputs, however these timeouts are usually seconds-minutes long: more than enough to bog-down the fuzzing process. However, I found that slow inputs often attempt to fill overly large DMA requests. Thus, we can mitigate most timeouts by setting a cap on the total number of DMA bytes written by an input. Signed-off-by: Alexander Bulekov Reviewed-by: Philippe Mathieu-Daudé Reviewed-by: Darren Kenny --- tests/qtest/fuzz/generic_fuzz.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/qtest/fuzz/generic_fuzz.c b/tests/qtest/fuzz/generic_fuzz.c index f4acfa45cccf..c525d22951e3 100644 --- a/tests/qtest/fuzz/generic_fuzz.c +++ b/tests/qtest/fuzz/generic_fuzz.c @@ -51,6 +51,7 @@ enum cmds { #define USEC_IN_SEC 1000000000 #define MAX_DMA_FILL_SIZE 0x10000 +#define MAX_TOTAL_DMA_SIZE 0x10000000 #define PCI_HOST_BRIDGE_CFG 0xcf8 #define PCI_HOST_BRIDGE_DATA 0xcfc @@ -61,6 +62,7 @@ typedef struct { } address_range; static bool qtest_log_enabled; +size_t dma_bytes_written; MemoryRegion *sparse_mem_mr; @@ -194,6 +196,7 @@ void fuzz_dma_read_cb(size_t addr, size_t len, MemoryRegion *mr) */ if (dma_patterns->len == 0 || len == 0 + || dma_bytes_written + len > MAX_TOTAL_DMA_SIZE || (mr != current_machine->ram && mr != sparse_mem_mr)) { return; } @@ -266,6 +269,7 @@ void fuzz_dma_read_cb(size_t addr, size_t len, MemoryRegion *mr) fflush(stderr); } qtest_memwrite(qts_global, addr, buf, l); + dma_bytes_written += l; } len -= l; buf += l; @@ -645,6 +649,7 @@ static void generic_fuzz(QTestState *s, const unsigned char *Data, size_t Size) op_clear_dma_patterns(s, NULL, 0); pci_disabled = false; + dma_bytes_written = 0; QPCIBus *pcibus = qpci_new_pc(s, NULL); g_ptr_array_foreach(fuzzable_pci_devices, pci_enum, pcibus); From 5d3c73e27e7e0ab09e4796a6218cb5762632c4e2 Mon Sep 17 00:00:00 2001 From: Alexander Bulekov Date: Sat, 4 Feb 2023 23:29:46 -0500 Subject: [PATCH 05/10] fuzz/virtio-scsi: remove fork-based fuzzer Signed-off-by: Alexander Bulekov Reviewed-by: Darren Kenny --- tests/qtest/fuzz/virtio_scsi_fuzz.c | 51 ++++------------------------- 1 file changed, 7 insertions(+), 44 deletions(-) diff --git a/tests/qtest/fuzz/virtio_scsi_fuzz.c b/tests/qtest/fuzz/virtio_scsi_fuzz.c index b3220ef6cb20..b6268efd5919 100644 --- a/tests/qtest/fuzz/virtio_scsi_fuzz.c +++ b/tests/qtest/fuzz/virtio_scsi_fuzz.c @@ -20,7 +20,6 @@ #include "standard-headers/linux/virtio_pci.h" #include "standard-headers/linux/virtio_scsi.h" #include "fuzz.h" -#include "fork_fuzz.h" #include "qos_fuzz.h" #define PCI_SLOT 0x02 @@ -132,48 +131,24 @@ static void virtio_scsi_fuzz(QTestState *s, QVirtioSCSIQueues* queues, } } -static void virtio_scsi_fork_fuzz(QTestState *s, - const unsigned char *Data, size_t Size) -{ - QVirtioSCSI *scsi = fuzz_qos_obj; - static QVirtioSCSIQueues *queues; - if (!queues) { - queues = qvirtio_scsi_init(scsi->vdev, 0); - } - if (fork() == 0) { - virtio_scsi_fuzz(s, queues, Data, Size); - flush_events(s); - _Exit(0); - } else { - flush_events(s); - wait(NULL); - } -} - static void virtio_scsi_with_flag_fuzz(QTestState *s, const unsigned char *Data, size_t Size) { QVirtioSCSI *scsi = fuzz_qos_obj; static QVirtioSCSIQueues *queues; - if (fork() == 0) { - if (Size >= sizeof(uint64_t)) { - queues = qvirtio_scsi_init(scsi->vdev, *(uint64_t *)Data); - virtio_scsi_fuzz(s, queues, - Data + sizeof(uint64_t), Size - sizeof(uint64_t)); - flush_events(s); - } - _Exit(0); - } else { + if (Size >= sizeof(uint64_t)) { + queues = qvirtio_scsi_init(scsi->vdev, *(uint64_t *)Data); + virtio_scsi_fuzz(s, queues, + Data + sizeof(uint64_t), Size - sizeof(uint64_t)); flush_events(s); - wait(NULL); } + fuzz_reset(s); } static void virtio_scsi_pre_fuzz(QTestState *s) { qos_init_path(s); - counter_shm_init(); } static void *virtio_scsi_test_setup(GString *cmd_line, void *arg) @@ -189,22 +164,10 @@ static void *virtio_scsi_test_setup(GString *cmd_line, void *arg) static void register_virtio_scsi_fuzz_targets(void) { - fuzz_add_qos_target(&(FuzzTarget){ - .name = "virtio-scsi-fuzz", - .description = "Fuzz the virtio-scsi virtual queues, forking " - "for each fuzz run", - .pre_vm_init = &counter_shm_init, - .pre_fuzz = &virtio_scsi_pre_fuzz, - .fuzz = virtio_scsi_fork_fuzz,}, - "virtio-scsi", - &(QOSGraphTestOptions){.before = virtio_scsi_test_setup} - ); - fuzz_add_qos_target(&(FuzzTarget){ .name = "virtio-scsi-flags-fuzz", - .description = "Fuzz the virtio-scsi virtual queues, forking " - "for each fuzz run (also fuzzes the virtio flags)", - .pre_vm_init = &counter_shm_init, + .description = "Fuzz the virtio-scsi virtual queues. " + "Also fuzzes the virtio flags", .pre_fuzz = &virtio_scsi_pre_fuzz, .fuzz = virtio_scsi_with_flag_fuzz,}, "virtio-scsi", From 5f47d07fd80cc2b500eb2df5b15130feb50d6338 Mon Sep 17 00:00:00 2001 From: Alexander Bulekov Date: Sat, 4 Feb 2023 23:29:47 -0500 Subject: [PATCH 06/10] fuzz/virtio-net: remove fork-based fuzzer Signed-off-by: Alexander Bulekov Reviewed-by: Darren Kenny --- tests/qtest/fuzz/virtio_net_fuzz.c | 54 +++--------------------------- 1 file changed, 5 insertions(+), 49 deletions(-) diff --git a/tests/qtest/fuzz/virtio_net_fuzz.c b/tests/qtest/fuzz/virtio_net_fuzz.c index c2c15f07f062..e239875e3b42 100644 --- a/tests/qtest/fuzz/virtio_net_fuzz.c +++ b/tests/qtest/fuzz/virtio_net_fuzz.c @@ -16,7 +16,6 @@ #include "tests/qtest/libqtest.h" #include "tests/qtest/libqos/virtio-net.h" #include "fuzz.h" -#include "fork_fuzz.h" #include "qos_fuzz.h" @@ -115,36 +114,18 @@ static void virtio_net_fuzz_multi(QTestState *s, } } -static void virtio_net_fork_fuzz(QTestState *s, - const unsigned char *Data, size_t Size) -{ - if (fork() == 0) { - virtio_net_fuzz_multi(s, Data, Size, false); - flush_events(s); - _Exit(0); - } else { - flush_events(s); - wait(NULL); - } -} -static void virtio_net_fork_fuzz_check_used(QTestState *s, +static void virtio_net_fuzz_check_used(QTestState *s, const unsigned char *Data, size_t Size) { - if (fork() == 0) { - virtio_net_fuzz_multi(s, Data, Size, true); - flush_events(s); - _Exit(0); - } else { - flush_events(s); - wait(NULL); - } + virtio_net_fuzz_multi(s, Data, Size, true); + flush_events(s); + fuzz_reset(s); } static void virtio_net_pre_fuzz(QTestState *s) { qos_init_path(s); - counter_shm_init(); } static void *virtio_net_test_setup_socket(GString *cmd_line, void *arg) @@ -158,23 +139,8 @@ static void *virtio_net_test_setup_socket(GString *cmd_line, void *arg) return arg; } -static void *virtio_net_test_setup_user(GString *cmd_line, void *arg) -{ - g_string_append_printf(cmd_line, " -netdev user,id=hs0 "); - return arg; -} - static void register_virtio_net_fuzz_targets(void) { - fuzz_add_qos_target(&(FuzzTarget){ - .name = "virtio-net-socket", - .description = "Fuzz the virtio-net virtual queues. Fuzz incoming " - "traffic using the socket backend", - .pre_fuzz = &virtio_net_pre_fuzz, - .fuzz = virtio_net_fork_fuzz,}, - "virtio-net", - &(QOSGraphTestOptions){.before = virtio_net_test_setup_socket} - ); fuzz_add_qos_target(&(FuzzTarget){ .name = "virtio-net-socket-check-used", @@ -182,20 +148,10 @@ static void register_virtio_net_fuzz_targets(void) "descriptors to be used. Timeout may indicate improperly handled " "input", .pre_fuzz = &virtio_net_pre_fuzz, - .fuzz = virtio_net_fork_fuzz_check_used,}, + .fuzz = virtio_net_fuzz_check_used,}, "virtio-net", &(QOSGraphTestOptions){.before = virtio_net_test_setup_socket} ); - fuzz_add_qos_target(&(FuzzTarget){ - .name = "virtio-net-slirp", - .description = "Fuzz the virtio-net virtual queues with the slirp " - " backend. Warning: May result in network traffic emitted from the " - " process. Run in an isolated network environment.", - .pre_fuzz = &virtio_net_pre_fuzz, - .fuzz = virtio_net_fork_fuzz,}, - "virtio-net", - &(QOSGraphTestOptions){.before = virtio_net_test_setup_user} - ); } fuzz_target_init(register_virtio_net_fuzz_targets); From 725767e9a1fd4c39628f9ad10cb7aa0fe98a04cc Mon Sep 17 00:00:00 2001 From: Alexander Bulekov Date: Sat, 4 Feb 2023 23:29:48 -0500 Subject: [PATCH 07/10] fuzz/virtio-blk: remove fork-based fuzzer Signed-off-by: Alexander Bulekov Reviewed-by: Darren Kenny --- tests/qtest/fuzz/virtio_blk_fuzz.c | 51 ++++-------------------------- 1 file changed, 7 insertions(+), 44 deletions(-) diff --git a/tests/qtest/fuzz/virtio_blk_fuzz.c b/tests/qtest/fuzz/virtio_blk_fuzz.c index a9fb9ecf6c1a..651fd4f04350 100644 --- a/tests/qtest/fuzz/virtio_blk_fuzz.c +++ b/tests/qtest/fuzz/virtio_blk_fuzz.c @@ -19,7 +19,6 @@ #include "standard-headers/linux/virtio_pci.h" #include "standard-headers/linux/virtio_blk.h" #include "fuzz.h" -#include "fork_fuzz.h" #include "qos_fuzz.h" #define TEST_IMAGE_SIZE (64 * 1024 * 1024) @@ -128,48 +127,24 @@ static void virtio_blk_fuzz(QTestState *s, QVirtioBlkQueues* queues, } } -static void virtio_blk_fork_fuzz(QTestState *s, - const unsigned char *Data, size_t Size) -{ - QVirtioBlk *blk = fuzz_qos_obj; - static QVirtioBlkQueues *queues; - if (!queues) { - queues = qvirtio_blk_init(blk->vdev, 0); - } - if (fork() == 0) { - virtio_blk_fuzz(s, queues, Data, Size); - flush_events(s); - _Exit(0); - } else { - flush_events(s); - wait(NULL); - } -} - static void virtio_blk_with_flag_fuzz(QTestState *s, const unsigned char *Data, size_t Size) { QVirtioBlk *blk = fuzz_qos_obj; static QVirtioBlkQueues *queues; - if (fork() == 0) { - if (Size >= sizeof(uint64_t)) { - queues = qvirtio_blk_init(blk->vdev, *(uint64_t *)Data); - virtio_blk_fuzz(s, queues, - Data + sizeof(uint64_t), Size - sizeof(uint64_t)); - flush_events(s); - } - _Exit(0); - } else { + if (Size >= sizeof(uint64_t)) { + queues = qvirtio_blk_init(blk->vdev, *(uint64_t *)Data); + virtio_blk_fuzz(s, queues, + Data + sizeof(uint64_t), Size - sizeof(uint64_t)); flush_events(s); - wait(NULL); } + fuzz_reset(s); } static void virtio_blk_pre_fuzz(QTestState *s) { qos_init_path(s); - counter_shm_init(); } static void drive_destroy(void *path) @@ -208,22 +183,10 @@ static void *virtio_blk_test_setup(GString *cmd_line, void *arg) static void register_virtio_blk_fuzz_targets(void) { - fuzz_add_qos_target(&(FuzzTarget){ - .name = "virtio-blk-fuzz", - .description = "Fuzz the virtio-blk virtual queues, forking " - "for each fuzz run", - .pre_vm_init = &counter_shm_init, - .pre_fuzz = &virtio_blk_pre_fuzz, - .fuzz = virtio_blk_fork_fuzz,}, - "virtio-blk", - &(QOSGraphTestOptions){.before = virtio_blk_test_setup} - ); - fuzz_add_qos_target(&(FuzzTarget){ .name = "virtio-blk-flags-fuzz", - .description = "Fuzz the virtio-blk virtual queues, forking " - "for each fuzz run (also fuzzes the virtio flags)", - .pre_vm_init = &counter_shm_init, + .description = "Fuzz the virtio-blk virtual queues. " + "Also fuzzes the virtio flags)", .pre_fuzz = &virtio_blk_pre_fuzz, .fuzz = virtio_blk_with_flag_fuzz,}, "virtio-blk", From f031c95941e3dbc816416d5336ed6225a4933cfc Mon Sep 17 00:00:00 2001 From: Alexander Bulekov Date: Sat, 4 Feb 2023 23:29:49 -0500 Subject: [PATCH 08/10] fuzz/i440fx: remove fork-based fuzzer Signed-off-by: Alexander Bulekov Reviewed-by: Darren Kenny --- tests/qtest/fuzz/i440fx_fuzz.c | 27 +-------------------------- 1 file changed, 1 insertion(+), 26 deletions(-) diff --git a/tests/qtest/fuzz/i440fx_fuzz.c b/tests/qtest/fuzz/i440fx_fuzz.c index b17fc725dfd6..155fe018f80b 100644 --- a/tests/qtest/fuzz/i440fx_fuzz.c +++ b/tests/qtest/fuzz/i440fx_fuzz.c @@ -18,7 +18,6 @@ #include "tests/qtest/libqos/pci-pc.h" #include "fuzz.h" #include "qos_fuzz.h" -#include "fork_fuzz.h" #define I440FX_PCI_HOST_BRIDGE_CFG 0xcf8 @@ -89,6 +88,7 @@ static void i440fx_fuzz_qtest(QTestState *s, size_t Size) { ioport_fuzz_qtest(s, Data, Size); + fuzz_reset(s); } static void pciconfig_fuzz_qos(QTestState *s, QPCIBus *bus, @@ -145,17 +145,6 @@ static void i440fx_fuzz_qos(QTestState *s, pciconfig_fuzz_qos(s, bus, Data, Size); } -static void i440fx_fuzz_qos_fork(QTestState *s, - const unsigned char *Data, size_t Size) { - if (fork() == 0) { - i440fx_fuzz_qos(s, Data, Size); - _Exit(0); - } else { - flush_events(s); - wait(NULL); - } -} - static const char *i440fx_qtest_argv = TARGET_NAME " -machine accel=qtest" " -m 0 -display none"; static GString *i440fx_argv(FuzzTarget *t) @@ -163,10 +152,6 @@ static GString *i440fx_argv(FuzzTarget *t) return g_string_new(i440fx_qtest_argv); } -static void fork_init(void) -{ - counter_shm_init(); -} static void register_pci_fuzz_targets(void) { @@ -178,16 +163,6 @@ static void register_pci_fuzz_targets(void) .get_init_cmdline = i440fx_argv, .fuzz = i440fx_fuzz_qtest}); - /* Uses libqos and forks to prevent state leakage */ - fuzz_add_qos_target(&(FuzzTarget){ - .name = "i440fx-qos-fork-fuzz", - .description = "Fuzz the i440fx using raw qtest commands and " - "rebooting after each run", - .pre_vm_init = &fork_init, - .fuzz = i440fx_fuzz_qos_fork,}, - "i440FX-pcihost", - &(QOSGraphTestOptions){} - ); /* * Uses libqos. Doesn't do anything to reset state. Note that if we were to From d2e6f9272d337d1b23b588e7ead8500d40cbf4e9 Mon Sep 17 00:00:00 2001 From: Alexander Bulekov Date: Sat, 4 Feb 2023 23:29:50 -0500 Subject: [PATCH 09/10] fuzz: remove fork-fuzzing scaffolding Fork-fuzzing provides a few pros, but our implementation prevents us from using fuzzers other than libFuzzer, and may be causing issues such as coverage-failure builds on OSS-Fuzz. It is not a great long-term solution as it depends on internal implementation details of libFuzzer (which is no longer in active development). Remove it in favor of other methods of resetting state between inputs. Signed-off-by: Alexander Bulekov Reviewed-by: Darren Kenny --- meson.build | 4 --- tests/qtest/fuzz/fork_fuzz.c | 41 ------------------------- tests/qtest/fuzz/fork_fuzz.h | 23 -------------- tests/qtest/fuzz/fork_fuzz.ld | 56 ----------------------------------- tests/qtest/fuzz/meson.build | 6 ++-- 5 files changed, 3 insertions(+), 127 deletions(-) delete mode 100644 tests/qtest/fuzz/fork_fuzz.c delete mode 100644 tests/qtest/fuzz/fork_fuzz.h delete mode 100644 tests/qtest/fuzz/fork_fuzz.ld diff --git a/meson.build b/meson.build index a76c85531250..b6f92bba3523 100644 --- a/meson.build +++ b/meson.build @@ -215,10 +215,6 @@ endif # Specify linker-script with add_project_link_arguments so that it is not placed # within a linker --start-group/--end-group pair if get_option('fuzzing') - add_project_link_arguments(['-Wl,-T,', - (meson.current_source_dir() / 'tests/qtest/fuzz/fork_fuzz.ld')], - native: false, language: all_languages) - # Specify a filter to only instrument code that is directly related to # virtual-devices. configure_file(output: 'instrumentation-filter', diff --git a/tests/qtest/fuzz/fork_fuzz.c b/tests/qtest/fuzz/fork_fuzz.c deleted file mode 100644 index 6ffb2a79372d..000000000000 --- a/tests/qtest/fuzz/fork_fuzz.c +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Fork-based fuzzing helpers - * - * Copyright Red Hat Inc., 2019 - * - * Authors: - * Alexander Bulekov - * - * 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) -{ - /* Copy what's in the counter region to a temporary buffer.. */ - void *copy = malloc(&__FUZZ_COUNTERS_END - &__FUZZ_COUNTERS_START); - memcpy(copy, - &__FUZZ_COUNTERS_START, - &__FUZZ_COUNTERS_END - &__FUZZ_COUNTERS_START); - - /* Map a shared region over the counter region */ - if (mmap(&__FUZZ_COUNTERS_START, - &__FUZZ_COUNTERS_END - &__FUZZ_COUNTERS_START, - PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED | MAP_ANONYMOUS, - 0, 0) == MAP_FAILED) { - perror("Error: "); - exit(1); - } - - /* Copy the original data back to the counter-region */ - memcpy(&__FUZZ_COUNTERS_START, copy, - &__FUZZ_COUNTERS_END - &__FUZZ_COUNTERS_START); - free(copy); -} - - diff --git a/tests/qtest/fuzz/fork_fuzz.h b/tests/qtest/fuzz/fork_fuzz.h deleted file mode 100644 index 9ecb8b58ef86..000000000000 --- a/tests/qtest/fuzz/fork_fuzz.h +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Fork-based fuzzing helpers - * - * Copyright Red Hat Inc., 2019 - * - * Authors: - * Alexander Bulekov - * - * 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 deleted file mode 100644 index cfb88b7fdb51..000000000000 --- a/tests/qtest/fuzz/fork_fuzz.ld +++ /dev/null @@ -1,56 +0,0 @@ -/* - * We adjust linker script modification to place all of the stuff that needs to - * persist across fuzzing runs into a contiguous section 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); - } -} -INSERT AFTER .data; - -SECTIONS -{ - .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*); - /* - * In case the above line fails, explicitly specify the (mangled) name of - * the object we care about - */ - *(.bss._ZN6fuzzer3TPCE); - } -} -INSERT AFTER .data.fuzz_start; - -SECTIONS -{ - .data.fuzz_end : ALIGN(4K) - { - __FUZZ_COUNTERS_END = .; - } -} -/* - * Don't overwrite the SECTIONS in the default linker script. Instead insert the - * above into the default script - */ -INSERT AFTER .data.fuzz_ordered; diff --git a/tests/qtest/fuzz/meson.build b/tests/qtest/fuzz/meson.build index 189901d4a26f..4d10b47b8f90 100644 --- a/tests/qtest/fuzz/meson.build +++ b/tests/qtest/fuzz/meson.build @@ -2,7 +2,7 @@ if not get_option('fuzzing') subdir_done() endif -specific_fuzz_ss.add(files('fuzz.c', 'fork_fuzz.c', 'qos_fuzz.c', +specific_fuzz_ss.add(files('fuzz.c', 'qos_fuzz.c', 'qtest_wrappers.c'), qos) # Targets @@ -12,7 +12,7 @@ specific_fuzz_ss.add(when: 'CONFIG_VIRTIO_SCSI', if_true: files('virtio_scsi_fuz specific_fuzz_ss.add(when: 'CONFIG_VIRTIO_BLK', if_true: files('virtio_blk_fuzz.c')) specific_fuzz_ss.add(files('generic_fuzz.c')) -fork_fuzz = declare_dependency( +fuzz_ld = declare_dependency( link_args: fuzz_exe_ldflags + ['-Wl,-wrap,qtest_inb', '-Wl,-wrap,qtest_inw', @@ -35,4 +35,4 @@ fork_fuzz = declare_dependency( '-Wl,-wrap,qtest_memset'] ) -specific_fuzz_ss.add(fork_fuzz) +specific_fuzz_ss.add(fuzz_ld) From 7d9e5f18a94792ed875a1caed2bfcd1e68a49481 Mon Sep 17 00:00:00 2001 From: Alexander Bulekov Date: Sat, 4 Feb 2023 23:29:51 -0500 Subject: [PATCH 10/10] docs/fuzz: remove mentions of fork-based fuzzing Signed-off-by: Alexander Bulekov Reviewed-by: Darren Kenny --- docs/devel/fuzzing.rst | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/docs/devel/fuzzing.rst b/docs/devel/fuzzing.rst index 715330c85613..3bfcb33fc4b5 100644 --- a/docs/devel/fuzzing.rst +++ b/docs/devel/fuzzing.rst @@ -19,11 +19,6 @@ responsibility to ensure that state is reset between fuzzing-runs. Building the fuzzers -------------------- -*NOTE*: If possible, build a 32-bit binary. When forking, the 32-bit fuzzer is -much faster, since the page-map has a smaller size. This is due to the fact that -AddressSanitizer maps ~20TB of memory, as part of its detection. This results -in a large page-map, and a much slower ``fork()``. - To build the fuzzers, install a recent version of clang: Configure with (substitute the clang binaries with the version you installed). Here, enable-sanitizers, is optional but it allows us to reliably detect bugs @@ -296,10 +291,9 @@ input. It is also responsible for manually calling ``main_loop_wait`` to ensure that bottom halves are executed and any cleanup required before the next input. Since the same process is reused for many fuzzing runs, QEMU state needs to -be reset at the end of each run. There are currently two implemented -options for resetting state: +be reset at the end of each run. For example, this can be done by rebooting the +VM, after each run. -- Reboot the guest between runs. - *Pros*: Straightforward and fast for simple fuzz targets. - *Cons*: Depending on the device, does not reset all device state. If the @@ -308,15 +302,3 @@ options for resetting state: reboot. - *Example target*: ``i440fx-qtest-reboot-fuzz`` - -- Run each test case in a separate forked process and copy the coverage - information back to the parent. This is fairly similar to AFL's "deferred" - fork-server mode [3] - - - *Pros*: Relatively fast. Devices only need to be initialized once. No need to - do slow reboots or vmloads. - - - *Cons*: Not officially supported by libfuzzer. Does not work well for - devices that rely on dedicated threads. - - - *Example target*: ``virtio-net-fork-fuzz``