]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
virtio-net: ensure the received length does not exceed allocated size
authorBui Quang Minh <minhquangbui99@gmail.com>
Sat, 12 Jul 2025 03:52:02 +0000 (23:52 -0400)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Thu, 17 Jul 2025 16:30:50 +0000 (18:30 +0200)
[ Upstream commit 315dbdd7cdf6aa533829774caaf4d25f1fd20e73 ]

In xdp_linearize_page, when reading the following buffers from the ring,
we forget to check the received length with the true allocate size. This
can lead to an out-of-bound read. This commit adds that missing check.

Cc: <stable@vger.kernel.org>
Fixes: 4941d472bf95 ("virtio-net: do not reset during XDP set")
Signed-off-by: Bui Quang Minh <minhquangbui99@gmail.com>
Acked-by: Jason Wang <jasowang@redhat.com>
Link: https://patch.msgid.link/20250630144212.48471-2-minhquangbui99@gmail.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
Signed-off-by: Sasha Levin <sashal@kernel.org>
drivers/net/virtio_net.c

index d8138ad4f865a6e081e1be37e355b19ce0a99f59..ed27dd5c7fc8d5753ccebe1a1d98554234d3b382 100644 (file)
@@ -394,6 +394,26 @@ static unsigned int mergeable_ctx_to_truesize(void *mrg_ctx)
        return (unsigned long)mrg_ctx & ((1 << MRG_CTX_HEADER_SHIFT) - 1);
 }
 
+static int check_mergeable_len(struct net_device *dev, void *mrg_ctx,
+                              unsigned int len)
+{
+       unsigned int headroom, tailroom, room, truesize;
+
+       truesize = mergeable_ctx_to_truesize(mrg_ctx);
+       headroom = mergeable_ctx_to_headroom(mrg_ctx);
+       tailroom = headroom ? sizeof(struct skb_shared_info) : 0;
+       room = SKB_DATA_ALIGN(headroom + tailroom);
+
+       if (len > truesize - room) {
+               pr_debug("%s: rx error: len %u exceeds truesize %lu\n",
+                        dev->name, len, (unsigned long)(truesize - room));
+               dev->stats.rx_length_errors++;
+               return -1;
+       }
+
+       return 0;
+}
+
 /* Called from bottom half context */
 static struct sk_buff *page_to_skb(struct virtnet_info *vi,
                                   struct receive_queue *rq,
@@ -672,8 +692,9 @@ static unsigned int virtnet_get_headroom(struct virtnet_info *vi)
  * across multiple buffers (num_buf > 1), and we make sure buffers
  * have enough headroom.
  */
-static struct page *xdp_linearize_page(struct receive_queue *rq,
-                                      u16 *num_buf,
+static struct page *xdp_linearize_page(struct net_device *dev,
+                                      struct receive_queue *rq,
+                                      int *num_buf,
                                       struct page *p,
                                       int offset,
                                       int page_off,
@@ -692,18 +713,27 @@ static struct page *xdp_linearize_page(struct receive_queue *rq,
        memcpy(page_address(page) + page_off, page_address(p) + offset, *len);
        page_off += *len;
 
+       /* Only mergeable mode can go inside this while loop. In small mode,
+        * *num_buf == 1, so it cannot go inside.
+        */
        while (--*num_buf) {
                unsigned int buflen;
                void *buf;
+               void *ctx;
                int off;
 
-               buf = virtqueue_get_buf(rq->vq, &buflen);
+               buf = virtqueue_get_buf_ctx(rq->vq, &buflen, &ctx);
                if (unlikely(!buf))
                        goto err_buf;
 
                p = virt_to_head_page(buf);
                off = buf - page_address(p);
 
+               if (check_mergeable_len(dev, ctx, buflen)) {
+                       put_page(p);
+                       goto err_buf;
+               }
+
                /* guard against a misconfigured or uncooperative backend that
                 * is sending packet larger than the MTU.
                 */
@@ -771,14 +801,14 @@ static struct sk_buff *receive_small(struct net_device *dev,
                if (unlikely(xdp_headroom < virtnet_get_headroom(vi))) {
                        int offset = buf - page_address(page) + header_offset;
                        unsigned int tlen = len + vi->hdr_len;
-                       u16 num_buf = 1;
+                       int num_buf = 1;
 
                        xdp_headroom = virtnet_get_headroom(vi);
                        header_offset = VIRTNET_RX_PAD + xdp_headroom;
                        headroom = vi->hdr_len + header_offset;
                        buflen = SKB_DATA_ALIGN(GOOD_PACKET_LEN + headroom) +
                                 SKB_DATA_ALIGN(sizeof(struct skb_shared_info));
-                       xdp_page = xdp_linearize_page(rq, &num_buf, page,
+                       xdp_page = xdp_linearize_page(dev, rq, &num_buf, page,
                                                      offset, header_offset,
                                                      &tlen);
                        if (!xdp_page)
@@ -949,10 +979,12 @@ static struct sk_buff *receive_mergeable(struct net_device *dev,
                if (unlikely(num_buf > 1 ||
                             headroom < virtnet_get_headroom(vi))) {
                        /* linearize data for XDP */
-                       xdp_page = xdp_linearize_page(rq, &num_buf,
+                       int _num_buf = num_buf;
+                       xdp_page = xdp_linearize_page(dev, rq, &_num_buf,
                                                      page, offset,
                                                      VIRTIO_XDP_HEADROOM,
                                                      &len);
+                       num_buf = _num_buf;
                        frame_sz = PAGE_SIZE;
 
                        if (!xdp_page)