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. */ 32 typedef struct { 33 int num_queues; 34 QVirtQueue *vq[MAX_NUM_QUEUES + 2]; 35 } QVirtioBlkQueues; 36 37 static 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 62 static 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 131 static 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 149 static 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 169 static void virtio_blk_pre_fuzz(QTestState *s) 170 { 171 qos_init_path(s); 172 counter_shm_init(); 173 } 174 175 static void drive_destroy(void *path) 176 { 177 unlink(path); 178 g_free(path); 179 } 180 181 static 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 197 static 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 209 static 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 234 fuzz_target_init(register_virtio_blk_fuzz_targets); 235