diff mbox series

[v1,1/2] fuzz: add virtio-blk fuzz target

Message ID 0b922b854ac4121dd8574c3e9cd36c562f7d0a3c.1602078083.git.dimastep@yandex-team.ru
State New
Headers show
Series fuzz: add virtio-blk fuzz target | expand

Commit Message

Dima Stepanov Oct. 7, 2020, 1:47 p.m. UTC
The virtio-blk fuzz target sets up and fuzzes the available virtio-blk
queues. The implementation is based on two files:
  - tests/qtest/fuzz/virtio_scsi_fuzz.c
  - tests/qtest/virtio_blk_test.c

Signed-off-by: Dima Stepanov <dimastep@yandex-team.ru>
---
 tests/qtest/fuzz/meson.build       |   1 +
 tests/qtest/fuzz/virtio_blk_fuzz.c | 234 +++++++++++++++++++++++++++++++++++++
 2 files changed, 235 insertions(+)
 create mode 100644 tests/qtest/fuzz/virtio_blk_fuzz.c

Comments

Alexander Bulekov Oct. 13, 2020, 3:30 p.m. UTC | #1
On 201007 1647, Dima Stepanov wrote:
> The virtio-blk fuzz target sets up and fuzzes the available virtio-blk
> queues. The implementation is based on two files:
>   - tests/qtest/fuzz/virtio_scsi_fuzz.c
>   - tests/qtest/virtio_blk_test.c
> 
> Signed-off-by: Dima Stepanov <dimastep@yandex-team.ru>
> ---
>  tests/qtest/fuzz/meson.build       |   1 +
>  tests/qtest/fuzz/virtio_blk_fuzz.c | 234 +++++++++++++++++++++++++++++++++++++
>  2 files changed, 235 insertions(+)
>  create mode 100644 tests/qtest/fuzz/virtio_blk_fuzz.c
> 
> diff --git a/tests/qtest/fuzz/meson.build b/tests/qtest/fuzz/meson.build
> index b31ace7..3b923dc 100644
> --- a/tests/qtest/fuzz/meson.build
> +++ b/tests/qtest/fuzz/meson.build
> @@ -5,6 +5,7 @@ specific_fuzz_ss.add(files('fuzz.c', 'fork_fuzz.c', 'qos_fuzz.c',
>  specific_fuzz_ss.add(when: 'CONFIG_I440FX', if_true: files('i440fx_fuzz.c'))
>  specific_fuzz_ss.add(when: 'CONFIG_VIRTIO_NET', if_true: files('virtio_net_fuzz.c'))
>  specific_fuzz_ss.add(when: 'CONFIG_VIRTIO_SCSI', if_true: files('virtio_scsi_fuzz.c'))
> +specific_fuzz_ss.add(files('virtio_blk_fuzz.c'))

Hi Dima,
For consistency, maybe
specific_fuzz_ss.add(when: 'CONFIG_VIRTIO_BLK', if_true: files('virtio_blk_fuzz.c'))

>  
>  fork_fuzz = declare_dependency(
>    link_args: config_host['FUZZ_EXE_LDFLAGS'].split() +
> diff --git a/tests/qtest/fuzz/virtio_blk_fuzz.c b/tests/qtest/fuzz/virtio_blk_fuzz.c
> new file mode 100644
> index 0000000..623a756
> --- /dev/null
> +++ b/tests/qtest/fuzz/virtio_blk_fuzz.c
> @@ -0,0 +1,234 @@
> +/*
> + * virtio-blk Fuzzing Target
> + *
> + * Copyright Red Hat Inc., 2020
> + *
> + * Based on virtio-scsi-fuzz target.
> + *
> + * 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 "tests/qtest/libqos/libqtest.h"
> +#include "tests/qtest/libqos/virtio-blk.h"
> +#include "tests/qtest/libqos/virtio.h"
> +#include "tests/qtest/libqos/virtio-pci.h"
> +#include "standard-headers/linux/virtio_ids.h"
> +#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)
> +#define PCI_SLOT                0x02
> +#define PCI_FN                  0x00
> +
> +#define MAX_NUM_QUEUES 64
> +
> +/* Based on tests/qtest/virtio-blk-test.c. */
> +typedef struct {
> +    int num_queues;
> +    QVirtQueue *vq[MAX_NUM_QUEUES + 2];
> +} QVirtioBlkQueues;
> +
> +static QVirtioBlkQueues *qvirtio_blk_init(QVirtioDevice *dev, uint64_t mask)
> +{
> +    QVirtioBlkQueues *vs;
> +    uint64_t features;
> +
> +    vs = g_new0(QVirtioBlkQueues, 1);
> +
> +    features = qvirtio_get_features(dev);
> +    if (!mask) {
> +        mask = ~((1u << VIRTIO_RING_F_INDIRECT_DESC) |
> +                (1u << VIRTIO_RING_F_EVENT_IDX) |
> +                (1u << VIRTIO_BLK_F_SCSI));
> +    }
> +    mask |= ~QVIRTIO_F_BAD_FEATURE;
> +    features &= mask;
> +    qvirtio_set_features(dev, features);
> +
> +    vs->num_queues = 1;
> +    vs->vq[0] = qvirtqueue_setup(dev, fuzz_qos_alloc, 0);
> +
> +    qvirtio_set_driver_ok(dev);
> +
> +    return vs;
> +}
> +
> +static void virtio_blk_fuzz(QTestState *s, QVirtioBlkQueues* queues,
> +        const unsigned char *Data, size_t Size)
> +{
> +    /*
> +     * Data is a sequence of random bytes. We split them up into "actions",
> +     * followed by data:
> +     * [vqa][dddddddd][vqa][dddd][vqa][dddddddddddd] ...
> +     * The length of the data is specified by the preceding vqa.length
> +     */
> +    typedef struct vq_action {
> +        uint8_t queue;
> +        uint8_t length;
> +        uint8_t write;
> +        uint8_t next;
> +        uint8_t kick;
> +    } vq_action;
> +
> +    /* Keep track of the free head for each queue we interact with */
> +    bool vq_touched[MAX_NUM_QUEUES + 2] = {0};
> +    uint32_t free_head[MAX_NUM_QUEUES + 2];
> +
> +    QGuestAllocator *t_alloc = fuzz_qos_alloc;
> +
> +    QVirtioBlk *blk = fuzz_qos_obj;
> +    QVirtioDevice *dev = blk->vdev;
> +    QVirtQueue *q;
> +    vq_action vqa;
> +    while (Size >= sizeof(vqa)) {
> +        /* Copy the action, so we can normalize length, queue and flags */
> +        memcpy(&vqa, Data, sizeof(vqa));
> +
> +        Data += sizeof(vqa);
> +        Size -= sizeof(vqa);
> +
> +        vqa.queue = vqa.queue % queues->num_queues;
> +        /* Cap length at the number of remaining bytes in data */
> +        vqa.length = vqa.length >= Size ? Size : vqa.length;
> +        vqa.write = vqa.write & 1;
> +        vqa.next = vqa.next & 1;
> +        vqa.kick = vqa.kick & 1;
> +
> +        q = queues->vq[vqa.queue];
> +
> +        /* Copy the data into ram, and place it on the virtqueue */
> +        uint64_t req_addr = guest_alloc(t_alloc, vqa.length);
> +        qtest_memwrite(s, req_addr, Data, vqa.length);
> +        if (vq_touched[vqa.queue] == 0) {
> +            vq_touched[vqa.queue] = 1;
> +            free_head[vqa.queue] = qvirtqueue_add(s, q, req_addr, vqa.length,
> +                    vqa.write, vqa.next);
> +        } else {
> +            qvirtqueue_add(s, q, req_addr, vqa.length, vqa.write , vqa.next);
> +        }
> +
> +        if (vqa.kick) {
> +            qvirtqueue_kick(s, dev, q, free_head[vqa.queue]);
> +            free_head[vqa.queue] = 0;
> +        }
> +        Data += vqa.length;
> +        Size -= vqa.length;
> +    }
> +    /* In the end, kick each queue we interacted with */
> +    for (int i = 0; i < MAX_NUM_QUEUES + 2; i++) {
> +        if (vq_touched[i]) {
> +            qvirtqueue_kick(s, dev, queues->vq[i], free_head[i]);
> +        }
> +    }
> +}
> +
> +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 {
> +        flush_events(s);
> +        wait(NULL);
> +    }
> +}
> +
> +static void virtio_blk_pre_fuzz(QTestState *s)
> +{
> +    qos_init_path(s);
> +    counter_shm_init();
> +}
> +
> +static void drive_destroy(void *path)
> +{
> +    unlink(path);
> +    g_free(path);
> +}
> +
> +static char *drive_create(void)
> +{
> +    int fd, ret;
> +    char *t_path = g_strdup("/tmp/qtest.XXXXXX");
> +
> +    /* Create a temporary raw image */
> +    fd = mkstemp(t_path);
> +    g_assert_cmpint(fd, >=, 0);
> +    ret = ftruncate(fd, TEST_IMAGE_SIZE);
> +    g_assert_cmpint(ret, ==, 0);
> +    close(fd);
> +
> +    g_test_queue_destroy(drive_destroy, t_path);
> +    return t_path;
> +}
> +

I tested this out and it works with multi-process fuzzing under -jobs=4
-workers=4 (this initialization happens after libfuzzer has already
forked the processes). This seems like an interesting alternative to
using fake null-co:// files. 
I wonder if some state might leak as these disks are filled with fuzzer
data.

Nit: these disk files remain after the fuzzer exists. It looks
like the libfuzzer people suggest simply using atexit() to perform
cleanup: https://reviews.llvm.org/D45762
The is that the only way I have found to terminate the fuzzer is with
SIGKILL, where atexit is skipped. QEMU installs some signal handlers in
os-posix.c:os_setup_signal_handling to notify the main_loop that the
qemu was killed. Since we replace qemu_main_loop by manually running
main_loop_wait, we don't check main_loop_should_exit().

I sent a patch to disable QEMU's signal handlers for the fuzzer.
Message-Id: <20201013152920.448335-1-alxndr@bu.edu>

With an atexit() call to clean up the temporary images:
Reviewed-by: Alexander Bulekov <alxndr@bu.edu>

> +static void *virtio_blk_test_setup(GString *cmd_line, void *arg)
> +{
> +    char *tmp_path = drive_create();
> +
> +    g_string_append_printf(cmd_line,
> +                           " -drive if=none,id=drive0,file=%s,"
> +                           "format=raw,auto-read-only=off ",
> +                           tmp_path);
> +
> +    return 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,
> +                .pre_fuzz = &virtio_blk_pre_fuzz,
> +                .fuzz = virtio_blk_with_flag_fuzz,},
> +                "virtio-blk",
> +                &(QOSGraphTestOptions){.before = virtio_blk_test_setup}
> +                );
> +}
> +
> +fuzz_target_init(register_virtio_blk_fuzz_targets);
> -- 
> 2.7.4
>
Dima Stepanov Oct. 14, 2020, 7:29 a.m. UTC | #2
On Tue, Oct 13, 2020 at 11:30:52AM -0400, Alexander Bulekov wrote:
> On 201007 1647, Dima Stepanov wrote:
> > The virtio-blk fuzz target sets up and fuzzes the available virtio-blk
> > queues. The implementation is based on two files:
> >   - tests/qtest/fuzz/virtio_scsi_fuzz.c
> >   - tests/qtest/virtio_blk_test.c
> > 
> > Signed-off-by: Dima Stepanov <dimastep@yandex-team.ru>
> > ---
> >  tests/qtest/fuzz/meson.build       |   1 +
> >  tests/qtest/fuzz/virtio_blk_fuzz.c | 234 +++++++++++++++++++++++++++++++++++++
> >  2 files changed, 235 insertions(+)
> >  create mode 100644 tests/qtest/fuzz/virtio_blk_fuzz.c
> > 
> > diff --git a/tests/qtest/fuzz/meson.build b/tests/qtest/fuzz/meson.build
> > index b31ace7..3b923dc 100644
> > --- a/tests/qtest/fuzz/meson.build
> > +++ b/tests/qtest/fuzz/meson.build
> > @@ -5,6 +5,7 @@ specific_fuzz_ss.add(files('fuzz.c', 'fork_fuzz.c', 'qos_fuzz.c',
> >  specific_fuzz_ss.add(when: 'CONFIG_I440FX', if_true: files('i440fx_fuzz.c'))
> >  specific_fuzz_ss.add(when: 'CONFIG_VIRTIO_NET', if_true: files('virtio_net_fuzz.c'))
> >  specific_fuzz_ss.add(when: 'CONFIG_VIRTIO_SCSI', if_true: files('virtio_scsi_fuzz.c'))
> > +specific_fuzz_ss.add(files('virtio_blk_fuzz.c'))
> 
> Hi Dima,
> For consistency, maybe
> specific_fuzz_ss.add(when: 'CONFIG_VIRTIO_BLK', if_true: files('virtio_blk_fuzz.c'))
Good point, will update it.

> 
...
> > +
> > +static char *drive_create(void)
> > +{
> > +    int fd, ret;
> > +    char *t_path = g_strdup("/tmp/qtest.XXXXXX");
> > +
> > +    /* Create a temporary raw image */
> > +    fd = mkstemp(t_path);
> > +    g_assert_cmpint(fd, >=, 0);
> > +    ret = ftruncate(fd, TEST_IMAGE_SIZE);
> > +    g_assert_cmpint(ret, ==, 0);
> > +    close(fd);
> > +
> > +    g_test_queue_destroy(drive_destroy, t_path);
> > +    return t_path;
> > +}
> > +
> 
> I tested this out and it works with multi-process fuzzing under -jobs=4
> -workers=4 (this initialization happens after libfuzzer has already
> forked the processes). This seems like an interesting alternative to
> using fake null-co:// files. 
> I wonder if some state might leak as these disks are filled with fuzzer
> data.
Yes, i've also chosen between the fake null device and temporary file.
Tried this approach, just to see what will happen ). It seems to me that
slightly different paths can be triggered in this case and it is closer
to real usage.
But indeed, mb some state can leak, this is interesting.

> 
> Nit: these disk files remain after the fuzzer exists. It looks
> like the libfuzzer people suggest simply using atexit() to perform
> cleanup: https://reviews.llvm.org/D45762
> The is that the only way I have found to terminate the fuzzer is with
> SIGKILL, where atexit is skipped. QEMU installs some signal handlers in
> os-posix.c:os_setup_signal_handling to notify the main_loop that the
> qemu was killed. Since we replace qemu_main_loop by manually running
> main_loop_wait, we don't check main_loop_should_exit().
Got it! Thanks for sharing this is good to know ).

