* [PATCH v2] rxrpc: Fix use-after-free in rxrpc_destroy_all_peers()
@ 2026-09-07 22:21 Marco Elver
2026-09-08 8:11 ` [syzbot ci] " syzbot ci
2026-09-10 13:21 ` [PATCH v2] " netdev-bot+sashiko
0 siblings, 2 replies; 3+ messages in thread
From: Marco Elver @ 2026-09-07 22:21 UTC (permalink / raw)
To: elver
Cc: David Howells, Marc Dionne, David S. Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, linux-afs, netdev,
linux-kernel, kasan-dev, syzkaller-bugs,
syzbot+c876adfab6362679008c
During network namespace teardown, rxrpc_destroy_all_peers() iterates over
the rxnet->peer_hash table to print leaked peers without holding
rxnet->peer_hash_lock. If concurrent activity or asynchronous teardown
drops the last reference to a peer, __rxrpc_put_peer() unlinks the peer
from rxnet->peer_hash and frees it via kfree_rcu(). Without locking,
traversal can follow an unlinked peer or race with RCU reclamation,
triggering a KASAN slab-use-after-free.
BUG: KASAN: slab-use-after-free in rxrpc_destroy_all_peers+0xcc/0x150
net/rxrpc/peer_object.c:461
Read of size 8 at addr ffff88811089e420 by task kworker/u8:1/13
Call Trace:
<TASK>
rxrpc_destroy_all_peers+0xcc/0x150 net/rxrpc/peer_object.c:461
rxrpc_exit_net+0x7f/0xc0 net/rxrpc/net_ns.c:114
ops_exit_list net/core/net_namespace.c:199 [inline]
ops_undo_list+0x43d/0x8d0 net/core/net_namespace.c:252
cleanup_net+0x572/0x810 net/core/net_namespace.c:702
process_one_work kernel/workqueue.c:3322 [inline]
process_scheduled_works+0xa8e/0x14e0 kernel/workqueue.c:3405
worker_thread+0x92d/0xe10 kernel/workqueue.c:3486
kthread+0x388/0x470 kernel/kthread.c:436
ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
</TASK>
To fix the slab-use-after-free, acquire rxnet->peer_hash_lock with
spin_lock_bh() while iterating over rxnet->peer_hash in
rxrpc_destroy_all_peers().
Additionally, split netns teardown by implementing .pre_exit in
rxrpc_net_ops to clean up calls, connections, and keepalive work. This
ensures in-flight references are released before cleanup_net() executes
its intermediate synchronize_rcu(), so that peer and local endpoint leak
checks in rxrpc_exit_net() run only after deferred RCU releases complete,
avoiding spurious leak warnings.
Fixes: 17226f124038 ("rxrpc: Fix leak of rxrpc_peer objects")
Reported-by: syzbot+c876adfab6362679008c@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=c876adfab6362679008c
Link: https://syzkaller.appspot.com/ai_job?id=7fecbeb2-cd9b-4ca2-8149-48663e20b153
Signed-off-by: Marco Elver <elver@google.com>
---
v2:
* Split teardown and use .pre_exit instead of rcu_barrier() (Eric).
---
net/rxrpc/net_ns.c | 23 +++++++++++++++--------
net/rxrpc/peer_object.c | 8 ++++++++
2 files changed, 23 insertions(+), 8 deletions(-)
diff --git a/net/rxrpc/net_ns.c b/net/rxrpc/net_ns.c
index 9a9834145e81..169116735366 100644
--- a/net/rxrpc/net_ns.c
+++ b/net/rxrpc/net_ns.c
@@ -97,10 +97,7 @@ static __net_init int rxrpc_init_net(struct net *net)
return ret;
}
-/*
- * Clean up a per-network namespace record.
- */
-static __net_exit void rxrpc_exit_net(struct net *net)
+static __net_exit void rxrpc_pre_exit_net(struct net *net)
{
struct rxrpc_net *rxnet = rxrpc_net(net);
@@ -111,14 +108,24 @@ static __net_exit void rxrpc_exit_net(struct net *net)
timer_delete_sync(&rxnet->peer_keepalive_timer);
rxrpc_destroy_all_calls(rxnet);
rxrpc_destroy_all_connections(rxnet);
+}
+
+/*
+ * Clean up a per-network namespace record.
+ */
+static __net_exit void rxrpc_exit_net(struct net *net)
+{
+ struct rxrpc_net *rxnet = rxrpc_net(net);
+
rxrpc_destroy_all_peers(rxnet);
rxrpc_destroy_all_locals(rxnet);
proc_remove(rxnet->proc_net);
}
struct pernet_operations rxrpc_net_ops = {
- .init = rxrpc_init_net,
- .exit = rxrpc_exit_net,
- .id = &rxrpc_net_id,
- .size = sizeof(struct rxrpc_net),
+ .init = rxrpc_init_net,
+ .pre_exit = rxrpc_pre_exit_net,
+ .exit = rxrpc_exit_net,
+ .id = &rxrpc_net_id,
+ .size = sizeof(struct rxrpc_net),
};
diff --git a/net/rxrpc/peer_object.c b/net/rxrpc/peer_object.c
index fa9a406e1168..faa2983638b2 100644
--- a/net/rxrpc/peer_object.c
+++ b/net/rxrpc/peer_object.c
@@ -454,6 +454,12 @@ void rxrpc_destroy_all_peers(struct rxrpc_net *rxnet)
struct rxrpc_peer *peer;
int i;
+ /*
+ * Prevent use-after-free if a peer is concurrently unlinked from the
+ * hash table and freed via RCU during iteration.
+ */
+ spin_lock_bh(&rxnet->peer_hash_lock);
+
for (i = 0; i < HASH_SIZE(rxnet->peer_hash); i++) {
if (hlist_empty(&rxnet->peer_hash[i]))
continue;
@@ -465,6 +471,8 @@ void rxrpc_destroy_all_peers(struct rxrpc_net *rxnet)
&peer->srx.transport);
}
}
+
+ spin_unlock_bh(&rxnet->peer_hash_lock);
}
/**
--
2.55.0.1003.g10538fe699-goog
^ permalink raw reply [flat|nested] 3+ messages in thread
* [syzbot ci] Re: rxrpc: Fix use-after-free in rxrpc_destroy_all_peers()
2026-09-07 22:21 [PATCH v2] rxrpc: Fix use-after-free in rxrpc_destroy_all_peers() Marco Elver
@ 2026-09-08 8:11 ` syzbot ci
2026-09-10 13:21 ` [PATCH v2] " netdev-bot+sashiko
1 sibling, 0 replies; 3+ messages in thread
From: syzbot ci @ 2026-09-08 8:11 UTC (permalink / raw)
To: davem, dhowells, edumazet, elver, horms, kasan-dev, kuba,
linux-afs, linux-kernel, marc.dionne, netdev, pabeni, syzbot,
syzkaller-bugs
Cc: syzbot, syzkaller-bugs
syzbot ci has tested the following series
[v2] rxrpc: Fix use-after-free in rxrpc_destroy_all_peers()
https://lore.kernel.org/all/20260907222242.3361174-2-elver@google.com
* [PATCH v2] rxrpc: Fix use-after-free in rxrpc_destroy_all_peers()
and found the following issue:
INFO: task hung in rxrpc_destroy_all_calls
Full report is available here:
https://ci.syzbot.org/series/72f7714d-3fb8-4e04-aa6f-2891301ed056
***
INFO: task hung in rxrpc_destroy_all_calls
tree: net-next
URL: https://kernel.googlesource.com/pub/scm/linux/kernel/git/netdev/net-next.git
base: 31f961de2f90fbf52eb2d4e15b3eeaa09f9b4fc2
arch: amd64
compiler: Debian clang version 22.1.8 (++20260613092233+e80beda6e255-1~exp1~20260613092250.77), Debian LLD 22.1.8
config: https://ci.syzbot.org/builds/6dafa5c2-5fb1-49b9-b4e5-8877f26c5afd/config
syz repro: https://ci.syzbot.org/findings/4eb5795c-f73d-43ee-b3df-3291d8644727/syz_repro
INFO: task kworker/u8:2:5453 blocked for more than 143 seconds.
Not tainted syzkaller #0
"echo 0 > /proc/sys/kernel/hung_task_timeout_secs" disables this message.
task:kworker/u8:2 state:D stack:26848 pid:5453 tgid:5453 ppid:2 task_flags:0x4208060 flags:0x00080000
Workqueue: netns cleanup_net
Call Trace:
<TASK>
context_switch kernel/sched/core.c:5520 [inline]
__schedule+0x17db/0x58f0 kernel/sched/core.c:7270
__schedule_loop kernel/sched/core.c:7347 [inline]
schedule+0x164/0x2b0 kernel/sched/core.c:7362
rxrpc_destroy_all_calls+0x43d/0x560 net/rxrpc/call_object.c:756
rxrpc_pre_exit_net+0x6f/0x90 net/rxrpc/net_ns.c:109
ops_pre_exit_list net/core/net_namespace.c:161 [inline]
ops_undo_list+0x17d/0x8d0 net/core/net_namespace.c:235
cleanup_net+0x572/0x810 net/core/net_namespace.c:706
process_one_work kernel/workqueue.c:3396 [inline]
process_scheduled_works+0xc3d/0x1630 kernel/workqueue.c:3479
worker_thread+0xa47/0xfb0 kernel/workqueue.c:3560
kthread+0x38b/0x480 kernel/kthread.c:436
ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
</TASK>
Showing all locks held in the system:
locks held by kworker/u8:1/13: 4, last CPU#0:
#0: ffff888177a23940 ((wq_completion)bat_events){+.+.}-{0:0}, at: rcu_lock_acquire include/linux/rcupdate.h:309 [inline]
#0: ffff888177a23940 ((wq_completion)bat_events){+.+.}-{0:0}, at: rcu_read_lock include/linux/rcupdate.h:849 [inline]
#0: ffff888177a23940 ((wq_completion)bat_events){+.+.}-{0:0}, at: process_one_work kernel/workqueue.c:3361 [inline]
#0: ffff888177a23940 ((wq_completion)bat_events){+.+.}-{0:0}, at: process_scheduled_works+0x97a/0x1630 kernel/workqueue.c:3479
#1: ffff88823c624408 (psi_seq){-.-.}-{0:0}, at: psi_task_switch+0x57/0x7d0 kernel/sched/psi.c:933
#2: ffff88823c626118 (&base->lock){-.-.}-{2:2}, at: __mod_timer+0x904/0xed0 kernel/time/timer.c:1117
#3: ffffffff9abf4d30 (&obj_hash[i].lock){-.-.}-{2:2}, at: debug_object_activate+0xb7/0x420 lib/debugobjects.c:873
locks held by pr/ttyS0/16: 2, last CPU#0:
#0: ffffffff8ec362b8 (console_srcu){....}-{0:0}, at: rcu_try_lock_acquire include/linux/rcupdate.h:314 [inline]
#0: ffffffff8ec362b8 (console_srcu){....}-{0:0}, at: srcu_read_lock_nmisafe include/linux/srcu.h:439 [inline]
#0: ffffffff8ec362b8 (console_srcu){....}-{0:0}, at: console_srcu_read_lock+0x30/0x60 kernel/printk/printk.c:291
#1: ffffffff9ad32098 (&port_lock_key){-.-.}-{3:3}, at: __uart_port_lock_irqsave include/linux/serial_core.h:613 [inline]
#1: ffffffff9ad32098 (&port_lock_key){-.-.}-{3:3}, at: univ8250_console_device_lock+0x67/0xc0 drivers/tty/serial/8250/8250_core.c:413
locks held by kworker/1:0/25: 3, on CPU#1:
#0: ffff88810006b140 ((wq_completion)events){+.+.}-{0:0}, at: rcu_lock_acquire include/linux/rcupdate.h:309 [inline]
#0: ffff88810006b140 ((wq_completion)events){+.+.}-{0:0}, at: rcu_read_lock include/linux/rcupdate.h:849 [inline]
#0: ffff88810006b140 ((wq_completion)events){+.+.}-{0:0}, at: process_one_work kernel/workqueue.c:3361 [inline]
#0: ffff88810006b140 ((wq_completion)events){+.+.}-{0:0}, at: process_scheduled_works+0x97a/0x1630 kernel/workqueue.c:3479
#1: ffffc900001f7c40 ((work_completion)(&data->fib_event_work)){+.+.}-{0:0}, at: rcu_lock_acquire include/linux/rcupdate.h:309 [inline]
#1: ffffc900001f7c40 ((work_completion)(&data->fib_event_work)){+.+.}-{0:0}, at: rcu_read_lock include/linux/rcupdate.h:849 [inline]
#1: ffffc900001f7c40 ((work_completion)(&data->fib_event_work)){+.+.}-{0:0}, at: process_one_work kernel/workqueue.c:3361 [inline]
#1: ffffc900001f7c40 ((work_completion)(&data->fib_event_work)){+.+.}-{0:0}, at: process_scheduled_works+0x97a/0x1630 kernel/workqueue.c:3479
#2: ffff88819efed250 (&data->fib_lock){+.+.}-{4:4}, at: nsim_fib_event_work+0x1fd/0x3b0 drivers/net/netdevsim/fib.c:1490
locks held by khungtaskd/35: 1, last CPU#1:
#0: ffffffff8ed5c6e0 (rcu_read_lock){....}-{1:3}, at: rcu_lock_acquire include/linux/rcupdate.h:309 [inline]
#0: ffffffff8ed5c6e0 (rcu_read_lock){....}-{1:3}, at: rcu_read_lock include/linux/rcupdate.h:849 [inline]
#0: ffffffff8ed5c6e0 (rcu_read_lock){....}-{1:3}, at: debug_show_all_locks+0x2e/0x180 kernel/locking/lockdep.c:6837
locks held by getty/5425: 2, on CPU#0:
#0: ffff8881001f80a0 (&tty->ldisc_sem){++++}-{0:0}, at: tty_ldisc_ref_wait+0x25/0x70 drivers/tty/tty_ldisc.c:243
#1: ffffc900034762e8 (&ldata->atomic_read_lock){+.+.}-{4:4}, at: n_tty_read+0x45a/0x1360 drivers/tty/n_tty.c:2211
locks held by kworker/u8:2/5453: 3, on CPU#1:
#0: ffff8881012d5940 ((wq_completion)netns){+.+.}-{0:0}, at: rcu_lock_acquire include/linux/rcupdate.h:309 [inline]
#0: ffff8881012d5940 ((wq_completion)netns){+.+.}-{0:0}, at: rcu_read_lock include/linux/rcupdate.h:849 [inline]
#0: ffff8881012d5940 ((wq_completion)netns){+.+.}-{0:0}, at: process_one_work kernel/workqueue.c:3361 [inline]
#0: ffff8881012d5940 ((wq_completion)netns){+.+.}-{0:0}, at: process_scheduled_works+0x97a/0x1630 kernel/workqueue.c:3479
#1: ffffc90003edfc40 (net_cleanup_work){+.+.}-{0:0}, at: rcu_lock_acquire include/linux/rcupdate.h:309 [inline]
#1: ffffc90003edfc40 (net_cleanup_work){+.+.}-{0:0}, at: rcu_read_lock include/linux/rcupdate.h:849 [inline]
#1: ffffc90003edfc40 (net_cleanup_work){+.+.}-{0:0}, at: process_one_work kernel/workqueue.c:3361 [inline]
#1: ffffc90003edfc40 (net_cleanup_work){+.+.}-{0:0}, at: process_scheduled_works+0x97a/0x1630 kernel/workqueue.c:3479
#2: ffffffff90242108 (pernet_ops_rwsem){++++}-{4:4}, at: cleanup_net+0xf5/0x810 net/core/net_namespace.c:677
locks held by kworker/0:6/5822: 3, on CPU#0:
#0: ffff88810006b140 ((wq_completion)events){+.+.}-{0:0}, at: rcu_lock_acquire include/linux/rcupdate.h:309 [inline]
#0: ffff88810006b140 ((wq_completion)events){+.+.}-{0:0}, at: rcu_read_lock include/linux/rcupdate.h:849 [inline]
#0: ffff88810006b140 ((wq_completion)events){+.+.}-{0:0}, at: process_one_work kernel/workqueue.c:3361 [inline]
#0: ffff88810006b140 ((wq_completion)events){+.+.}-{0:0}, at: process_scheduled_works+0x97a/0x1630 kernel/workqueue.c:3479
#1: ffffc9000388fc40 (netdev_work){+.+.}-{0:0}, at: rcu_lock_acquire include/linux/rcupdate.h:309 [inline]
#1: ffffc9000388fc40 (netdev_work){+.+.}-{0:0}, at: rcu_read_lock include/linux/rcupdate.h:849 [inline]
#1: ffffc9000388fc40 (netdev_work){+.+.}-{0:0}, at: process_one_work kernel/workqueue.c:3361 [inline]
#1: ffffc9000388fc40 (netdev_work){+.+.}-{0:0}, at: process_scheduled_works+0x97a/0x1630 kernel/workqueue.c:3479
#2: ffffffff90250960 (rtnl_mutex){+.+.}-{4:4}, at: netdev_work_proc+0x6f/0x770 net/core/netdev_work.c:132
locks held by syz-executor/3546: 1, last CPU#0:
#0: ffffffff8ed5c6e0 (rcu_read_lock){....}-{1:3}, at: rcu_lock_acquire include/linux/rcupdate.h:309 [inline]
#0: ffffffff8ed5c6e0 (rcu_read_lock){....}-{1:3}, at: rcu_read_lock include/linux/rcupdate.h:849 [inline]
#0: ffffffff8ed5c6e0 (rcu_read_lock){....}-{1:3}, at: class_rcu_constructor include/linux/rcupdate.h:1216 [inline]
#0: ffffffff8ed5c6e0 (rcu_read_lock){....}-{1:3}, at: unwind_next_frame+0x8f/0x2550 arch/x86/kernel/unwind_orc.c:495
locks held by syz-executor/4305: 3, last CPU#0:
#0: ffffffff90250960 (rtnl_mutex){+.+.}-{4:4}, at: rtnl_lock net/core/rtnetlink.c:80 [inline]
#0: ffffffff90250960 (rtnl_mutex){+.+.}-{4:4}, at: rtnl_nets_lock net/core/rtnetlink.c:366 [inline]
#0: ffffffff90250960 (rtnl_mutex){+.+.}-{4:4}, at: rtnl_newlink+0xc10/0x1c30 net/core/rtnetlink.c:4214
#1: ffff8881a15ace70 (&dev_instance_lock_key#23){+.+.}-{4:4}, at: netdev_lock include/linux/netdevice.h:2861 [inline]
#1: ffff8881a15ace70 (&dev_instance_lock_key#23){+.+.}-{4:4}, at: netdev_lock_ops include/net/netdev_lock.h:42 [inline]
#1: ffff8881a15ace70 (&dev_instance_lock_key#23){+.+.}-{4:4}, at: do_setlink+0x3d4/0x4670 net/core/rtnetlink.c:3176
#2: ffffffff90242048 (net_rwsem){++++}-{4:4}, at: wireless_nlevent_flush net/wireless/wext-core.c:350 [inline]
#2: ffffffff90242048 (net_rwsem){++++}-{4:4}, at: wext_netdev_notifier_call+0x28/0x110 net/wireless/wext-core.c:370
=============================================
NMI backtrace for cpu 1
CPU: 1 UID: 0 PID: 35 Comm: khungtaskd Not tainted syzkaller #0 PREEMPT(full)
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-debian-1.16.2-1 04/01/2014
Call Trace:
<TASK>
dump_stack_lvl+0xe8/0x150 lib/dump_stack.c:120
nmi_cpu_backtrace+0x274/0x2d0 lib/nmi_backtrace.c:123
nmi_trigger_cpumask_backtrace+0x17d/0x390 lib/nmi_backtrace.c:66
trigger_all_cpu_backtrace include/linux/nmi.h:164 [inline]
__sys_info lib/sys_info.c:157 [inline]
sys_info+0x135/0x170 lib/sys_info.c:165
check_hung_uninterruptible_tasks kernel/hung_task.c:353 [inline]
watchdog+0xfd7/0x1030 kernel/hung_task.c:561
kthread+0x38b/0x480 kernel/kthread.c:436
ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
</TASK>
Sending NMI from CPU 1 to CPUs 0:
NMI backtrace for cpu 0
CPU: 0 UID: 0 PID: 16 Comm: pr/ttyS0 Not tainted syzkaller #0 PREEMPT(full)
Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-debian-1.16.2-1 04/01/2014
RIP: 0010:io_serial_out+0x7c/0xc0 drivers/tty/serial/8250/8250_port.c:416
Code: b2 55 fc 44 89 f9 d3 e5 49 83 c6 40 4c 89 f0 48 c1 e8 03 42 80 3c 20 00 74 08 4c 89 f7 e8 cc 7b c5 fc 41 03 2e 89 d8 89 ea ee <5b> 41 5c 41 5e 41 5f 5d e9 c7 4f 75 06 cc 44 89 f9 80 e1 07 38 c1
RSP: 0018:ffffc90000157900 EFLAGS: 00000002
RAX: 000000000000005b RBX: 000000000000005b RCX: 0000000000000000
RDX: 00000000000003f8 RSI: 0000000000000000 RDI: 0000000000000020
RBP: 00000000000003f8 R08: 0000000000000003 R09: 0000000000000004
R10: dffffc0000000000 R11: ffffffff85720850 R12: dffffc0000000000
R13: 000000000000005b R14: ffffffff9ad320c0 R15: 0000000000000000
FS: 0000000000000000(0000) GS:ffff88818d6d9000(0000) knlGS:0000000000000000
CS: 0010 DS: 0000 ES: 0000 CR0: 0000000080050033
CR2: 00007fdb361ed158 CR3: 00000001a3b52000 CR4: 00000000000006f0
Call Trace:
<TASK>
serial_port_out include/linux/serial_core.h:817 [inline]
serial8250_console_putchar drivers/tty/serial/8250/8250_port.c:3287 [inline]
__serial8250_console_fifo_write+0x1e6/0x3e0 drivers/tty/serial/8250/8250_port.c:3359
serial8250_console_fifo_write drivers/tty/serial/8250/8250_port.c:3378 [inline]
__serial8250_console_write drivers/tty/serial/8250/8250_port.c:3429 [inline]
serial8250_console_write+0xaa1/0x1200 drivers/tty/serial/8250/8250_port.c:3493
nbcon_emit_next_record+0xef5/0x1a90 kernel/printk/nbcon.c:-1
nbcon_emit_one kernel/printk/nbcon.c:1157 [inline]
nbcon_kthread_func+0x679/0x880 kernel/printk/nbcon.c:1271
kthread+0x38b/0x480 kernel/kthread.c:436
ret_from_fork+0x514/0xb70 arch/x86/kernel/process.c:158
ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245
</TASK>
***
If these findings have caused you to resend the series or submit a
separate fix, please add the following tag to your commit message:
Tested-by: syzbot@syzkaller.appspotmail.com
---
This report is generated by a bot. It may contain errors.
syzbot ci engineers can be reached at syzkaller@googlegroups.com.
To test a fix for this bug, please reply with `#syz test`
(on a separate line) and attach the patch to the email.
Notes:
- The patch will be applied on top of the tested series (as an
incremental fix).
- To test a new version of the whole series, please send it directly
to syzbot@lists.linux.dev.
- Arguments like custom git repos and branches are not supported.
^ permalink raw reply [flat|nested] 3+ messages in thread
* Re: [PATCH v2] rxrpc: Fix use-after-free in rxrpc_destroy_all_peers()
2026-09-07 22:21 [PATCH v2] rxrpc: Fix use-after-free in rxrpc_destroy_all_peers() Marco Elver
2026-09-08 8:11 ` [syzbot ci] " syzbot ci
@ 2026-09-10 13:21 ` netdev-bot+sashiko
1 sibling, 0 replies; 3+ messages in thread
From: netdev-bot+sashiko @ 2026-09-10 13:21 UTC (permalink / raw)
To: elver
Cc: dhowells, marc.dionne, davem, edumazet, kuba, pabeni, horms,
linux-afs, netdev, linux-kernel, kasan-dev, syzkaller-bugs,
syzbot+c876adfab6362679008c
Thank you for your contribution! Sashiko AI review found 5 potential
issue(s) to consider:
Critical: 0 · High: 3 · Medium: 2 · Low: 0
- [High] Moving rxrpc_destroy_all_calls()/rxrpc_destroy_all_connections()
into the new .pre_exit hook deadlocks netns teardown whenever the kafs…
- [High] The patch hardens only the reader side of the peer hash.
- [High] The changelog's justification for the .pre_exit split is not
what the code does.
- [Medium] Ordering regression in the leak checks: rxrpc_exit_net() now
runs rxrpc_destroy_all_peers()/rxrpc_destroy_all_locals() with no…
- [Medium] rxrpc_destroy_all_peers() (net/rxrpc/peer_object.c:452-476)
now performs an unbounded pr_err() loop over all 1024 hash buckets and…
--
rxrpc: Fix use-after-free in rxrpc_destroy_all_peers()
This takes rxnet->peer_hash_lock with spin_lock_bh() around the leaked-peer
walk in rxrpc_destroy_all_peers() to fix a syzbot-reported KASAN
slab-use-after-free, and additionally splits netns teardown so that calls,
connections and the keepalive work are cleaned up from a new .pre_exit
handler while the peer and local endpoint leak checks stay in .exit.
Link: https://syzkaller.appspot.com/ai_job?id=7fecbeb2-cd9b-4ca2-8149-48663e20b153
A few questions below, mostly about the .pre_exit split rather than the
locking hunk.
First, about the changelog:
> Additionally, split netns teardown by implementing .pre_exit in
> rxrpc_net_ops to clean up calls, connections, and keepalive work. This
> ensures in-flight references are released before cleanup_net() executes
> its intermediate synchronize_rcu(), so that peer and local endpoint leak
> checks in rxrpc_exit_net() run only after deferred RCU releases complete,
> avoiding spurious leak warnings.
[Severity: High]
Is this description accurate?
The relative order of the four teardown steps is unchanged: before the
patch rxrpc_exit_net() already ran calls -> connections -> peers -> locals,
so moving the first two into .pre_exit does not reorder them with respect
to the leak checks.
The only new event inserted between them is the grace-period wait in
ops_undo_list():
net/core/net_namespace.c:ops_undo_list() {
...
if (expedite_rcu)
synchronize_rcu_expedited();
else
synchronize_rcu();
...
}
That waits for a grace period, not for already-queued call_rcu()/kfree_rcu()
callbacks to execute, which is what rcu_barrier() (the v1 approach) did.
Also, rxrpc itself does not release peer references from an RCU callback.
rxrpc_destroy_call() drops the peer synchronously before queueing the
callback:
net/rxrpc/call_object.c:rxrpc_destroy_call() {
...
rxrpc_put_peer(call->peer, rxrpc_peer_put_call);
rxrpc_put_local(call->local, rxrpc_local_put_call);
key_put(call->key);
call_rcu(&call->rcu, rxrpc_rcu_free_call);
}
and rxrpc_clean_up_connection() does the same before call_rcu(&conn->rcu,
...), both already covered by the pre-existing wait_var_event(nr_calls) and
wait_var_event(nr_conns).
The peer references that really are dropped from an RCU callback belong to
kafs (afs_free_addrlist() calling rxrpc_kernel_put_peer() for each address),
and those callbacks are queued from afs_net_exit(), i.e. after the
intermediate synchronize_rcu(), so the split cannot drain them either.
Spurious reports also still look possible: a peer whose refcount already
reached zero but which is blocked on peer_hash_lock inside
__rxrpc_put_peer() is still linked and gets printed as
"Leaked peer <id> {0}".
Could the changelog be reworded to describe what the split actually
achieves?
> diff --git a/net/rxrpc/net_ns.c b/net/rxrpc/net_ns.c
> index 9a9834145e81e..1691167353663 100644
> --- a/net/rxrpc/net_ns.c
> +++ b/net/rxrpc/net_ns.c
> @@ -97,10 +97,7 @@ static __net_init int rxrpc_init_net(struct net *net)
> return ret;
> }
>
> -/*
> - * Clean up a per-network namespace record.
> - */
> -static __net_exit void rxrpc_exit_net(struct net *net)
> +static __net_exit void rxrpc_pre_exit_net(struct net *net)
> {
> struct rxrpc_net *rxnet = rxrpc_net(net);
>
> @@ -111,14 +108,24 @@ static __net_exit void rxrpc_exit_net(struct net *net)
> timer_delete_sync(&rxnet->peer_keepalive_timer);
> rxrpc_destroy_all_calls(rxnet);
> rxrpc_destroy_all_connections(rxnet);
> +}
[Severity: High]
Can this hang netns teardown when kafs is loaded?
ops_undo_list() runs every ops->pre_exit() before any ops->exit():
net/core/net_namespace.c:ops_undo_list() {
...
list_for_each_entry_continue_reverse(ops, ops_list, list) {
hold_rtnl |= !!ops->exit_rtnl;
ops_pre_exit_list(ops, net_exit_list);
}
...
}
kafs has no .pre_exit:
fs/afs/main.c:
static struct pernet_operations afs_net_ops = {
.init = afs_net_init,
.exit = afs_net_exit,
...
};
so afs_close_socket(), the only path that reaches
rxrpc_discard_prealloc(), cannot run until rxrpc_pre_exit_net() has already
returned. afs_net_init() -> afs_open_socket() charges rxrpc_max_backlog
preallocated service calls (rxrpc_alloc_call() does
atomic_inc(&rxnet->nr_calls)) and the matching prealloc service
connections in every namespace.
That leaves rxrpc_destroy_all_calls() waiting on a counter that nothing can
decrement:
net/rxrpc/call_object.c:rxrpc_destroy_all_calls() {
...
atomic_dec(&rxnet->nr_calls);
wait_var_event(&rxnet->nr_calls, !atomic_read(&rxnet->nr_calls));
}
with the cleanup_net worker holding pernet_ops_rwsem for read, and after
printing "Call ... still in use" for the prealloc calls.
If the call backlog were somehow empty, would rxrpc_destroy_all_connections()
then trip on the still-linked AFS prealloc service conns?
net/rxrpc/conn_object.c:rxrpc_destroy_all_connections() {
...
BUG_ON(leak);
ASSERT(list_empty(&rxnet->conn_proc_list));
...
}
Before the patch all four steps ran from .exit, which executes after
afs_net_exit() has closed the socket and discarded the preallocation. Does
the reported use-after-free need the split at all, or is the peer_hash_lock
hunk alone sufficient?
> +
> +/*
> + * Clean up a per-network namespace record.
> + */
> +static __net_exit void rxrpc_exit_net(struct net *net)
> +{
> + struct rxrpc_net *rxnet = rxrpc_net(net);
> +
> rxrpc_destroy_all_peers(rxnet);
> rxrpc_destroy_all_locals(rxnet);
> proc_remove(rxnet->proc_net);
> }
[Severity: Medium]
With both drains moved out, rxrpc_exit_net() now reaches the peer and local
leak checks with no preceding quiescence step, while the socket closures
that generate asynchronous peer releases happen in the same .exit phase
just before it (.exit hooks run in reverse registration order, so
afs_net_exit() precedes rxrpc_exit_net()):
fs/afs/main.c:afs_net_exit() {
...
afs_wait_for_servers(net);
afs_close_socket(net);
...
}
Previously rxrpc_destroy_all_calls() (wait for nr_calls == 0) and
rxrpc_destroy_all_connections() (flush_workqueue plus wait for
nr_conns == 0) ran immediately before the peer walk and drained exactly the
releases produced by those closures. Now they run before the events they
were draining.
Can rxrpc_destroy_all_peers() therefore print "Leaked peer" for peers whose
release is still in flight, in particular the peer references dropped from
afs_free_addrlist(), queued during afs_net_exit() and hence after the
core's intermediate synchronize_rcu()?
For the record, the related worry about rxrpc_destroy_all_locals() hitting
BUG() does not seem to hold: rxrpc_alloc_peer() takes only a plain
rxrpc_get_local() reference, and the hlist_del_init_rcu(&local->link) unlink
is driven by active_users reaching zero and completes synchronously inside
rxrpc_unuse_local()'s kthread_stop(), which for kafs happens inside
afs_close_socket().
[ ... ]
> diff --git a/net/rxrpc/peer_object.c b/net/rxrpc/peer_object.c
> index fa9a406e11683..faa2983638b2b 100644
> --- a/net/rxrpc/peer_object.c
> +++ b/net/rxrpc/peer_object.c
> @@ -454,6 +454,12 @@ void rxrpc_destroy_all_peers(struct rxrpc_net *rxnet)
> struct rxrpc_peer *peer;
> int i;
>
> + /*
> + * Prevent use-after-free if a peer is concurrently unlinked from the
> + * hash table and freed via RCU during iteration.
> + */
> + spin_lock_bh(&rxnet->peer_hash_lock);
> +
> for (i = 0; i < HASH_SIZE(rxnet->peer_hash); i++) {
> if (hlist_empty(&rxnet->peer_hash[i]))
> continue;
[Severity: Medium]
Should the number of reported peers be capped now that the walk runs with
BH disabled?
The loop covers all 1024 buckets and every peer in them, with one pr_err()
per peer and no output limit, whereas the call reporter deliberately stops
early:
net/rxrpc/call_object.c:rxrpc_destroy_all_calls() {
...
if (++shown >= 10)
break;
...
}
Before the patch this loop ran in plain preemptible process context. A peer
refcount leak (the very condition this diagnostic exists to report, and the
class addressed by the referenced Fixes: commit) can leave a large number of
peers hashed, so this becomes a long atomic section full of printk() calls
to a possibly slow console.
peer_hash_lock is also taken from softirq context (rxrpc_lookup_peer(),
rxrpc_new_incoming_peer(), and __rxrpc_put_peer()), so does holding it
across that burst stall packet input for any still-live local endpoint in
the namespace, and can it trip softlockup or RCU-stall watchdogs?
Collecting under the lock and printing outside it, or capping the count as
the call and conn reporters do, would avoid that.
>
> @@ -465,6 +471,8 @@ void rxrpc_destroy_all_peers(struct rxrpc_net *rxnet)
> &peer->srx.transport);
> }
> }
> +
> + spin_unlock_bh(&rxnet->peer_hash_lock);
> }
[Severity: High]
This hardens the reader side, but rxrpc_destroy_all_peers() still only
reports peers; it neither frees them nor waits for outstanding peer
references, so rxrpc_exit_net() can return with peers still holding a
pointer into rxnet.
rxnet is net_generic memory freed by ops_free_list() at the end of
ops_undo_list(), which happens before cleanup_net()'s rcu_barrier():
net/core/net_namespace.c:cleanup_net() {
...
ops_undo_list(&pernet_list, NULL, &net_exit_list, true);
up_read(&pernet_ops_rwsem);
/* Ensure there are no outstanding rcu callbacks using this
* network namespace.
*/
rcu_barrier();
...
}
A concrete late-put path exists in kafs: afs_put_addrlist() does
call_rcu(&alist->rcu, afs_free_addrlist), and the callback drops the peer
references:
fs/afs/addr_list.c:afs_free_addrlist() {
...
for (i = 0; i < alist->nr_addrs; i++)
rxrpc_kernel_put_peer(alist->addrs[i].peer);
...
}
Those call_rcu()s are queued during afs_net_exit(), i.e. in the .exit phase
after the core's intermediate synchronize_rcu(), and nothing waits for them.
When the callback finally runs:
net/rxrpc/peer_object.c:__rxrpc_put_peer() {
struct rxrpc_net *rxnet = peer->local->rxnet;
...
spin_lock_bh(&rxnet->peer_hash_lock);
hash_del_rcu(&peer->hash_link);
...
}
Can this write into the already-freed rxrpc_net block? Calls and connections
are covered by bias counters plus wait_var_event(&rxnet->nr_calls) and
wait_var_event(&rxnet->nr_conns), but there is no equivalent drain for peer
references held outside calls and conns. Would a peer bias counter plus
wait, or a barrier, be needed to close this?
>
> /**
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/20260907222242.3361174-2-elver%40google.com
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-09-10 13:21 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-09-07 22:21 [PATCH v2] rxrpc: Fix use-after-free in rxrpc_destroy_all_peers() Marco Elver
2026-09-08 8:11 ` [syzbot ci] " syzbot ci
2026-09-10 13:21 ` [PATCH v2] " netdev-bot+sashiko
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®