[PATCH net v3 1/1] rxrpc: fix encap_rcv skb accounting exhaustion

From: Zihan Xi

Date: Sat Sep 05 2026 - 04:19:00 EST


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 each skb when the I/O thread dequeues it so
UDP ownership does not follow the packet onto call or connection
queues. Clear sk_user_data under RCU protection and release the
socket only after the local queues are purged.

Fixes: 446b3e14525b ("rxrpc: Move packet reception processing into I/O thread")
Cc: stable@xxxxxxxxxxxxxxx
Reported-by: Vega <vega@xxxxxxxxxx>
Assisted-by: LLM
Signed-off-by: Zihan Xi <zihanx@xxxxxxxxxx>
---
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@xxxxxxxxxx/
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@xxxxxxxxxx/

net/rxrpc/io_thread.c | 17 +++++++++++++++--
net/rxrpc/local_object.c | 8 ++++++--
2 files changed, 21 insertions(+), 4 deletions(-)

diff --git a/net/rxrpc/io_thread.c b/net/rxrpc/io_thread.c
index dc5184a2fa9d1..8b77d137888ea 100644
--- a/net/rxrpc/io_thread.c
+++ b/net/rxrpc/io_thread.c
@@ -41,8 +41,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 +50,19 @@ int rxrpc_encap_rcv(struct sock *udp_sk, struct sk_buff *skb)
}
#endif

+ if (atomic_read(&udp_sk->sk_rmem_alloc) >= READ_ONCE(udp_sk->sk_rcvbuf) ||
+ !sk_rmem_schedule(udp_sk, skb, skb->truesize)) {
+ sk_drops_inc(udp_sk);
+ kfree_skb(skb);
+ return 0;
+ }
+
+ skb->dev = NULL;
+ skb_set_owner_r(skb, udp_sk);
+ 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 +482,8 @@ int rxrpc_io_thread(void *data)
/* Distribute packets and errors. */
while ((skb = __skb_dequeue(&rx_queue))) {
struct rxrpc_skb_priv *sp = rxrpc_skb(skb);
+
+ skb_orphan(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