CVE-2025-40280 fixed tipc_mon_reinit_self() accessing monitors[] from a workqueue without RTNL. That patch closed the workqueue path by adding rtnl_lock() around the call. However, three additional functions in the same subsystem access tipc_net->monitors[] from softirq context with no RCU protection at all: tipc_mon_peer_up() - called from tipc_node_write_unlock() tipc_mon_peer_down() - called from tipc_node_write_unlock() tipc_mon_remove_peer() - called from tipc_node_link_down() These three are invoked from the packet receive path (tipc_rcv -> tipc_node_write_unlock / tipc_node_link_down) and hold only the per-node rwlock, not RTNL. Concurrently, bearer_disable() -- which always holds RTNL per its own inline documentation -- calls tipc_mon_delete(), which: 1. acquires mon->lock 2. sets tn->monitors[bearer_id] = NULL 3. frees all peer entries 4. releases mon->lock 5. calls kfree(mon) <-- no synchronize_rcu() The race is structural: there is no shared lock between the data-path reader (which reads monitors[id] then acquires mon->lock) and the teardown path (which acquires mon->lock, NULLs the slot, then frees). A softirq thread can read a non-NULL mon pointer, get preempted, and resume after kfree(mon) has run on another CPU, then call write_lock_bh(&mon->lock) on freed memory: CPU 0 (softirq / tipc_rcv) CPU 1 (RTNL / bearer_disable) tipc_mon_peer_up() mon = tipc_monitor(net, id) [mon is non-NULL] tipc_mon_delete() write_lock_bh(&mon->lock) tn->monitors[id] = NULL ... write_unlock_bh(&mon->lock) kfree(mon) write_lock_bh(&mon->lock) <-- UAF The fix mirrors the existing bearer_list[] pattern in the same module: convert monitors[] to __rcu, use rcu_assign_pointer() on creation, RCU_INIT_POINTER() + synchronize_rcu() on deletion (before the kfree), and the appropriate rcu_dereference_bh() vs rtnl_dereference() variant at each read site depending on execution context. synchronize_rcu() in tipc_mon_delete() is placed after the write_unlock_bh() and before timer_shutdown_sync() + kfree() to ensure all softirq-context readers that already observed the old pointer have completed before the memory is freed. Fixes: 35c55c9877f8 ("tipc: add neighbor monitoring framework") Cc: stable@vger.kernel.org Signed-off-by: Kai Aizen --- v2: Resubmit targeting mainline via netdev per stable-kernel-rules (Option 1). No code changes from v1. net/tipc/core.h | 2 +- net/tipc/monitor.c | 51 +++++++++++++++++++++++++++++++++-------------- 2 files changed, 37 insertions(+), 16 deletions(-) diff --git a/net/tipc/core.h b/net/tipc/core.h --- a/net/tipc/core.h +++ b/net/tipc/core.h @@ -109,7 +109,7 @@ u32 num_links; /* Neighbor monitoring list */ - struct tipc_monitor *monitors[MAX_BEARERS]; + struct tipc_monitor __rcu *monito[MAX_BEARERS]; rs +