diff mbox series

[v4,19/20] fuzz: add virtio-net fuzz target

Message ID 20191030144926.11873-20-alxndr@bu.edu (mailing list archive)
State New, archived
Headers show
Series Add virtual device fuzzing support | expand

Commit Message

Alexander Bulekov Oct. 30, 2019, 2:50 p.m. UTC
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>
---
 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

Comments

Stefan Hajnoczi Nov. 7, 2019, 1:36 p.m. UTC | #1
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.
Jason Wang Nov. 7, 2019, 1:42 p.m. UTC | #2
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);
Stefan Hajnoczi Nov. 7, 2019, 3:41 p.m. UTC | #3
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 mbox series

Patch

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);