[PATCH net v2 1/2] virtio_net: fix AF_XDP pool disable after queue shrink

Xiong Weimin <[email protected]>
Newsgroups gmane.linux.kernel.virtualization,gmane.linux.network,gmane.linux.kernel
Message-ID <[email protected]>
xp_clear_dev() calls the driver to unbind an AF_XDP pool, then frees
the pool.  virtnet_xsk_pool_disable() currently rejects qid >=
curr_queue_pairs, so if XDP detach (or another path) has already shrunk
curr_queue_pairs below a still-bound qid, disable returns -EINVAL.
The core only WARNs, clears the netdev pool pointer, and destroys the
pool, which leaks the driver's DMA mappings and xsk_buffs and leaves
rq/sq->xsk_pool dangling for a use-after-free if those queues are
brought back later.

Use max_queue_pairs for the bounds check so cleanup can still run,
null the freed pointers, and avoid refill on inactive queues when
unbinding.  Also unmap the shared TX header with sq->vq on the enable
error path to match the mapping side (no functional change while
rq/sq share a DMA device).

Fixes: 09d2b3182c8e ("virtio_net: xsk: bind/unbind xsk for rx")
Signed-off-by: Xiong Weimin <[email protected]>
---
v2:
- rewrite as a real fix for disable-after-shrink (leak / UAF)
- drop the previous "check queue index before use" reorder patch
- fold sq->vq unmap symmetry here as NFC (no separate Fixes for that)

 drivers/net/virtio_net.c | 24 ++++++++++++++++++------
 1 file changed, 18 insertions(+), 6 deletions(-)

diff --git a/drivers/net/virtio_net.c b/drivers/net/virtio_net.c
index 3e2a587..d07ccef 100644
--- a/drivers/net/virtio_net.c
+++ b/drivers/net/virtio_net.c
@@ -5848,7 +5848,7 @@ static int virtnet_rq_bind_xsk_pool(struct virtnet_info *vi, struct receive_queu
 
 	rq->xsk_pool = pool;
 
-	virtnet_rx_resume(vi, rq, true);
+	virtnet_rx_resume(vi, rq, qindex < vi->curr_queue_pairs);
 
 	if (pool)
 		return 0;
@@ -5959,10 +5959,11 @@ err_sq:
 err_rq:
 	xsk_pool_dma_unmap(pool, 0);
 err_xsk_map:
-	virtqueue_unmap_single_attrs(rq->vq, hdr_dma, vi->hdr_len,
+	virtqueue_unmap_single_attrs(sq->vq, hdr_dma, vi->hdr_len,
 				     DMA_TO_DEVICE, 0);
 err_free_buffs:
 	kvfree(rq->xsk_buffs);
+	rq->xsk_buffs = NULL;
 	return err;
 }
 
@@ -5974,7 +5975,12 @@ static int virtnet_xsk_pool_disable(struct net_device *dev, u16 qid)
 	struct send_queue *sq;
 	int err;
 
-	if (qid >= vi->curr_queue_pairs)
+	/* rq/sq are sized by max_queue_pairs.  Allow cleanup even if
+	 * curr_queue_pairs has shrunk below qid (e.g. after XDP detach),
+	 * otherwise disable fails, leaks mappings/xsk_buffs, and leaves
+	 * dangling rq/sq->xsk_pool pointers to a soon-to-be-freed pool.
+	 */
+	if (qid >= vi->max_queue_pairs)
 		return -EINVAL;
 
 	sq = &vi->sq[qid];
@@ -5985,11 +5991,17 @@ static int virtnet_xsk_pool_disable(struct net_device *dev, u16 qid)
 	err = virtnet_rq_bind_xsk_pool(vi, rq, NULL);
 	err |= virtnet_sq_bind_xsk_pool(vi, sq, NULL);
 
-	xsk_pool_dma_unmap(pool, 0);
+	if (pool)
+		xsk_pool_dma_unmap(pool, 0);
+
+	if (sq->xsk_hdr_dma_addr) {
+		virtqueue_unmap_single_attrs(sq->vq, sq->xsk_hdr_dma_addr,
+					     vi->hdr_len, DMA_TO_DEVICE, 0);
+		sq->xsk_hdr_dma_addr = 0;
+	}
 
-	virtqueue_unmap_single_attrs(sq->vq, sq->xsk_hdr_dma_addr,
-				     vi->hdr_len, DMA_TO_DEVICE, 0);
 	kvfree(rq->xsk_buffs);
+	rq->xsk_buffs = NULL;
 
 	return err;
 }
-- 
2.43.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.