qemu/tests/qtest/fuzz/virtio_blk_fuzz.c
<<
>>
Prefs
   1/*
   2 * virtio-blk Fuzzing Target
   3 *
   4 * Copyright Red Hat Inc., 2020
   5 *
   6 * Based on virtio-scsi-fuzz target.
   7 *
   8 * This work is licensed under the terms of the GNU GPL, version 2 or later.
   9 * See the COPYING file in the top-level directory.
  10 */
  11
  12#include "qemu/osdep.h"
  13
  14#include "tests/qtest/libqos/libqtest.h"
  15#include "tests/qtest/libqos/virtio-blk.h"
  16#include "tests/qtest/libqos/virtio.h"
  17#include "tests/qtest/libqos/virtio-pci.h"
  18#include "standard-headers/linux/virtio_ids.h"
  19#include "standard-headers/linux/virtio_pci.h"
  20#include "standard-headers/linux/virtio_blk.h"
  21#include "fuzz.h"
  22#include "fork_fuzz.h"
  23#include "qos_fuzz.h"
  24
  25#define TEST_IMAGE_SIZE         (64 * 1024 * 1024)
  26#define PCI_SLOT                0x02
  27#define PCI_FN                  0x00
  28
  29#define MAX_NUM_QUEUES 64
  30
  31/* Based on tests/qtest/virtio-blk-test.c. */
  32typedef struct {
  33    int num_queues;
  34    QVirtQueue *vq[MAX_NUM_QUEUES + 2];
  35} QVirtioBlkQueues;
  36
  37static QVirtioBlkQueues *qvirtio_blk_init(QVirtioDevice *dev, uint64_t mask)
  38{
  39    QVirtioBlkQueues *vs;
  40    uint64_t features;
  41
  42    vs = g_new0(QVirtioBlkQueues, 1);
  43
  44    features = qvirtio_get_features(dev);
  45    if (!mask) {
  46        mask = ~((1u << VIRTIO_RING_F_INDIRECT_DESC) |
  47                (1u << VIRTIO_RING_F_EVENT_IDX) |
  48                (1u << VIRTIO_BLK_F_SCSI));
  49    }
  50    mask |= ~QVIRTIO_F_BAD_FEATURE;
  51    features &= mask;
  52    qvirtio_set_features(dev, features);
  53
  54    vs->num_queues = 1;
  55    vs->vq[0] = qvirtqueue_setup(dev, fuzz_qos_alloc, 0);
  56
  57    qvirtio_set_driver_ok(dev);
  58
  59    return vs;
  60}
  61
  62static void virtio_blk_fuzz(QTestState *s, QVirtioBlkQueues* queues,
  63        const unsigned char *Data, size_t Size)
  64{
  65    /*
  66     * Data is a sequence of random bytes. We split them up into "actions",
  67     * followed by data:
  68     * [vqa][dddddddd][vqa][dddd][vqa][dddddddddddd] ...
  69     * The length of the data is specified by the preceding vqa.length
  70     */
  71    typedef struct vq_action {
  72        uint8_t queue;
  73        uint8_t length;
  74        uint8_t write;
  75        uint8_t next;
  76        uint8_t kick;
  77    } vq_action;
  78
  79    /* Keep track of the free head for each queue we interact with */
  80    bool vq_touched[MAX_NUM_QUEUES + 2] = {0};
  81    uint32_t free_head[MAX_NUM_QUEUES + 2];
  82
  83    QGuestAllocator *t_alloc = fuzz_qos_alloc;
  84
  85    QVirtioBlk *blk = fuzz_qos_obj;
  86    QVirtioDevice *dev = blk->vdev;
  87    QVirtQueue *q;
  88    vq_action vqa;
  89    while (Size >= sizeof(vqa)) {
  90        /* Copy the action, so we can normalize length, queue and flags */
  91        memcpy(&vqa, Data, sizeof(vqa));
  92
  93        Data += sizeof(vqa);
  94        Size -= sizeof(vqa);
  95
  96        vqa.queue = vqa.queue % queues->num_queues;
  97        /* Cap length at the number of remaining bytes in data */
  98        vqa.length = vqa.length >= Size ? Size : vqa.length;
  99        vqa.write = vqa.write & 1;
 100        vqa.next = vqa.next & 1;
 101        vqa.kick = vqa.kick & 1;
 102
 103        q = queues->vq[vqa.queue];
 104
 105        /* Copy the data into ram, and place it on the virtqueue */
 106        uint64_t req_addr = guest_alloc(t_alloc, vqa.length);
 107        qtest_memwrite(s, req_addr, Data, vqa.length);
 108        if (vq_touched[vqa.queue] == 0) {
 109            vq_touched[vqa.queue] = 1;
 110            free_head[vqa.queue] = qvirtqueue_add(s, q, req_addr, vqa.length,
 111                    vqa.write, vqa.next);
 112        } else {
 113            qvirtqueue_add(s, q, req_addr, vqa.length, vqa.write , vqa.next);
 114        }
 115
 116        if (vqa.kick) {
 117            qvirtqueue_kick(s, dev, q, free_head[vqa.queue]);
 118            free_head[vqa.queue] = 0;
 119        }
 120        Data += vqa.length;
 121        Size -= vqa.length;
 122    }
 123    /* In the end, kick each queue we interacted with */
 124    for (int i = 0; i < MAX_NUM_QUEUES + 2; i++) {
 125        if (vq_touched[i]) {
 126            qvirtqueue_kick(s, dev, queues->vq[i], free_head[i]);
 127        }
 128    }
 129}
 130
 131static void virtio_blk_fork_fuzz(QTestState *s,
 132        const unsigned char *Data, size_t Size)
 133{
 134    QVirtioBlk *blk = fuzz_qos_obj;
 135    static QVirtioBlkQueues *queues;
 136    if (!queues) {
 137        queues = qvirtio_blk_init(blk->vdev, 0);
 138    }
 139    if (fork() == 0) {
 140        virtio_blk_fuzz(s, queues, Data, Size);
 141        flush_events(s);
 142        _Exit(0);
 143    } else {
 144        flush_events(s);
 145        wait(NULL);
 146    }
 147}
 148
 149static void virtio_blk_with_flag_fuzz(QTestState *s,
 150        const unsigned char *Data, size_t Size)
 151{
 152    QVirtioBlk *blk = fuzz_qos_obj;
 153    static QVirtioBlkQueues *queues;
 154
 155    if (fork() == 0) {
 156        if (Size >= sizeof(uint64_t)) {
 157            queues = qvirtio_blk_init(blk->vdev, *(uint64_t *)Data);
 158            virtio_blk_fuzz(s, queues,
 159                             Data + sizeof(uint64_t), Size - sizeof(uint64_t));
 160            flush_events(s);
 161        }
 162        _Exit(0);
 163    } else {
 164        flush_events(s);
 165        wait(NULL);
 166    }
 167}
 168
 169static void virtio_blk_pre_fuzz(QTestState *s)
 170{
 171    qos_init_path(s);
 172    counter_shm_init();
 173}
 174
 175static void drive_destroy(void *path)
 176{
 177    unlink(path);
 178    g_free(path);
 179}
 180
 181static char *drive_create(void)
 182{
 183    int fd, ret;
 184    char *t_path = g_strdup("/tmp/qtest.XXXXXX");
 185
 186    /* Create a temporary raw image */
 187    fd = mkstemp(t_path);
 188    g_assert_cmpint(fd, >=, 0);
 189    ret = ftruncate(fd, TEST_IMAGE_SIZE);
 190    g_assert_cmpint(ret, ==, 0);
 191    close(fd);
 192
 193    g_test_queue_destroy(drive_destroy, t_path);
 194    return t_path;
 195}
 196
 197static void *virtio_blk_test_setup(GString *cmd_line, void *arg)
 198{
 199    char *tmp_path = drive_create();
 200
 201    g_string_append_printf(cmd_line,
 202                           " -drive if=none,id=drive0,file=%s,"
 203                           "format=raw,auto-read-only=off ",
 204                           tmp_path);
 205
 206    return arg;
 207}
 208
 209static void register_virtio_blk_fuzz_targets(void)
 210{
 211    fuzz_add_qos_target(&(FuzzTarget){
 212                .name = "virtio-blk-fuzz",
 213                .description = "Fuzz the virtio-blk virtual queues, forking "
 214                                "for each fuzz run",
 215                .pre_vm_init = &counter_shm_init,
 216                .pre_fuzz = &virtio_blk_pre_fuzz,
 217                .fuzz = virtio_blk_fork_fuzz,},
 218                "virtio-blk",
 219                &(QOSGraphTestOptions){.before = virtio_blk_test_setup}
 220                );
 221
 222    fuzz_add_qos_target(&(FuzzTarget){
 223                .name = "virtio-blk-flags-fuzz",
 224                .description = "Fuzz the virtio-blk virtual queues, forking "
 225                "for each fuzz run (also fuzzes the virtio flags)",
 226                .pre_vm_init = &counter_shm_init,
 227                .pre_fuzz = &virtio_blk_pre_fuzz,
 228                .fuzz = virtio_blk_with_flag_fuzz,},
 229                "virtio-blk",
 230                &(QOSGraphTestOptions){.before = virtio_blk_test_setup}
 231                );
 232}
 233
 234fuzz_target_init(register_virtio_blk_fuzz_targets);
 235