Thread (3 messages) read the whole thread 3 messages, 2 authors, 4d ago

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

From: Simon Horman <horms@kernel.org>
Date: 2026-07-28 12:26:03

This is an AI-generated review of your patch. The human sending this
email has considered the AI review valid, or at least plausible.
Full review at: https://netdev-ai.bots.linux.dev/sashiko/#/patchset/b640f1b3e27afeb9f397b2bab9ce23f4f2f864a7.1784742007.git.zihanx@nebusec.ai?part=1
---
rxrpc: fix encap_rcv skb accounting exhaustion

This patch adds UDP rcvbuf accounting to rxrpc_encap_rcv() so that an
AF_RXRPC service such as the AFS callback listener cannot be flooded
until local queue memory grows unbounded. It also switches
rxrpc_destroy_local() to clear sk_user_data via RCU and defers
sock_release() until after the local rx queues are purged.
quoted hunk ↗ jump to hunk
diff --git a/net/rxrpc/io_thread.c b/net/rxrpc/io_thread.c
index dc5184a2fa9d..85411a2d8428 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)) {
+		atomic_inc(&udp_sk->sk_drops);
+		kfree_skb(skb);
[Low]
Should the drop reason here be SKB_DROP_REASON_SOCKET_RCVBUFF rather
than the default SKB_DROP_REASON_NOT_SPECIFIED that kfree_skb() maps
to?

The regular UDP receive path in __udp_enqueue_schedule_skb() uses
kfree_skb_reason(skb, SKB_DROP_REASON_SOCKET_RCVBUFF) so that
drop_monitor / drop-reason tracepoints classify rcvbuf-full drops. As
written, rxrpc's rcvbuf-full drops will show up as unspecified.
+		return 0;
+	}
+
+	skb->dev = NULL;
+	skb_set_owner_r(skb, udp_sk);
[Critical]
Once skb_set_owner_r(skb, udp_sk) installs sock_rfree() as the
destructor, skb->sk / skb->destructor are bound to udp_sk for the
lifetime of the skb.

The io_thread then hands these skbs off to further queues without
re-orphaning, for example:

net/rxrpc/ar-internal.h:rxrpc_queue_rx_call_packet()
    __skb_queue_tail(&call->rx_queue, skb);

net/rxrpc/conn_event.c:rxrpc_post_packet_to_conn()
    skb_queue_tail(&conn->rx_queue, skb);

net/rxrpc/oob.c:rxrpc_notify_socket_oob()
    skb_queue_tail(&rx->recvmsg_oobq, skb);

None of these paths call skb_orphan() or re-run skb_set_owner_r() with
a different sk, so skb->sk stays pointing at udp_sk and the destructor
stays sock_rfree.

rxrpc_destroy_local() only purges local->rx_queue and
local->rx_delay_queue before sock_release(socket):

    rxrpc_purge_queue(&local->rx_delay_queue);
    rxrpc_purge_queue(&local->rx_queue);

    if (socket)
        sock_release(socket);

Meanwhile rxrpc_put_connection() can defer rxrpc_clean_up_connection()
to a workqueue when called from softirq or when the processor work /
timer is busy, and rxrpc_destroy_local() does not flush_work() /
cancel_work_sync() those destructor workers.

If the deferred cleanup runs after sock_release() has freed udp_sk,
rxrpc_clean_up_connection() -> rxrpc_purge_queue(&conn->rx_queue) will
invoke sock_rfree() on each skb, which does atomic_sub(len,
&sk->sk_rmem_alloc) and sk_mem_uncharge() on freed memory.

Can this produce a remotely triggerable use-after-free on the UDP
sock, since any peer can drive rxrpc_post_packet_to_conn() or
rxrpc_queue_rx_call_packet()?

[High]
Is any lock held on udp_sk when sk_rmem_schedule() and
skb_set_owner_r() run here?

Both paths do a non-atomic RMW on sk_forward_alloc via
sk_forward_alloc_add(), which is only WRITE_ONCE(sk->sk_forward_alloc,
sk->sk_forward_alloc + val).

The comment above rxrpc_encap_rcv() states the socket is not held
locked by the caller and this runs under RCU only. The regular UDP
path in __udp_enqueue_schedule_skb() takes spin_lock(&list->lock) on
sk_receive_queue before udp_rmem_schedule() and sk_forward_alloc_add()
precisely to serialise this counter.

With concurrent softirqs on multiple CPUs delivering to the same UDP
socket, and sock_rfree() destructors firing asynchronously from the
io_thread purge and deferred conn/call cleanup, can updates to
sk_forward_alloc be lost, allowing the rcvbuf ceiling this patch is
meant to enforce to drift?

[Medium]
Is the sequence

    if (atomic_read(&udp_sk->sk_rmem_alloc) >= READ_ONCE(udp_sk->sk_rcvbuf) ||
        !sk_rmem_schedule(udp_sk, skb, skb->truesize)) {
        ...
    }

    skb_set_owner_r(skb, udp_sk);

racy across CPUs?

N concurrent softirqs can each observe rmem_alloc < rcvbuf and then
each add skb->truesize in skb_set_owner_r() -> atomic_add(...,
&sk->sk_rmem_alloc), so the effective cap becomes rcvbuf + O(NCPU *
truesize) rather than a strict bound. The commit message states the
patch will "drop packets once the socket rcvbuf limit is reached" —
should that be qualified given this overshoot?
+	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;
[Low]
rxrpc's UDP endpoint is opened by kernel code via udp_sock_create() in
rxrpc_open_socket() and is not exposed to userspace, so there is no
SO_RCVBUF path to tune sk_rcvbuf per endpoint. After this change,
net.core.rmem_default (typically ~200 KB) becomes the effective drop
threshold for all AF_RXRPC / AFS traffic on the host, with no
per-endpoint or rxrpc-specific knob to raise it.

Was this tradeoff considered, and should the commit message note it?
On busy AFS deployments this could drop legitimate traffic that is
only tunable via a global sysctl affecting every socket on the system.
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help