No other comments mixed in below.

Dima.
> 
> I sent a patch to disable QEMU's signal handlers for the fuzzer.
> Message-Id: <20201013152920.448335-1-alxndr@bu.edu>
> 
> With an atexit() call to clean up the temporary images:
> Reviewed-by: Alexander Bulekov <alxndr@bu.edu>
> 
> > +static void *virtio_blk_test_setup(GString *cmd_line, void *arg)
> > +{
> > +    char *tmp_path = drive_create();
> > +
> > +    g_string_append_printf(cmd_line,
> > +                           " -drive if=none,id=drive0,file=%s,"
> > +                           "format=raw,auto-read-only=off ",
> > +                           tmp_path);
> > +
> > +    return 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,
> > +                .pre_fuzz = &virtio_blk_pre_fuzz,
> > +                .fuzz = virtio_blk_with_flag_fuzz,},
> > +                "virtio-blk",
> > +                &(QOSGraphTestOptions){.before = virtio_blk_test_setup}
> > +                );
> > +}
> > +
> > +fuzz_target_init(register_virtio_blk_fuzz_targets);
> > -- 
> > 2.7.4
> >
Dima Stepanov Oct. 14, 2020, 7:39 a.m. UTC | #3
On Wed, Oct 14, 2020 at 10:29:41AM +0300, Dima Stepanov wrote:
> On Tue, Oct 13, 2020 at 11:30:52AM -0400, Alexander Bulekov wrote:
> > On 201007 1647, Dima Stepanov wrote:
> > > The virtio-blk fuzz target sets up and fuzzes the available virtio-blk
> > > queues. The implementation is based on two files:
> > >   - tests/qtest/fuzz/virtio_scsi_fuzz.c
> > >   - tests/qtest/virtio_blk_test.c
> > > 
> > > Signed-off-by: Dima Stepanov <dimastep@yandex-team.ru>
> > > ---
> > >  tests/qtest/fuzz/meson.build       |   1 +
> > >  tests/qtest/fuzz/virtio_blk_fuzz.c | 234 +++++++++++++++++++++++++++++++++++++
> > >  2 files changed, 235 insertions(+)
> > >  create mode 100644 tests/qtest/fuzz/virtio_blk_fuzz.c
> > > 
> > > diff --git a/tests/qtest/fuzz/meson.build b/tests/qtest/fuzz/meson.build
> > > index b31ace7..3b923dc 100644
> > > --- a/tests/qtest/fuzz/meson.build
> > > +++ b/tests/qtest/fuzz/meson.build
> > > @@ -5,6 +5,7 @@ specific_fuzz_ss.add(files('fuzz.c', 'fork_fuzz.c', 'qos_fuzz.c',
> > >  specific_fuzz_ss.add(when: 'CONFIG_I440FX', if_true: files('i440fx_fuzz.c'))
> > >  specific_fuzz_ss.add(when: 'CONFIG_VIRTIO_NET', if_true: files('virtio_net_fuzz.c'))
> > >  specific_fuzz_ss.add(when: 'CONFIG_VIRTIO_SCSI', if_true: files('virtio_scsi_fuzz.c'))
> > > +specific_fuzz_ss.add(files('virtio_blk_fuzz.c'))
> > 
> > Hi Dima,
> > For consistency, maybe
> > specific_fuzz_ss.add(when: 'CONFIG_VIRTIO_BLK', if_true: files('virtio_blk_fuzz.c'))
> Good point, will update it.
> 
> > 
> ...
> > > +
> > > +static char *drive_create(void)
> > > +{
> > > +    int fd, ret;
> > > +    char *t_path = g_strdup("/tmp/qtest.XXXXXX");
> > > +
> > > +    /* Create a temporary raw image */
> > > +    fd = mkstemp(t_path);
> > > +    g_assert_cmpint(fd, >=, 0);
> > > +    ret = ftruncate(fd, TEST_IMAGE_SIZE);
> > > +    g_assert_cmpint(ret, ==, 0);
> > > +    close(fd);
> > > +
> > > +    g_test_queue_destroy(drive_destroy, t_path);
> > > +    return t_path;
> > > +}
> > > +
> > 
> > I tested this out and it works with multi-process fuzzing under -jobs=4
> > -workers=4 (this initialization happens after libfuzzer has already
> > forked the processes). This seems like an interesting alternative to
> > using fake null-co:// files. 
> > I wonder if some state might leak as these disks are filled with fuzzer
> > data.
> Yes, i've also chosen between the fake null device and temporary file.
> Tried this approach, just to see what will happen ). It seems to me that
> slightly different paths can be triggered in this case and it is closer
> to real usage.
> But indeed, mb some state can leak, this is interesting.
> 
> > 
> > Nit: these disk files remain after the fuzzer exists. It looks
> > like the libfuzzer people suggest simply using atexit() to perform
> > cleanup: https://reviews.llvm.org/D45762
> > The is that the only way I have found to terminate the fuzzer is with
> > SIGKILL, where atexit is skipped. QEMU installs some signal handlers in
> > os-posix.c:os_setup_signal_handling to notify the main_loop that the
> > qemu was killed. Since we replace qemu_main_loop by manually running
> > main_loop_wait, we don't check main_loop_should_exit().
> Got it! Thanks for sharing this is good to know ).
> 
> No other comments mixed in below.
> 
> Dima.
> > 
> > I sent a patch to disable QEMU's signal handlers for the fuzzer.
> > Message-Id: <20201013152920.448335-1-alxndr@bu.edu>
Sorry, i couldn't find a patch you've pointed out above. Could you share
some link to it? Also, am i correct that it is a general change for the
QEMU fuzzing, so all the fuzzing targets will automatically reuse it?

