]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
virtio-net: ensure the received length does not exceed allocated size
authorBui Quang Minh <minhquangbui99@gmail.com>
Mon, 30 Jun 2025 14:42:10 +0000 (21:42 +0700)
committerPaolo Abeni <pabeni@redhat.com>
Thu, 3 Jul 2025 08:56:55 +0000 (10:56 +0200)
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>
drivers/net/virtio_net.c

index e53ba600605a5d7193b16a1a9decb6e42cf12366..31661bcb393231ccf3c29ed4959e8e28db526988 100644 (file)
@@ -778,6 +778,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_INC(dev, rx_length_errors);
+               return -1;
+       }
+
+       return 0;
+}
+
 static struct sk_buff *virtnet_build_skb(void *buf, unsigned int buflen,
                                         unsigned int headroom,
                                         unsigned int len)
@@ -1797,7 +1817,8 @@ 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,
+static struct page *xdp_linearize_page(struct net_device *dev,
+                                      struct receive_queue *rq,
                                       int *num_buf,
                                       struct page *p,
                                       int offset,
@@ -1817,18 +1838,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 = virtnet_rq_get_buf(rq, &buflen, NULL);
+               buf = virtnet_rq_get_buf(rq, &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.
                 */
@@ -1917,7 +1947,7 @@ static struct sk_buff *receive_small_xdp(struct net_device *dev,
                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)
@@ -2252,7 +2282,7 @@ static void *mergeable_xdp_get_buf(struct virtnet_info *vi,
         */
        if (!xdp_prog->aux->xdp_has_frags) {
                /* linearize data for XDP */
-               xdp_page = xdp_linearize_page(rq, num_buf,
+               xdp_page = xdp_linearize_page(vi->dev, rq, num_buf,
                                              *page, offset,
                                              XDP_PACKET_HEADROOM,
                                              len);