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 Assisted-by: LLM Co-developed-by: Luxing Yin Signed-off-by: Luxing Yin Signed-off-by: Zihan Xi --- 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/ 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/ 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/ 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