febaa2
From c21488ecfff60d0acd0e8ced19f94b3ac050f945 Mon Sep 17 00:00:00 2001
febaa2
From: Jason Wang <jasowang@redhat.com>
febaa2
Date: Thu, 2 Sep 2021 13:44:12 +0800
febaa2
Subject: [PATCH] virtio-net: fix use after unmap/free for sg
febaa2
MIME-Version: 1.0
febaa2
Content-Type: text/plain; charset=UTF-8
febaa2
Content-Transfer-Encoding: 8bit
febaa2
febaa2
RH-Author: Jon Maloy <jmaloy@redhat.com>
febaa2
RH-MergeRequest: 45: virtio-net: fix use after unmap/free for sg
febaa2
RH-Commit: [1/1] 85cd1f4894f9584a11b797303139be479ce9781d (jmaloy/qemu-kvm)
febaa2
RH-Bugzilla: 1999221
febaa2
RH-Acked-by: Philippe Mathieu-Daudé <philmd@redhat.com>
febaa2
RH-Acked-by: Stefano Garzarella <sgarzare@redhat.com>
febaa2
RH-Acked-by: Laurent Vivier <lvivier@redhat.com>
febaa2
febaa2
When mergeable buffer is enabled, we try to set the num_buffers after
febaa2
the virtqueue elem has been unmapped. This will lead several issues,
febaa2
E.g a use after free when the descriptor has an address which belongs
febaa2
to the non direct access region. In this case we use bounce buffer
febaa2
that is allocated during address_space_map() and freed during
febaa2
address_space_unmap().
febaa2
febaa2
Fixing this by storing the elems temporarily in an array and delay the
febaa2
unmap after we set the the num_buffers.
febaa2
febaa2
This addresses CVE-2021-3748.
febaa2
febaa2
Reported-by: Alexander Bulekov <alxndr@bu.edu>
febaa2
Fixes: fbe78f4f55c6 ("virtio-net support")
febaa2
Cc: qemu-stable@nongnu.org
febaa2
Signed-off-by: Jason Wang <jasowang@redhat.com>
febaa2
febaa2
(cherry picked from commit bedd7e93d01961fcb16a97ae45d93acf357e11f6)
febaa2
Signed-off-by: Jon Maloy <jmaloy@redhat.com>
febaa2
---
febaa2
 hw/net/virtio-net.c | 39 ++++++++++++++++++++++++++++++++-------
febaa2
 1 file changed, 32 insertions(+), 7 deletions(-)
febaa2
febaa2
diff --git a/hw/net/virtio-net.c b/hw/net/virtio-net.c
febaa2
index 16d20cdee5..f205331dcf 100644
febaa2
--- a/hw/net/virtio-net.c
febaa2
+++ b/hw/net/virtio-net.c
febaa2
@@ -1746,10 +1746,13 @@ static ssize_t virtio_net_receive_rcu(NetClientState *nc, const uint8_t *buf,
febaa2
     VirtIONet *n = qemu_get_nic_opaque(nc);
febaa2
     VirtIONetQueue *q = virtio_net_get_subqueue(nc);
febaa2
     VirtIODevice *vdev = VIRTIO_DEVICE(n);
febaa2
+    VirtQueueElement *elems[VIRTQUEUE_MAX_SIZE];
febaa2
+    size_t lens[VIRTQUEUE_MAX_SIZE];
febaa2
     struct iovec mhdr_sg[VIRTQUEUE_MAX_SIZE];
febaa2
     struct virtio_net_hdr_mrg_rxbuf mhdr;
febaa2
     unsigned mhdr_cnt = 0;
febaa2
-    size_t offset, i, guest_offset;
febaa2
+    size_t offset, i, guest_offset, j;
febaa2
+    ssize_t err;
febaa2
 
febaa2
     if (!virtio_net_can_receive(nc)) {
febaa2
         return -1;
febaa2
@@ -1780,6 +1783,12 @@ static ssize_t virtio_net_receive_rcu(NetClientState *nc, const uint8_t *buf,
febaa2
 
febaa2
         total = 0;
febaa2
 
febaa2
+        if (i == VIRTQUEUE_MAX_SIZE) {
febaa2
+            virtio_error(vdev, "virtio-net unexpected long buffer chain");
febaa2
+            err = size;
febaa2
+            goto err;
febaa2
+        }
febaa2
+
febaa2
         elem = virtqueue_pop(q->rx_vq, sizeof(VirtQueueElement));
febaa2
         if (!elem) {
febaa2
             if (i) {
febaa2
@@ -1791,7 +1800,8 @@ static ssize_t virtio_net_receive_rcu(NetClientState *nc, const uint8_t *buf,
febaa2
                              n->guest_hdr_len, n->host_hdr_len,
febaa2
                              vdev->guest_features);
febaa2
             }
febaa2
-            return -1;
febaa2
+            err = -1;
febaa2
+            goto err;
febaa2
         }
febaa2
 
febaa2
         if (elem->in_num < 1) {
febaa2
@@ -1799,7 +1809,8 @@ static ssize_t virtio_net_receive_rcu(NetClientState *nc, const uint8_t *buf,
febaa2
                          "virtio-net receive queue contains no in buffers");
febaa2
             virtqueue_detach_element(q->rx_vq, elem, 0);
febaa2
             g_free(elem);
febaa2
-            return -1;
febaa2
+            err = -1;
febaa2
+            goto err;
febaa2
         }
febaa2
 
febaa2
         sg = elem->in_sg;
febaa2
@@ -1836,12 +1847,13 @@ static ssize_t virtio_net_receive_rcu(NetClientState *nc, const uint8_t *buf,
febaa2
         if (!n->mergeable_rx_bufs && offset < size) {
febaa2
             virtqueue_unpop(q->rx_vq, elem, total);
febaa2
             g_free(elem);
febaa2
-            return size;
febaa2
+            err = size;
febaa2
+            goto err;
febaa2
         }
febaa2
 
febaa2
-        /* signal other side */
febaa2
-        virtqueue_fill(q->rx_vq, elem, total, i++);
febaa2
-        g_free(elem);
febaa2
+        elems[i] = elem;
febaa2
+        lens[i] = total;
febaa2
+        i++;
febaa2
     }
febaa2
 
febaa2
     if (mhdr_cnt) {
febaa2
@@ -1851,10 +1863,23 @@ static ssize_t virtio_net_receive_rcu(NetClientState *nc, const uint8_t *buf,
febaa2
                      &mhdr.num_buffers, sizeof mhdr.num_buffers);
febaa2
     }
febaa2
 
febaa2
+    for (j = 0; j < i; j++) {
febaa2
+        /* signal other side */
febaa2
+        virtqueue_fill(q->rx_vq, elems[j], lens[j], j);
febaa2
+        g_free(elems[j]);
febaa2
+    }
febaa2
+
febaa2
     virtqueue_flush(q->rx_vq, i);
febaa2
     virtio_notify(vdev, q->rx_vq);
febaa2
 
febaa2
     return size;
febaa2
+
febaa2
+err:
febaa2
+    for (j = 0; j < i; j++) {
febaa2
+        g_free(elems[j]);
febaa2
+    }
febaa2
+
febaa2
+    return err;
febaa2
 }
febaa2
 
febaa2
 static ssize_t virtio_net_do_receive(NetClientState *nc, const uint8_t *buf,
febaa2
-- 
febaa2
2.18.2
febaa2