Headline
CVE-2021-3748: virtio-net: fix use after unmap/free for sg · qemu/qemu@bedd7e9
A use-after-free vulnerability was found in the virtio-net device of QEMU. It could occur when the descriptor’s address belongs to the non direct access region, due to num_buffers being set after the virtqueue elem has been unmapped. A malicious guest could use this flaw to crash QEMU, resulting in a denial of service condition, or potentially execute code on the host with the privileges of the QEMU process.
@@ -1746,10 +1746,13 @@ static ssize_t virtio_net_receive_rcu(NetClientState *nc, const uint8_t *buf, VirtIONet *n = qemu_get_nic_opaque(nc); VirtIONetQueue *q = virtio_net_get_subqueue(nc); VirtIODevice *vdev = VIRTIO_DEVICE(n); VirtQueueElement *elems[VIRTQUEUE_MAX_SIZE]; size_t lens[VIRTQUEUE_MAX_SIZE]; struct iovec mhdr_sg[VIRTQUEUE_MAX_SIZE]; struct virtio_net_hdr_mrg_rxbuf mhdr; unsigned mhdr_cnt = 0; size_t offset, i, guest_offset; size_t offset, i, guest_offset, j; ssize_t err;
if (!virtio_net_can_receive(nc)) { return -1; @@ -1780,6 +1783,12 @@ static ssize_t virtio_net_receive_rcu(NetClientState *nc, const uint8_t *buf,
total = 0;
if (i == VIRTQUEUE_MAX_SIZE) { virtio_error(vdev, “virtio-net unexpected long buffer chain”); err = size; goto err; }
elem = virtqueue_pop(q->rx_vq, sizeof(VirtQueueElement)); if (!elem) { if (i) { @@ -1791,15 +1800,17 @@ static ssize_t virtio_net_receive_rcu(NetClientState *nc, const uint8_t *buf, n->guest_hdr_len, n->host_hdr_len, vdev->guest_features); } return -1; err = -1; goto err; }
if (elem->in_num < 1) { virtio_error(vdev, “virtio-net receive queue contains no in buffers”); virtqueue_detach_element(q->rx_vq, elem, 0); g_free(elem); return -1; err = -1; goto err; }
sg = elem->in_sg; @@ -1836,12 +1847,13 @@ static ssize_t virtio_net_receive_rcu(NetClientState *nc, const uint8_t *buf, if (!n->mergeable_rx_bufs && offset < size) { virtqueue_unpop(q->rx_vq, elem, total); g_free(elem); return size; err = size; goto err; }
/* signal other side */ virtqueue_fill(q->rx_vq, elem, total, i++); g_free(elem); elems[i] = elem; lens[i] = total; i++; }
if (mhdr_cnt) { @@ -1851,10 +1863,23 @@ static ssize_t virtio_net_receive_rcu(NetClientState *nc, const uint8_t *buf, &mhdr.num_buffers, sizeof mhdr.num_buffers); }
for (j = 0; j < i; j++) { /* signal other side */ virtqueue_fill(q->rx_vq, elems[j], lens[j], j); g_free(elems[j]); }
virtqueue_flush(q->rx_vq, i); virtio_notify(vdev, q->rx_vq);
return size;
err: for (j = 0; j < i; j++) { g_free(elems[j]); }
return err; }
static ssize_t virtio_net_do_receive(NetClientState *nc, const uint8_t *buf,