Technical Analysis of Root Cause
1. Manifestation Site & Crash Anatomy
The kernel oops occurs in network namespace cleanup (cleanup_net workqueue) when ops_exit_rtnl_list() calls WireGuard's pernet exit handler wg_netns_exit_rtnl():
sk_has_account include/net/sock.h:1578 [inline]
sk_mem_reclaim include/net/sock.h:1626 [inline]
sk_clear_memalloc+0x9c/0x254 net/core/sock.c:325
sock_free drivers/net/wireguard/socket.c:337 [inline]
wg_socket_reinit+0x190/0x2bc drivers/net/wireguard/socket.c:435
wg_netns_exit_rtnl+0xd8/0x1e0 drivers/net/wireguard/device.c:425
ops_exit_rtnl_list net/core/net_namespace.c:181 [inline]
ops_undo_list+0x2bc/0x81c net/core/net_namespace.c:249
cleanup_net+0x474/0x6c4 net/core/net_namespace.c:708
At net/core/sock.c:325 in sk_clear_memalloc():
void sk_clear_memalloc(struct sock *sk)
{
sock_reset_flag(sk, SOCK_MEMALLOC);
sk->sk_allocation &= ~__GFP_MEMALLOC;
static_branch_dec(&memalloc_socks_key);
sk_mem_reclaim(sk);
}
sk_mem_reclaim() invokes sk_has_account(sk):
static inline bool sk_has_account(struct sock *sk)
{
/* return true if protocol supports memory accounting */
return !sk->sk_prot->memory_allocated;
}
From the crash dump and disassembly:
x19 (sk) = 0xffff0000c737ec00 (which is old6, read from wg->sock6).
- Offset
0x28 of sk (sk->sk_prot) is loaded into x8. In this crash, x8 = 0x0 (NULL).
- The code then executes
add x20, x8, #0x100 (offset of proto::memory_allocated) resulting in x20 = 0x100.
- Dereferencing
0x100 (via KASAN shadow address dfff800000000020) generates an immediate page translation fault and oopses.
2. Root Cause Mechanism
-
Dangling / Stale Reference in wg->sock6:
In wg_socket_reinit(wg, NULL, NULL):
mutex_lock(&wg->socket_update_lock);
old4 = rcu_dereference_protected(wg->sock4, lockdep_is_held(&wg->socket_update_lock));
old6 = rcu_dereference_protected(wg->sock6, lockdep_is_held(&wg->socket_update_lock));
rcu_assign_pointer(wg->sock4, new4);
rcu_assign_pointer(wg->sock6, new6);
...
mutex_unlock(&wg->socket_update_lock);
synchronize_net();
sock_free(old4);
sock_free(old6);
old4 is freed first without error. Next, sock_free(old6) is executed.
Normally, any socket allocated via sk_alloc() has sk->sk_prot initialized to a valid protocol structure (udpv6_prot), and no code in the socket lifecycle ever clears sk->sk_prot to NULL.
-
Memory Lifecycle & Allocator Interaction:
The kernel configuration has CONFIG_INIT_ON_ALLOC_DEFAULT_ON=y. When old6 was previously released (or its refcount prematurely dropped to 0 via another teardown path, socket release, or failure recovery), the underlying slab object was freed. Subsequent allocation and zero-initialization of that memory caused all fieldsβincluding sk->sk_prot at offset 0x28βto become 0x0, while the KASAN shadow for that memory block became valid/unpoisoned. Consequently, preceding field writes in sk_clear_memalloc() (sock_reset_flag at offset 0x60 and sk->sk_allocation at offset 0x3D0) succeeded silently, mutating foreign memory, until the dereference of sk->sk_prot->memory_allocated tripped over the NULL pointer.
3. Scope and Unverified Hypotheses
- Verified:
- The crash occurs specifically on
old6 (wg->sock6), after old4 was freed.
old6 (0xffff0000c737ec00) points to a zeroed/reallocated memory chunk whose sk_prot is NULL, demonstrating that wg->sock6 held a dangling pointer to an already-freed socket.
- Mutation of
sk->sk_flags and sk->sk_allocation on that reallocated memory occurs before the crash.
- Inconclusive / Unverified:
- The exact earlier call path that dropped the final reference on
wg->sock6 prior to netns teardown (e.g. an asynchronous IPv6 stack netns release vs. an error unwind path or interface stop race) cannot be definitively isolated from the crash dump alone, as the freeing event occurred prior to the cleanup_net work execution.