qemu/tests/qtest/fuzz/virtio_net_fuzz.c
<<
>>
Prefs
   1/*
   2 * virtio-net Fuzzing Target
   3 *
   4 * Copyright Red Hat Inc., 2019
   5 *
   6 * Authors:
   7 *  Alexander Bulekov   <alxndr@bu.edu>
   8 *
   9 * This work is licensed under the terms of the GNU GPL, version 2 or later.
  10 * See the COPYING file in the top-level directory.
  11 */
  12
  13#include "qemu/osdep.h"
  14
  15#include "standard-headers/linux/virtio_config.h"
  16#include "tests/qtest/libqos/libqtest.h"
  17#include "tests/qtest/libqos/virtio-net.h"
  18#include "fuzz.h"
  19#include "fork_fuzz.h"
  20#include "qos_fuzz.h"
  21
  22
  23#define QVIRTIO_NET_TIMEOUT_US (30 * 1000 * 1000)
  24#define QVIRTIO_RX_VQ 0
  25#define QVIRTIO_TX_VQ 1
  26#define QVIRTIO_CTRL_VQ 2
  27
  28static int sockfds[2];
  29static bool sockfds_initialized;
  30
  31static void virtio_net_fuzz_multi(QTestState *s,
  32        const unsigned char *Data, size_t Size, bool check_used)
  33{
  34    typedef struct vq_action {
  35        uint8_t queue;
  36        uint8_t length;
  37        uint8_t write;
  38        uint8_t next;
  39        uint8_t rx;
  40    } vq_action;
  41
  42    uint32_t free_head = 0;
  43
  44    QGuestAllocator *t_alloc = fuzz_qos_alloc;
  45
  46    QVirtioNet *net_if = fuzz_qos_obj;
  47    QVirtioDevice *dev = net_if->vdev;
  48    QVirtQueue *q;
  49    vq_action vqa;
  50    while (Size >= sizeof(vqa)) {
  51        memcpy(&vqa, Data, sizeof(vqa));
  52        Data += sizeof(vqa);
  53        Size -= sizeof(vqa);
  54
  55        q = net_if->queues[vqa.queue % 3];
  56
  57        vqa.length = vqa.length >= Size ? Size :  vqa.length;
  58
  59        /*
  60         * Only attempt to write incoming packets, when using the socket
  61         * backend. Otherwise, always place the input on a virtqueue.
  62         */
  63        if (vqa.rx && sockfds_initialized) {
  64            int ignored = write(sockfds[0], Data, vqa.length);
  65            (void) ignored;
  66        } else {
  67            vqa.rx = 0;
  68            uint64_t req_addr = guest_alloc(t_alloc, vqa.length);
  69            /*
  70             * If checking used ring, ensure that the fuzzer doesn't trigger
  71             * trivial asserion failure on zero-zied buffer
  72             */
  73            qtest_memwrite(s, req_addr, Data, vqa.length);
  74
  75
  76            free_head = qvirtqueue_add(s, q, req_addr, vqa.length,
  77                    vqa.write, vqa.next);
  78            qvirtqueue_add(s, q, req_addr, vqa.length, vqa.write , vqa.next);
  79            qvirtqueue_kick(s, dev, q, free_head);
  80        }
  81
  82        /* Run the main loop */
  83        qtest_clock_step(s, 100);
  84        flush_events(s);
  85
  86        /* Wait on used descriptors */
  87        if (check_used && !vqa.rx) {
  88            gint64 start_time = g_get_monotonic_time();
  89            /*
  90             * normally, we could just use qvirtio_wait_used_elem, but since we
  91             * must manually run the main-loop for all the bhs to run, we use
  92             * this hack with flush_events(), to run the main_loop
  93             */
  94            while (!vqa.rx && q != net_if->queues[QVIRTIO_RX_VQ]) {
  95                uint32_t got_desc_idx;
  96                /* Input led to a virtio_error */
  97                if (dev->bus->get_status(dev) & VIRTIO_CONFIG_S_NEEDS_RESET) {
  98                    break;
  99                }
 100                if (dev->bus->get_queue_isr_status(dev, q) &&
 101                        qvirtqueue_get_buf(s, q, &got_desc_idx, NULL)) {
 102                    g_assert_cmpint(got_desc_idx, ==, free_head);
 103                    break;
 104                }
 105                g_assert(g_get_monotonic_time() - start_time
 106                        <= QVIRTIO_NET_TIMEOUT_US);
 107
 108                /* Run the main loop */
 109                qtest_clock_step(s, 100);
 110                flush_events(s);
 111            }
 112        }
 113        Data += vqa.length;
 114        Size -= vqa.length;
 115    }
 116}
 117
 118static void virtio_net_fork_fuzz(QTestState *s,
 119        const unsigned char *Data, size_t Size)
 120{
 121    if (fork() == 0) {
 122        virtio_net_fuzz_multi(s, Data, Size, false);
 123        flush_events(s);
 124        _Exit(0);
 125    } else {
 126        flush_events(s);
 127        wait(NULL);
 128    }
 129}
 130
 131static void virtio_net_fork_fuzz_check_used(QTestState *s,
 132        const unsigned char *Data, size_t Size)
 133{
 134    if (fork() == 0) {
 135        virtio_net_fuzz_multi(s, Data, Size, true);
 136        flush_events(s);
 137        _Exit(0);
 138    } else {
 139        flush_events(s);
 140        wait(NULL);
 141    }
 142}
 143
 144static void virtio_net_pre_fuzz(QTestState *s)
 145{
 146    qos_init_path(s);
 147    counter_shm_init();
 148}
 149
 150static void *virtio_net_test_setup_socket(GString *cmd_line, void *arg)
 151{
 152    int ret = socketpair(PF_UNIX, SOCK_STREAM, 0, sockfds);
 153    g_assert_cmpint(ret, !=, -1);
 154    fcntl(sockfds[0], F_SETFL, O_NONBLOCK);
 155    sockfds_initialized = true;
 156    g_string_append_printf(cmd_line, " -netdev socket,fd=%d,id=hs0 ",
 157                           sockfds[1]);
 158    return arg;
 159}
 160
 161static void *virtio_net_test_setup_user(GString *cmd_line, void *arg)
 162{
 163    g_string_append_printf(cmd_line, " -netdev user,id=hs0 ");
 164    return arg;
 165}
 166
 167static void register_virtio_net_fuzz_targets(void)
 168{
 169    fuzz_add_qos_target(&(FuzzTarget){
 170            .name = "virtio-net-socket",
 171            .description = "Fuzz the virtio-net virtual queues. Fuzz incoming "
 172            "traffic using the socket backend",
 173            .pre_fuzz = &virtio_net_pre_fuzz,
 174            .fuzz = virtio_net_fork_fuzz,},
 175            "virtio-net",
 176            &(QOSGraphTestOptions){.before = virtio_net_test_setup_socket}
 177            );
 178
 179    fuzz_add_qos_target(&(FuzzTarget){
 180            .name = "virtio-net-socket-check-used",
 181            .description = "Fuzz the virtio-net virtual queues. Wait for the "
 182            "descriptors to be used. Timeout may indicate improperly handled "
 183            "input",
 184            .pre_fuzz = &virtio_net_pre_fuzz,
 185            .fuzz = virtio_net_fork_fuzz_check_used,},
 186            "virtio-net",
 187            &(QOSGraphTestOptions){.before = virtio_net_test_setup_socket}
 188            );
 189    fuzz_add_qos_target(&(FuzzTarget){
 190            .name = "virtio-net-slirp",
 191            .description = "Fuzz the virtio-net virtual queues with the slirp "
 192            " backend. Warning: May result in network traffic emitted from the "
 193            " process. Run in an isolated network environment.",
 194            .pre_fuzz = &virtio_net_pre_fuzz,
 195            .fuzz = virtio_net_fork_fuzz,},
 196            "virtio-net",
 197            &(QOSGraphTestOptions){.before = virtio_net_test_setup_user}
 198            );
 199}
 200
 201fuzz_target_init(register_virtio_net_fuzz_targets);
 202