> > 
> > With an atexit() call to clean up the temporary images:
> > Reviewed-by: Alexander Bulekov <alxndr@bu.edu>
> > 
> > > +static void *virtio_blk_test_setup(GString *cmd_line, void *arg)
> > > +{
> > > +    char *tmp_path = drive_create();
> > > +
> > > +    g_string_append_printf(cmd_line,
> > > +                           " -drive if=none,id=drive0,file=%s,"
> > > +                           "format=raw,auto-read-only=off ",
> > > +                           tmp_path);
> > > +
> > > +    return 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,
> > > +                .pre_fuzz = &virtio_blk_pre_fuzz,
> > > +                .fuzz = virtio_blk_with_flag_fuzz,},
> > > +                "virtio-blk",
> > > +                &(QOSGraphTestOptions){.before = virtio_blk_test_setup}
> > > +                );
> > > +}
> > > +
> > > +fuzz_target_init(register_virtio_blk_fuzz_targets);
> > > -- 
> > > 2.7.4
> > >
Darren Kenny Oct. 14, 2020, 9:03 a.m. UTC | #4
Hi Dima,

On Wednesday, 2020-10-14 at 10:39:01 +03, Dima Stepanov wrote:
> On Wed, Oct 14, 2020 at 10:29:41AM +0300, Dima Stepanov wrote:
>> On Tue, Oct 13, 2020 at 11:30:52AM -0400, Alexander Bulekov wrote:
>> > On 201007 1647, Dima Stepanov wrote:
...

