[PATCH net v4 1/1] rxrpc: fix encap_rcv skb accounting exhaustion
From: Zihan Xi <hidden>
Date: 2026-09-09 07:45:10
Also in:
lkml, stable
Subsystem:
networking [general], rxrpc sockets (af_rxrpc), the rest · Maintainers:
"David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, David Howells, Marc Dionne, Linus Torvalds
rxrpc_encap_rcv() moves encapsulated UDP packets onto the local
RxRPC queue without preserving UDP receive-buffer accounting. A local
AF_RXRPC service such as the AFS callback listener can therefore be
flooded with RxRPC-shaped UDP packets until the local queue grows
without bound and consumes large amounts of memory.
Reaccount encapsulated packets against the UDP socket before queueing
them on the RxRPC local queue and drop packets once the socket rcvbuf
limit is reached. Orphan PACKET skbs when the I/O thread dequeues
them so UDP ownership does not follow those packets onto call or
connection queues. Error-queue skbs keep their existing destructor.
Clear sk_user_data under RCU protection and release the socket only
after the local queues are purged.
sk_forward_alloc is not atomic. UDP already serialises it with
sk->sk_receive_queue.lock; take that lock around the charge in
rxrpc_encap_rcv() and around skb_orphan() in the I/O thread. The I/O
thread uses spin_lock_bh() so a concurrent BH encap_rcv() cannot
update the same counter. The skbs stay on the RxRPC local queue, not
the UDP receive queue.
Fixes: 446b3e14525b ("rxrpc: Move packet reception processing into I/O thread")
Cc: stable@vger.kernel.org
Reported-by: Vega <redacted>
Assisted-by: LLM
Co-developed-by: Luxing Yin <redacted>
Signed-off-by: Luxing Yin <redacted>
Signed-off-by: Zihan Xi <redacted>
---
changes in v4:
- serialise UDP rmem charge/uncharge with sk->sk_receive_queue.lock
- use spin_lock() in encap_rcv() (BH) and spin_lock_bh() around
skb_orphan() in the I/O thread
- do not enqueue encapsulated skbs on the UDP receive queue
- orphan only PACKET skbs charged in encap_rcv(); leave error-queue
skb ownership alone
- restore the unshare -Urn reproducer and document the AFS callback
listener
- add the privileged pin/FIFO hog commands used to record the panic
- clarify the recorded panic is a downstream OOM after
privileged pin/FIFO hog, not the unshare-only flood
- include the full OOM Mem-Info in the cover crash log
- v3 Link: https://lore.kernel.org/all/cover.1788539302.git.zihanx@nebusec.ai/ (local)
changes in v3:
- orphan the skb when the I/O thread dequeues it from the local
queue so UDP rmem ownership does not follow packets onto
call/conn queues
- mention both io_thread.c and local_object.c in the cover opening
- do not describe the recorded panic as a complete non-root-only
reproducer; the flood is unprivileged but I/O-thread starvation
used privileged steps
- attribute the OOM to skbuff growth rather than incoming-call
setup
- describe the recorded panic as a downstream OOM in
rxrpc_reject_packet()/sock_alloc_send_pskb after I/O-thread
contention, not as an allocation at the encap_rcv enqueue site
- note that cgroup.freeze does not stop krxrpcio; the crash still
shows that kthread allocating, and the CPU pin plus SCHED_FIFO
hog are the steps that slowed it
- v2 Link: https://lore.kernel.org/all/cover.1785339953.git.zihanx@nebusec.ai/ (local)
changes in v2:
- switch the drop path from atomic_inc(&udp_sk->sk_drops) to
sk_drops_inc(udp_sk)
- retarget Fixes to 446b3e14525b, the first boundary where encap_rcv
queued the skb onto local->rx_queue for later I/O-thread consumption
- rebase onto current net/main
- refresh the cover crash log from an unfixed 7.3.0-rc1+ net/main run
and include the decoded stack
- explain in the cover why packetdrill was not used
- document the actual local flood command instead of a generic
unshare invocation
- v1 Link: https://lore.kernel.org/all/cover.1784742007.git.zihanx@nebusec.ai/ (local)
net/rxrpc/io_thread.c | 37 +++++++++++++++++++++++++++++++++++--
net/rxrpc/local_object.c | 8 ++++++--
2 files changed, 41 insertions(+), 4 deletions(-)
diff --git a/net/rxrpc/io_thread.c b/net/rxrpc/io_thread.c
index dc5184a2fa9d1..415b05f5e2b47 100644
--- a/net/rxrpc/io_thread.c
+++ b/net/rxrpc/io_thread.c@@ -13,6 +13,22 @@ static int rxrpc_input_packet_on_conn(struct rxrpc_connection *conn, struct sockaddr_rxrpc *peer_srx, struct sk_buff *skb); +/* + * Drop UDP rmem ownership for packets charged in encap_rcv(). + * sk_forward_alloc is serialised by sk_receive_queue.lock. + */ +static void rxrpc_skb_orphan_udp(struct sk_buff *skb) +{ + struct sock *sk = skb->sk; + + if (!sk) + return; + + spin_lock_bh(&sk->sk_receive_queue.lock); + skb_orphan(skb); + spin_unlock_bh(&sk->sk_receive_queue.lock); +} + /* * handle data received on the local endpoint * - may be called in interrupt context
@@ -41,8 +57,6 @@ int rxrpc_encap_rcv(struct sock *udp_sk, struct sk_buff *skb) if (skb->tstamp == 0) skb->tstamp = ktime_get_real(); - skb->mark = RXRPC_SKB_MARK_PACKET; - rxrpc_new_skb(skb, rxrpc_skb_new_encap_rcv); rx_queue = &local->rx_queue; #ifdef CONFIG_AF_RXRPC_INJECT_RX_DELAY if (rxrpc_inject_rx_delay ||
@@ -52,6 +66,22 @@ int rxrpc_encap_rcv(struct sock *udp_sk, struct sk_buff *skb) } #endif + spin_lock(&udp_sk->sk_receive_queue.lock); + if (atomic_read(&udp_sk->sk_rmem_alloc) >= READ_ONCE(udp_sk->sk_rcvbuf) || + !sk_rmem_schedule(udp_sk, skb, skb->truesize)) { + spin_unlock(&udp_sk->sk_receive_queue.lock); + sk_drops_inc(udp_sk); + kfree_skb(skb); + return 0; + } + + skb->dev = NULL; + skb_set_owner_r(skb, udp_sk); + spin_unlock(&udp_sk->sk_receive_queue.lock); + skb_dst_force(skb); + + skb->mark = RXRPC_SKB_MARK_PACKET; + rxrpc_new_skb(skb, rxrpc_skb_new_encap_rcv); skb_queue_tail(rx_queue, skb); wake_up_process(io_thread); return 0;
@@ -471,6 +501,9 @@ int rxrpc_io_thread(void *data) /* Distribute packets and errors. */ while ((skb = __skb_dequeue(&rx_queue))) { struct rxrpc_skb_priv *sp = rxrpc_skb(skb); + + if (skb->mark == RXRPC_SKB_MARK_PACKET) + rxrpc_skb_orphan_udp(skb); switch (skb->mark) { case RXRPC_SKB_MARK_PACKET: skb->priority = 0;
diff --git a/net/rxrpc/local_object.c b/net/rxrpc/local_object.c
index 169f9dfdaa77f..6604f9f952660 100644
--- a/net/rxrpc/local_object.c
+++ b/net/rxrpc/local_object.c@@ -437,8 +437,8 @@ void rxrpc_destroy_local(struct rxrpc_local *local) if (socket) { local->socket = NULL; kernel_sock_shutdown(socket, SHUT_RDWR); - socket->sk->sk_user_data = NULL; - sock_release(socket); + rcu_assign_sk_user_data(socket->sk, NULL); + synchronize_rcu(); } /* At this point, there should be no more packets coming in to the
@@ -448,6 +448,10 @@ void rxrpc_destroy_local(struct rxrpc_local *local) rxrpc_purge_queue(&local->rx_delay_queue); #endif rxrpc_purge_queue(&local->rx_queue); + + if (socket) + sock_release(socket); + rxrpc_purge_client_connections(local); page_frag_cache_drain(&local->tx_alloc); }
--
2.43.0