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/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 "qos_fuzz.h" 23 24 #define TEST_IMAGE_SIZE (64 * 1024 * 1024) 25 #define PCI_SLOT 0x02 26 #define PCI_FN 0x00 27 28 #define MAX_NUM_QUEUES 64 29 30 /* Based on tests/qtest/virtio-blk-test.c. */ 31 typedef struct { 32 int num_queues; 33 QVirtQueue *vq[MAX_NUM_QUEUES + 2]; 34 } QVirtioBlkQueues; 35 36 static QVirtioBlkQueues *qvirtio_blk_init(QVirtioDevice *dev, uint64_t mask) 37 { 38 QVirtioBlkQueues *vs; 39 uint64_t features; 40 41 vs = g_new0(QVirtioBlkQueues, 1); 42 43 features = qvirtio_get_features(dev); 44 if (!mask) { 45 mask = ~((1u << VIRTIO_RING_F_INDIRECT_DESC) | 46 (1u << VIRTIO_RING_F_EVENT_IDX) | 47 (1u << VIRTIO_BLK_F_SCSI)); 48 } 49 mask |= ~QVIRTIO_F_BAD_FEATURE; 50 features &= mask; 51 qvirtio_set_features(dev, features); 52 53 vs->num_queues = 1; 54 vs->vq[0] = qvirtqueue_setup(dev, fuzz_qos_alloc, 0); 55 56 qvirtio_set_driver_ok(dev); 57 58 return vs; 59 } 60 61 static void virtio_blk_fuzz(QTestState *s, QVirtioBlkQueues* queues, 62 const unsigned char *Data, size_t Size) 63 { 64 /* 65 * Data is a sequence of random bytes. We split them up into "actions", 66 * followed by data: 67 * [vqa][dddddddd][vqa][dddd][vqa][dddddddddddd] ... 68 * The length of the data is specified by the preceding vqa.length 69 */ 70 typedef struct vq_action { 71 uint8_t queue; 72 uint8_t length; 73 uint8_t write; 74 uint8_t next; 75 uint8_t kick; 76 } vq_action; 77 78 /* Keep track of the free head for each queue we interact with */ 79 bool vq_touched[MAX_NUM_QUEUES + 2] = {0}; 80 uint32_t free_head[MAX_NUM_QUEUES + 2]; 81 82 QGuestAllocator *t_alloc = fuzz_qos_alloc; 83 84 QVirtioBlk *blk = fuzz_qos_obj; 85 QVirtioDevice *dev = blk->vdev; 86 QVirtQueue *q; 87 vq_action vqa; 88 while (Size >= sizeof(vqa)) { 89 /* Copy the action, so we can normalize length, queue and flags */ 90 memcpy(&vqa, Data, sizeof(vqa)); 91 92 Data += sizeof(vqa); 93 Size -= sizeof(vqa); 94 95 vqa.queue = vqa.queue % queues->num_queues; 96 /* Cap length at the number of remaining bytes in data */ 97 vqa.length = vqa.length >= Size ? Size : vqa.length; 98 vqa.write = vqa.write & 1; 99 vqa.next = vqa.next & 1; 100 vqa.kick = vqa.kick & 1; 101 102 q = queues->vq[vqa.queue]; 103 104 /* Copy the data into ram, and place it on the virtqueue */ 105 uint64_t req_addr = guest_alloc(t_alloc, vqa.length); 106 qtest_memwrite(s, req_addr, Data, vqa.length); 107 if (vq_touched[vqa.queue] == 0) { 108 vq_touched[vqa.queue] = 1; 109 free_head[vqa.queue] = qvirtqueue_add(s, q, req_addr, vqa.length, 110 vqa.write, vqa.next); 111 } else { 112 qvirtqueue_add(s, q, req_addr, vqa.length, vqa.write , vqa.next); 113 } 114 115 if (vqa.kick) { 116 qvirtqueue_kick(s, dev, q, free_head[vqa.queue]); 117 free_head[vqa.queue] = 0; 118 } 119 Data += vqa.length; 120 Size -= vqa.length; 121 } 122 /* In the end, kick each queue we interacted with */ 123 for (int i = 0; i < MAX_NUM_QUEUES + 2; i++) { 124 if (vq_touched[i]) { 125 qvirtqueue_kick(s, dev, queues->vq[i], free_head[i]); 126 } 127 } 128 } 129 130 static void virtio_blk_with_flag_fuzz(QTestState *s, 131 const unsigned char *Data, size_t Size) 132 { 133 QVirtioBlk *blk = fuzz_qos_obj; 134 static QVirtioBlkQueues *queues; 135 136 if (Size >= sizeof(uint64_t)) { 137 queues = qvirtio_blk_init(blk->vdev, *(uint64_t *)Data); 138 virtio_blk_fuzz(s, queues, 139 Data + sizeof(uint64_t), Size - sizeof(uint64_t)); 140 flush_events(s); 141 } 142 fuzz_reset(s); 143 } 144 145 static void virtio_blk_pre_fuzz(QTestState *s) 146 { 147 qos_init_path(s); 148 } 149 150 static void drive_destroy(void *path) 151 { 152 unlink(path); 153 g_free(path); 154 } 155 156 static char *drive_create(void) 157 { 158 int fd, ret; 159 char *t_path; 160 161 /* Create a temporary raw image */ 162 fd = g_file_open_tmp("qtest.XXXXXX", &t_path, NULL); 163 g_assert_cmpint(fd, >=, 0); 164 ret = ftruncate(fd, TEST_IMAGE_SIZE); 165 g_assert_cmpint(ret, ==, 0); 166 close(fd); 167 168 g_test_queue_destroy(drive_destroy, t_path); 169 return t_path; 170 } 171 172 static void *virtio_blk_test_setup(GString *cmd_line, void *arg) 173 { 174 char *tmp_path = drive_create(); 175 176 g_string_append_printf(cmd_line, 177 " -drive if=none,id=drive0,file=%s," 178 "format=raw,auto-read-only=off ", 179 tmp_path); 180 181 return arg; 182 } 183 184 static void register_virtio_blk_fuzz_targets(void) 185 { 186 fuzz_add_qos_target(&(FuzzTarget){ 187 .name = "virtio-blk-flags-fuzz", 188 .description = "Fuzz the virtio-blk virtual queues. " 189 "Also fuzzes the virtio flags)", 190 .pre_fuzz = &virtio_blk_pre_fuzz, 191 .fuzz = virtio_blk_with_flag_fuzz,}, 192 "virtio-blk", 193 &(QOSGraphTestOptions){.before = virtio_blk_test_setup} 194 ); 195 } 196 197 fuzz_target_init(register_virtio_blk_fuzz_targets); 198