>> > 
>> > I sent a patch to disable QEMU's signal handlers for the fuzzer.
>> > Message-Id: <20201013152920.448335-1-alxndr@bu.edu>
> Sorry, i couldn't find a patch you've pointed out above. Could you share
> some link to it? Also, am i correct that it is a general change for the
> QEMU fuzzing, so all the fuzzing targets will automatically reuse it?
>

The patch Alex is referring to is:

- https://lore.kernel.org/qemu-devel/20201013152920.448335-1-alxndr@bu.edu/

Thanks,

Darren.
diff mbox series

Patch

diff --git a/tests/qtest/fuzz/meson.build b/tests/qtest/fuzz/meson.build
index b31ace7..3b923dc 100644
--- a/tests/qtest/fuzz/meson.build
+++ b/tests/qtest/fuzz/meson.build
@@ -5,6 +5,7 @@  specific_fuzz_ss.add(files('fuzz.c', 'fork_fuzz.c', 'qos_fuzz.c',
 specific_fuzz_ss.add(when: 'CONFIG_I440FX', if_true: files('i440fx_fuzz.c'))
 specific_fuzz_ss.add(when: 'CONFIG_VIRTIO_NET', if_true: files('virtio_net_fuzz.c'))
 specific_fuzz_ss.add(when: 'CONFIG_VIRTIO_SCSI', if_true: files('virtio_scsi_fuzz.c'))
+specific_fuzz_ss.add(files('virtio_blk_fuzz.c'))
 
 fork_fuzz = declare_dependency(
   link_args: config_host['FUZZ_EXE_LDFLAGS'].split() +
diff --git a/tests/qtest/fuzz/virtio_blk_fuzz.c b/tests/qtest/fuzz/virtio_blk_fuzz.c
new file mode 100644
index 0000000..623a756
--- /dev/null
+++ b/tests/qtest/fuzz/virtio_blk_fuzz.c
@@ -0,0 +1,234 @@ 
+/*
+ * virtio-blk Fuzzing Target
+ *
+ * Copyright Red Hat Inc., 2020
+ *
+ * Based on virtio-scsi-fuzz target.
+ *
+ * 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 "tests/qtest/libqos/libqtest.h"
+#include "tests/qtest/libqos/virtio-blk.h"
+#include "tests/qtest/libqos/virtio.h"
+#include "tests/qtest/libqos/virtio-pci.h"
+#include "standard-headers/linux/virtio_ids.h"
+#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)
+#define PCI_SLOT                0x02
+#define PCI_FN                  0x00
+
+#define MAX_NUM_QUEUES 64
+
+/* Based on tests/qtest/virtio-blk-test.c. */
+typedef struct {
+    int num_queues;
+    QVirtQueue *vq[MAX_NUM_QUEUES + 2];
+} QVirtioBlkQueues;
+
+static QVirtioBlkQueues *qvirtio_blk_init(QVirtioDevice *dev, uint64_t mask)
+{
+    QVirtioBlkQueues *vs;
+    uint64_t features;
+
+    vs = g_new0(QVirtioBlkQueues, 1);
+
+    features = qvirtio_get_features(dev);
+    if (!mask) {
+        mask = ~((1u << VIRTIO_RING_F_INDIRECT_DESC) |
+                (1u << VIRTIO_RING_F_EVENT_IDX) |
+                (1u << VIRTIO_BLK_F_SCSI));
+    }
+    mask |= ~QVIRTIO_F_BAD_FEATURE;
+    features &= mask;
+    qvirtio_set_features(dev, features);
+
+    vs->num_queues = 1;
+    vs->vq[0] = qvirtqueue_setup(dev, fuzz_qos_alloc, 0);
+
+    qvirtio_set_driver_ok(dev);
+
+    return vs;
+}
+
+static void virtio_blk_fuzz(QTestState *s, QVirtioBlkQueues* queues,
+        const unsigned char *Data, size_t Size)
+{
+    /*
+     * Data is a sequence of random bytes. We split them up into "actions",
+     * followed by data:
+     * [vqa][dddddddd][vqa][dddd][vqa][dddddddddddd] ...
+     * The length of the data is specified by the preceding vqa.length
+     */
+    typedef struct vq_action {
+        uint8_t queue;
+        uint8_t length;
+        uint8_t write;
+        uint8_t next;
+        uint8_t kick;
+    } vq_action;
+
+    /* Keep track of the free head for each queue we interact with */
+    bool vq_touched[MAX_NUM_QUEUES + 2] = {0};
+    uint32_t free_head[MAX_NUM_QUEUES + 2];
+
+    QGuestAllocator *t_alloc = fuzz_qos_alloc;
+
+    QVirtioBlk *blk = fuzz_qos_obj;
+    QVirtioDevice *dev = blk->vdev;
+    QVirtQueue *q;
+    vq_action vqa;
+    while (Size >= sizeof(vqa)) {
+        /* Copy the action, so we can normalize length, queue and flags */
+        memcpy(&vqa, Data, sizeof(vqa));
+
+        Data += sizeof(vqa);
+        Size -= sizeof(vqa);
+
+        vqa.queue = vqa.queue % queues->num_queues;
+        /* Cap length at the number of remaining bytes in data */
+        vqa.length = vqa.length >= Size ? Size : vqa.length;
+        vqa.write = vqa.write & 1;
+        vqa.next = vqa.next & 1;
+        vqa.kick = vqa.kick & 1;
+
+        q = queues->vq[vqa.queue];
+
+        /* Copy the data into ram, and place it on the virtqueue */
+        uint64_t req_addr = guest_alloc(t_alloc, vqa.length);
+        qtest_memwrite(s, req_addr, Data, vqa.length);
+        if (vq_touched[vqa.queue] == 0) {
+            vq_touched[vqa.queue] = 1;
+            free_head[vqa.queue] = qvirtqueue_add(s, q, req_addr, vqa.length,
+                    vqa.write, vqa.next);
+        } else {
+            qvirtqueue_add(s, q, req_addr, vqa.length, vqa.write , vqa.next);
+        }
+
+        if (vqa.kick) {
+            qvirtqueue_kick(s, dev, q, free_head[vqa.queue]);
+            free_head[vqa.queue] = 0;
+        }
+        Data += vqa.length;
+        Size -= vqa.length;
+    }
+    /* In the end, kick each queue we interacted with */
+    for (int i = 0; i < MAX_NUM_QUEUES + 2; i++) {
+        if (vq_touched[i]) {
+            qvirtqueue_kick(s, dev, queues->vq[i], free_head[i]);
+        }
+    }
+}
+
+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 {
+        flush_events(s);
+        wait(NULL);
+    }
+}
+
+static void virtio_blk_pre_fuzz(QTestState *s)
+{
+    qos_init_path(s);
+    counter_shm_init();
+}
+
+static void drive_destroy(void *path)
+{
+    unlink(path);
+    g_free(path);
+}
+
+static char *drive_create(void)
+{
+    int fd, ret;
+    char *t_path = g_strdup("/tmp/qtest.XXXXXX");
+
+    /* Create a temporary raw image */
+    fd = mkstemp(t_path);
+    g_assert_cmpint(fd, >=, 0);
+    ret = ftruncate(fd, TEST_IMAGE_SIZE);
+    g_assert_cmpint(ret, ==, 0);
+    close(fd);
+
+    g_test_queue_destroy(drive_destroy, t_path);
+    return t_path;
+}
+
+static void *virtio_blk_test_setup(GString *cmd_line, void *arg)
+{
+    char *tmp_path = drive_create();
+
+    g_string_append_printf(cmd_line,
+                           " -drive if=none,id=drive0,file=%s,"
+                           "format=raw,auto-read-only=off ",
+                           tmp_path);
+
+    return 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,
+                .pre_fuzz = &virtio_blk_pre_fuzz,
+                .fuzz = virtio_blk_with_flag_fuzz,},
+                "virtio-blk",
+                &(QOSGraphTestOptions){.before = virtio_blk_test_setup}
+                );
+}
+
+fuzz_target_init(register_virtio_blk_fuzz_targets);