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