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