Commit 315dbdd7 authored by Bui Quang Minh's avatar Bui Quang Minh Committed by Paolo Abeni
Browse files

virtio-net: ensure the received length does not exceed allocated size



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: 4941d472 ("virtio-net: do not reset during XDP set")
Signed-off-by: default avatarBui Quang Minh <minhquangbui99@gmail.com>
Acked-by: default avatarJason Wang <jasowang@redhat.com>
Link: https://patch.msgid.link/20250630144212.48471-2-minhquangbui99@gmail.com


Signed-off-by: default avatarPaolo Abeni <pabeni@redhat.com>
parent bd475eea
Loading
Loading
Loading
Loading
+34 −4
Original line number Diff line number Diff line
@@ -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);