Commit 476127b4 authored by 李澍非 's avatar 李澍非
Browse files

virtio-blk-fuzz: add virtio-blk fuzz target

parent af9eeb6a
Loading
Loading
Loading
Loading
Loading
+224 −0
Original line number Diff line number Diff line
/*
 * virtio-blk Fuzzing Target
 *
 * Copyright Huawei Technologies Co.,Ltd, 2020
 *
 * Authors:
 *  Pan Nengyuan   <pannengyuan@huawei.com> 
 *  Shufei Li   <lishufei@iie.ac.cn>
 *
 * 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 "qemu/bswap.h"
#include "qemu/module.h"
#include "libqos/qgraph.h"
#include "libqos/virtio-blk.h"
#include "standard-headers/linux/virtio_blk.h"
#include "standard-headers/linux/virtio_pci.h"
#include "fuzz.h"
#include "fork_fuzz.h"
#include "qos_fuzz.h"

/* TODO actually test the results and get rid of this */
#define qmp_discard_response(...) qobject_unref(qmp(__VA_ARGS__))

#define TEST_IMAGE_SIZE         (64 * 1024 * 1024)
#define QVIRTIO_BLK_TIMEOUT_US  (30 * 1000 * 1000)
#define PCI_SLOT_HP             0x06

#ifdef HOST_WORDS_BIGENDIAN
const bool host_is_big_endian = true;
#else
const bool host_is_big_endian; /* false */
#endif

static void drive_destroy(void *path)
{
    unlink(path);
    g_free(path);
    // qos_invalidate_command_line();
}

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 QVirtQueue *qvirtio_blk_init(QVirtioDevice *dev)
{
    uint64_t capacity;
    uint64_t features;
    QVirtQueue *vq;
    QGuestAllocator *t_alloc = fuzz_qos_alloc;
     
    capacity = qvirtio_config_readq(dev, 0); 
    g_assert_cmpint(capacity, ==, TEST_IMAGE_SIZE / 512); 
    features = qvirtio_get_features(dev); 
    features = features & ~(QVIRTIO_F_BAD_FEATURE | 
                    (1u << VIRTIO_RING_F_INDIRECT_DESC) | 
                    (1u << VIRTIO_RING_F_EVENT_IDX) | 
                    (1u << VIRTIO_BLK_F_SCSI)); 
    qvirtio_set_features(dev, features); 

    vq = qvirtqueue_setup(dev, t_alloc, 0);

    qvirtio_set_driver_ok(dev);

    return vq;
}

static void virtio_blk_fuzz(QTestState *s, QVirtQueue* vq, 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 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 = 0;
    uint32_t free_head;

    QGuestAllocator *t_alloc = fuzz_qos_alloc;

    QVirtioBlk *blk = fuzz_qos_obj;
    QVirtioDevice *dev = blk->vdev;
    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);

        /* 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;

        /* 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 == 0) {
            vq_touched = 1;
            free_head = qvirtqueue_add(s, vq, req_addr, vqa.length,
                    vqa.write, vqa.next);
        } else {
            qvirtqueue_add(s, vq, req_addr, vqa.length, vqa.write , vqa.next);
        }

        if (vqa.kick) {
            qvirtqueue_kick(s, dev, vq, free_head);
            free_head = 0;
        }
        Data += vqa.length;
        Size -= vqa.length;
    }
    /* In the end, kick each queue we interacted with */
    if (vq_touched) {
        qvirtqueue_kick(s, dev, vq, free_head);
    }
}

static void virtio_blk_with_flag_fuzz(QTestState *s,
        const unsigned char *Data, size_t Size)
{
    QVirtioBlk *blk = fuzz_qos_obj;
    static QVirtQueue *queue;

    if (fork() == 0) {
        if (Size >= sizeof(uint64_t)) {
            queue = qvirtio_blk_init(blk->vdev);
            virtio_blk_fuzz(s, queue,
                             Data + sizeof(uint64_t), Size - sizeof(uint64_t));
            flush_events(s);
        }
        _Exit(0);
    } else {
        flush_events(s);
        wait(NULL);
    }
}

static void virtio_blk_fork_fuzz(QTestState *s,
        const unsigned char *Data, size_t Size)
{
    QVirtioBlk *blk = fuzz_qos_obj;
    static QVirtQueue *queue;
        if (!queue) {
        queue = qvirtio_blk_init(blk->vdev);
    }
    if (fork() == 0) {
        virtio_blk_fuzz(s, queue, Data, Size);
        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 *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 "
                           "-drive if=none,id=drive1,file=null-co://,format=raw ",
                           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_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);
 No newline at end of file