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


Reply via email to