AF_XDP lets user space create RX, TX, fill and completion rings via setsockopt(). xskq_create() validates that the requested number of entries is non-zero and a power of two, but the resulting ring size is not charged to any socket memory limit before vmalloc_user() is called. A process with CAP_NET_RAW in a network namespace can therefore request very large rings and pin a large amount of vmalloc memory through an AF_XDP socket. This can exhaust memory before bind or any packet I/O is required. Charge the page-aligned ring size to sk_omem_alloc and bound the charge by the namespace optmem limit. Use an atomic cmpxchg loop so concurrent ring allocations cannot race past the limit or overflow the signed sk_omem_alloc counter. Keep a socket reference with the queue, because UMEM fill and completion queues can be moved to the buffer pool and destroyed after socket release. Drop the socket reference to the pool and UMEM from xsk_release() before the final socket put, so pool destruction no longer depends on the socket destructor and the queue reference does not form a release cycle. Fixes: 423f38329d26 ("xsk: add umem fill queue support and mmap") Cc: stable@vger.kernel.org Reported-by: Vega Assisted-by: Codex:gpt-5.4 Signed-off-by: Zihan Xi --- net/xdp/xsk.c | 26 +++++++++++++++++++------- net/xdp/xsk_queue.c | 35 ++++++++++++++++++++++++++++++++++- net/xdp/xsk_queue.h | 7 +++++-- 3 files changed, 58 insertions(+), 10 deletions(-) diff --git a/net/xdp/xsk.c b/net/xdp/xsk.c index f906d51b6699..7d6df8b6b43d 100644 --- a/net/xdp/xsk.c +++ b/net/xdp/xsk.c @@ -1446,15 +1446,15 @@ static __poll_t xsk_poll(struct file *file, struct socket *sock, return mask; } -static int xsk_init_queue(u32 entries, struct xsk_queue **queue, - bool umem_queue) +static int xsk_init_queue(struct sock *sk, u32 entries, + struct xsk_queue **queue, bool umem_queue) { struct xsk_queue *q; if (entries == 0 || *queue || !is_power_of_2(entries)) return -EINVAL; - q = xskq_create(entries, umem_queue); + q = xskq_create(sk, entries, umem_queue); if (!q) return -ENOMEM; @@ -1498,6 +1498,18 @@ static struct xsk_map *xsk_get_map_list_entry(struct xdp_sock *xs, return map; } +static void xsk_put_pool_and_umem(struct xdp_sock *xs) +{ + struct xsk_buff_pool *pool = xs->pool; + struct xdp_umem *umem = xs->umem; + + xs->pool = NULL; + xs->umem = NULL; + + if (!xp_put_pool(pool)) + xdp_put_umem(umem, !pool); +} + static void xsk_delete_from_maps(struct xdp_sock *xs) { /* This function removes the current XDP socket from all the @@ -1553,6 +1565,7 @@ static int xsk_release(struct socket *sock) xskq_destroy(xs->tx); xskq_destroy(xs->fq_tmp); xskq_destroy(xs->cq_tmp); + xsk_put_pool_and_umem(xs); sock_orphan(sk); sock->sk = NULL; @@ -1810,7 +1823,7 @@ static int xsk_setsockopt(struct socket *sock, int level, int optname, return -EBUSY; } q = (optname == XDP_TX_RING) ? &xs->tx : &xs->rx; - err = xsk_init_queue(entries, q, false); + err = xsk_init_queue(sock->sk, entries, q, false); if (!err && optname == XDP_TX_RING) /* Tx needs to be explicitly woken up the first time */ xs->tx->ring->flags |= XDP_RING_NEED_WAKEUP; @@ -1879,7 +1892,7 @@ static int xsk_setsockopt(struct socket *sock, int level, int optname, q = (optname == XDP_UMEM_FILL_RING) ? &xs->fq_tmp : &xs->cq_tmp; - err = xsk_init_queue(entries, q, true); + err = xsk_init_queue(sock->sk, entries, q, true); mutex_unlock(&xs->mutex); return err; } @@ -2156,8 +2169,7 @@ static void xsk_destruct(struct sock *sk) if (!sock_flag(sk, SOCK_DEAD)) return; - if (!xp_put_pool(xs->pool)) - xdp_put_umem(xs->umem, !xs->pool); + xsk_put_pool_and_umem(xs); } static int xsk_create(struct net *net, struct socket *sock, int protocol, diff --git a/net/xdp/xsk_queue.c b/net/xdp/xsk_queue.c index 4dd01b7d858e..edc2ba97cc0f 100644 --- a/net/xdp/xsk_queue.c +++ b/net/xdp/xsk_queue.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include "xsk_queue.h" @@ -21,7 +22,25 @@ static size_t xskq_get_ring_size(struct xsk_queue *q, bool umem_queue) return struct_size(rxtx_ring, desc, q->nentries); } -struct xsk_queue *xskq_create(u32 nentries, bool umem_queue) +static bool xskq_charge(struct sock *sk, size_t size) +{ + int optmem_max = READ_ONCE(sock_net(sk)->core.sysctl_optmem_max); + int old, new; + + if (optmem_max <= 0 || size > optmem_max) + return false; + + do { + old = atomic_read(&sk->sk_omem_alloc); + if (old < 0 || old > optmem_max - (int)size) + return false; + new = old + (int)size; + } while (atomic_cmpxchg(&sk->sk_omem_alloc, old, new) != old); + + return true; +} + +struct xsk_queue *xskq_create(struct sock *sk, u32 nentries, bool umem_queue) { struct xsk_queue *q; size_t size; @@ -45,9 +64,18 @@ struct xsk_queue *xskq_create(u32 nentries, bool umem_queue) } size = PAGE_ALIGN(size); + if (!xskq_charge(sk, size)) { + kfree(q); + return NULL; + } + + sock_hold(sk); + q->sk = sk; q->ring = vmalloc_user(size); if (!q->ring) { + atomic_sub((int)size, &sk->sk_omem_alloc); + sock_put(sk); kfree(q); return NULL; } @@ -61,6 +89,11 @@ void xskq_destroy(struct xsk_queue *q) if (!q) return; + if (q->sk) { + atomic_sub((int)q->ring_vmalloc_size, &q->sk->sk_omem_alloc); + sock_put(q->sk); + } + vfree(q->ring); kfree(q); } diff --git a/net/xdp/xsk_queue.h b/net/xdp/xsk_queue.h index 1bc42c8902f4..61744e4c1ca2 100644 --- a/net/xdp/xsk_queue.h +++ b/net/xdp/xsk_queue.h @@ -37,6 +37,8 @@ struct xdp_umem_ring { u64 desc[] ____cacheline_aligned_in_smp; }; +struct sock; + struct xsk_queue { u32 ring_mask; u32 nentries; @@ -46,6 +48,7 @@ struct xsk_queue { u64 invalid_descs; u64 queue_empty_descs; size_t ring_vmalloc_size; + struct sock *sk; /* Mutual exclusion of the completion ring in the SKB mode. * Protect: when sockets share a single cq when the same netdev * and queue id is shared. @@ -531,7 +534,7 @@ static inline u64 xskq_nb_queue_empty_descs(struct xsk_queue *q) return q ? q->queue_empty_descs : 0; } -struct xsk_queue *xskq_create(u32 nentries, bool umem_queue); -void xskq_destroy(struct xsk_queue *q_ops); +struct xsk_queue *xskq_create(struct sock *sk, u32 nentries, bool umem_queue); +void xskq_destroy(struct xsk_queue *q); #endif /* _LINUX_XSK_QUEUE_H */ -- 2.43.0