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

commit 315dbdd7cdf6aa533829774caaf4d25f1fd20e73 upstream.

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: 4941d472bf ("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: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
Bui Quang Minh
2025-06-30 21:42:10 +07:00
committed by Greg Kroah-Hartman
parent 39617dc3fa
commit 80b971be4c

View File

@@ -487,6 +487,26 @@ static unsigned int mergeable_ctx_to_truesize(void *mrg_ctx)
return (unsigned long)mrg_ctx & ((1 << MRG_CTX_HEADER_SHIFT) - 1); 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, static struct sk_buff *virtnet_build_skb(void *buf, unsigned int buflen,
unsigned int headroom, unsigned int headroom,
unsigned int len) unsigned int len)
@@ -1084,7 +1104,8 @@ static unsigned int virtnet_get_headroom(struct virtnet_info *vi)
* across multiple buffers (num_buf > 1), and we make sure buffers * across multiple buffers (num_buf > 1), and we make sure buffers
* have enough headroom. * 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, int *num_buf,
struct page *p, struct page *p,
int offset, int offset,
@@ -1104,18 +1125,27 @@ static struct page *xdp_linearize_page(struct receive_queue *rq,
memcpy(page_address(page) + page_off, page_address(p) + offset, *len); memcpy(page_address(page) + page_off, page_address(p) + offset, *len);
page_off += *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) { while (--*num_buf) {
unsigned int buflen; unsigned int buflen;
void *buf; void *buf;
void *ctx;
int off; int off;
buf = virtnet_rq_get_buf(rq, &buflen, NULL); buf = virtnet_rq_get_buf(rq, &buflen, &ctx);
if (unlikely(!buf)) if (unlikely(!buf))
goto err_buf; goto err_buf;
p = virt_to_head_page(buf); p = virt_to_head_page(buf);
off = buf - page_address(p); 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 /* guard against a misconfigured or uncooperative backend that
* is sending packet larger than the MTU. * is sending packet larger than the MTU.
*/ */
@@ -1204,7 +1234,7 @@ static struct sk_buff *receive_small_xdp(struct net_device *dev,
headroom = vi->hdr_len + header_offset; headroom = vi->hdr_len + header_offset;
buflen = SKB_DATA_ALIGN(GOOD_PACKET_LEN + headroom) + buflen = SKB_DATA_ALIGN(GOOD_PACKET_LEN + headroom) +
SKB_DATA_ALIGN(sizeof(struct skb_shared_info)); 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, offset, header_offset,
&tlen); &tlen);
if (!xdp_page) if (!xdp_page)
@@ -1539,7 +1569,7 @@ static void *mergeable_xdp_get_buf(struct virtnet_info *vi,
*/ */
if (!xdp_prog->aux->xdp_has_frags) { if (!xdp_prog->aux->xdp_has_frags) {
/* linearize data for XDP */ /* linearize data for XDP */
xdp_page = xdp_linearize_page(rq, num_buf, xdp_page = xdp_linearize_page(vi->dev, rq, num_buf,
*page, offset, *page, offset,
VIRTIO_XDP_HEADROOM, VIRTIO_XDP_HEADROOM,
len); len);