Message ID | 20191030144926.11873-20-alxndr@bu.edu (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | Add virtual device fuzzing support | expand |
On Wed, Oct 30, 2019 at 02:50:03PM +0000, Oleinik, Alexander wrote: > +static void virtio_net_fuzz_multi(QTestState *s, > + const unsigned char *Data, size_t Size) > +{ > + typedef struct vq_action { > + uint8_t queue; > + uint8_t length; > + uint8_t write; > + uint8_t next; > + bool kick; > + } vq_action; > + > + uint64_t req_addr[10]; > + int reqi = 0; > + uint32_t free_head = 0; > + > + QGuestAllocator *t_alloc = fuzz_qos_alloc; > + > + QVirtioNet *net_if = fuzz_qos_obj; > + QVirtioDevice *dev = net_if->vdev; > + QVirtQueue *q; > + vq_action vqa; > + int iters = 0; > + while (true) { > + if (Size < sizeof(vqa)) { > + break; > + } More concisely: while (Size < sizeof(vqa)) { > + memcpy(&vqa, Data, sizeof(vqa)); > + vqa = *((vq_action *)Data); The assignment is redundant after the memcpy. > + Data += sizeof(vqa); > + Size -= sizeof(vqa); > + > + q = net_if->queues[vqa.queue % 3]; > + > + vqa.length = vqa.length >= Size ? Size : vqa.length; > + > + req_addr[reqi] = guest_alloc(t_alloc, vqa.length); > + qtest_memwrite(s, req_addr[reqi], Data, vqa.length); > + if (iters == 0) { What is the difference between iters and reqi? The values of these variables are always identical so I think only one of them is needed. > + free_head = qvirtqueue_add(s, q, req_addr[reqi], vqa.length, > + vqa.write, vqa.next); > + } else { > + qvirtqueue_add(s, q, > + req_addr[reqi], vqa.length, vqa.write , vqa.next); > + } > + iters++; > + reqi++; > + if (iters == 10) { > + break; > + } > + Data += vqa.length; > + Size -= vqa.length; > + } > + if (iters) { > + qvirtqueue_kick(s, dev, q, free_head); > + qtest_clock_step_next(s); Here we could run the main loop until free_head appears in the used ring. That should allow the request processing code path to be explored more fully, but this it's okay to leave it like this for now.
On 2019/10/30 下午10:50, Oleinik, Alexander wrote: > From: Alexander Oleinik <alxndr@bu.edu> > > The virtio-net fuzz target feeds inputs to all three virtio-net > virtqueues, and uses forking to avoid leaking state between fuzz runs. > > Signed-off-by: Alexander Oleinik <alxndr@bu.edu> Can this fuzz vhost-net or vhost-user (I only see socket backend)? If it's not too hard, it would be even more interesting. Thanks > --- > tests/fuzz/Makefile.include | 1 + > tests/fuzz/virtio_net_fuzz.c | 123 +++++++++++++++++++++++++++++++++++ > 2 files changed, 124 insertions(+) > create mode 100644 tests/fuzz/virtio_net_fuzz.c > > diff --git a/tests/fuzz/Makefile.include b/tests/fuzz/Makefile.include > index 37d6821bee..f1d9b46b1c 100644 > --- a/tests/fuzz/Makefile.include > +++ b/tests/fuzz/Makefile.include > @@ -6,5 +6,6 @@ fuzz-obj-y += tests/fuzz/fork_fuzz.o > fuzz-obj-y += tests/fuzz/qos_fuzz.o > > fuzz-obj-y += tests/fuzz/i440fx_fuzz.o > +fuzz-obj-y += tests/fuzz/virtio_net_fuzz.o > > FUZZ_LDFLAGS += -Xlinker -T$(SRC_PATH)/tests/fuzz/fork_fuzz.ld > diff --git a/tests/fuzz/virtio_net_fuzz.c b/tests/fuzz/virtio_net_fuzz.c > new file mode 100644 > index 0000000000..0543cfd32a > --- /dev/null > +++ b/tests/fuzz/virtio_net_fuzz.c > @@ -0,0 +1,123 @@ > +/* > + * virtio-net Fuzzing Target > + * > + * 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 "fuzz.h" > +#include "fork_fuzz.h" > +#include "qos_fuzz.h" > +#include "tests/libqtest.h" > +#include "tests/libqos/virtio-net.h" > + > + > +static void virtio_net_fuzz_multi(QTestState *s, > + const unsigned char *Data, size_t Size) > +{ > + typedef struct vq_action { > + uint8_t queue; > + uint8_t length; > + uint8_t write; > + uint8_t next; > + bool kick; > + } vq_action; > + > + uint64_t req_addr[10]; > + int reqi = 0; > + uint32_t free_head = 0; > + > + QGuestAllocator *t_alloc = fuzz_qos_alloc; > + > + QVirtioNet *net_if = fuzz_qos_obj; > + QVirtioDevice *dev = net_if->vdev; > + QVirtQueue *q; > + vq_action vqa; > + int iters = 0; > + while (true) { > + if (Size < sizeof(vqa)) { > + break; > + } > + memcpy(&vqa, Data, sizeof(vqa)); > + vqa = *((vq_action *)Data); > + Data += sizeof(vqa); > + Size -= sizeof(vqa); > + > + q = net_if->queues[vqa.queue % 3]; > + > + vqa.length = vqa.length >= Size ? Size : vqa.length; > + > + req_addr[reqi] = guest_alloc(t_alloc, vqa.length); > + qtest_memwrite(s, req_addr[reqi], Data, vqa.length); > + if (iters == 0) { > + free_head = qvirtqueue_add(s, q, req_addr[reqi], vqa.length, > + vqa.write, vqa.next); > + } else { > + qvirtqueue_add(s, q, > + req_addr[reqi], vqa.length, vqa.write , vqa.next); > + } > + iters++; > + reqi++; > + if (iters == 10) { > + break; > + } > + Data += vqa.length; > + Size -= vqa.length; > + } > + if (iters) { > + qvirtqueue_kick(s, dev, q, free_head); > + qtest_clock_step_next(s); > + for (int i = 0; i < reqi; i++) { > + guest_free(t_alloc, req_addr[i]); > + } > + } > +} > + > +static int *sv; > + > +static void *virtio_net_test_setup_socket(GString *cmd_line, void *arg) > +{ > + if (!sv) { > + sv = g_new(int, 2); > + int ret = socketpair(PF_UNIX, SOCK_STREAM, 0, sv); > + fcntl(sv[0], F_SETFL, O_NONBLOCK); > + g_assert_cmpint(ret, !=, -1); > + } > + g_string_append_printf(cmd_line, " -netdev socket,fd=%d,id=hs0 ", sv[1]); > + return arg; > +} > + > +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); > + flush_events(s); > + _Exit(0); > + } else { > + wait(NULL); > + } > +} > + > +static void register_virtio_net_fuzz_targets(void) > +{ > + fuzz_add_qos_target(&(FuzzTarget){ > + .name = "virtio-net-fork-fuzz", > + .description = "Fuzz the virtio-net virtual queues, forking" > + "for each fuzz run", > + .pre_vm_init = &counter_shm_init, > + .pre_fuzz = &qos_init_path, > + .fuzz = virtio_net_fork_fuzz,}, > + "virtio-net", > + &(QOSGraphTestOptions){.before = virtio_net_test_setup_socket} > + ); > +} > + > +fuzz_target_init(register_virtio_net_fuzz_targets);
On Thu, Nov 7, 2019 at 2:44 PM Jason Wang <jasowang@redhat.com> wrote: > On 2019/10/30 下午10:50, Oleinik, Alexander wrote: > > From: Alexander Oleinik <alxndr@bu.edu> > > > > The virtio-net fuzz target feeds inputs to all three virtio-net > > virtqueues, and uses forking to avoid leaking state between fuzz runs. > > > > Signed-off-by: Alexander Oleinik <alxndr@bu.edu> > > > Can this fuzz vhost-net or vhost-user (I only see socket backend)? If > it's not too hard, it would be even more interesting. Fuzzing vhost devices would be awesome but this patch series does not do that. libfuzzer uses coverage-guided fuzzing. It needs to instrument the code. vhost kernel modules or external vhost-user processes aren't instrumented so the fuzzing engine has no code instrumentation feedback. It should be possible to solve those problems eventually. You could also run it as-is, but the fuzzer wouldn't make intelligent decisions about mutating input data to explore new code paths in vhost kernel modules. Stefan
diff --git a/tests/fuzz/Makefile.include b/tests/fuzz/Makefile.include index 37d6821bee..f1d9b46b1c 100644 --- a/tests/fuzz/Makefile.include +++ b/tests/fuzz/Makefile.include @@ -6,5 +6,6 @@ fuzz-obj-y += tests/fuzz/fork_fuzz.o fuzz-obj-y += tests/fuzz/qos_fuzz.o fuzz-obj-y += tests/fuzz/i440fx_fuzz.o +fuzz-obj-y += tests/fuzz/virtio_net_fuzz.o FUZZ_LDFLAGS += -Xlinker -T$(SRC_PATH)/tests/fuzz/fork_fuzz.ld diff --git a/tests/fuzz/virtio_net_fuzz.c b/tests/fuzz/virtio_net_fuzz.c new file mode 100644 index 0000000000..0543cfd32a --- /dev/null +++ b/tests/fuzz/virtio_net_fuzz.c @@ -0,0 +1,123 @@ +/* + * virtio-net Fuzzing Target + * + * 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 "fuzz.h" +#include "fork_fuzz.h" +#include "qos_fuzz.h" +#include "tests/libqtest.h" +#include "tests/libqos/virtio-net.h" + + +static void virtio_net_fuzz_multi(QTestState *s, + const unsigned char *Data, size_t Size) +{ + typedef struct vq_action { + uint8_t queue; + uint8_t length; + uint8_t write; + uint8_t next; + bool kick; + } vq_action; + + uint64_t req_addr[10]; + int reqi = 0; + uint32_t free_head = 0; + + QGuestAllocator *t_alloc = fuzz_qos_alloc; + + QVirtioNet *net_if = fuzz_qos_obj; + QVirtioDevice *dev = net_if->vdev; + QVirtQueue *q; + vq_action vqa; + int iters = 0; + while (true) { + if (Size < sizeof(vqa)) { + break; + } + memcpy(&vqa, Data, sizeof(vqa)); + vqa = *((vq_action *)Data); + Data += sizeof(vqa); + Size -= sizeof(vqa); + + q = net_if->queues[vqa.queue % 3]; + + vqa.length = vqa.length >= Size ? Size : vqa.length; + + req_addr[reqi] = guest_alloc(t_alloc, vqa.length); + qtest_memwrite(s, req_addr[reqi], Data, vqa.length); + if (iters == 0) { + free_head = qvirtqueue_add(s, q, req_addr[reqi], vqa.length, + vqa.write, vqa.next); + } else { + qvirtqueue_add(s, q, + req_addr[reqi], vqa.length, vqa.write , vqa.next); + } + iters++; + reqi++; + if (iters == 10) { + break; + } + Data += vqa.length; + Size -= vqa.length; + } + if (iters) { + qvirtqueue_kick(s, dev, q, free_head); + qtest_clock_step_next(s); + for (int i = 0; i < reqi; i++) { + guest_free(t_alloc, req_addr[i]); + } + } +} + +static int *sv; + +static void *virtio_net_test_setup_socket(GString *cmd_line, void *arg) +{ + if (!sv) { + sv = g_new(int, 2); + int ret = socketpair(PF_UNIX, SOCK_STREAM, 0, sv); + fcntl(sv[0], F_SETFL, O_NONBLOCK); + g_assert_cmpint(ret, !=, -1); + } + g_string_append_printf(cmd_line, " -netdev socket,fd=%d,id=hs0 ", sv[1]); + return arg; +} + +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); + flush_events(s); + _Exit(0); + } else { + wait(NULL); + } +} + +static void register_virtio_net_fuzz_targets(void) +{ + fuzz_add_qos_target(&(FuzzTarget){ + .name = "virtio-net-fork-fuzz", + .description = "Fuzz the virtio-net virtual queues, forking" + "for each fuzz run", + .pre_vm_init = &counter_shm_init, + .pre_fuzz = &qos_init_path, + .fuzz = virtio_net_fork_fuzz,}, + "virtio-net", + &(QOSGraphTestOptions){.before = virtio_net_test_setup_socket} + ); +} + +fuzz_target_init(register_virtio_net_fuzz_targets);