xfrm_policy_bysel_ctx() saves a pointer to a xfrm_pol_inexact_bin while holding xfrm_policy_lock, then drops the lock to call xfrm_policy_kill(). After that it calls xfrm_policy_inexact_prune_bin() on the saved pointer. A concurrent xfrm_hash_rebuild (triggered by XFRM_MSG_NEWSPDINFO) can free the bin via __xfrm_policy_inexact_flush() -> kfree_rcu() during the window where the lock is not held, making the saved pointer stale. Fix by pruning the bin while still holding xfrm_policy_lock, before dropping it. Use __xfrm_policy_inexact_prune_bin() directly since the lock is already held. This is safe because the function uses kfree_rcu() for the actual free, which is non-blocking. The wrapper xfrm_policy_inexact_prune_bin() becomes unused and is removed. Race: CPU0 (XFRM_MSG_DELPOLICY) CPU1 (XFRM_MSG_NEWSPDINFO) ============================ ========================== xfrm_policy_bysel_ctx(): spin_lock_bh(xfrm_policy_lock) bin = xfrm_policy_inexact_lookup() __xfrm_policy_unlink(pol) spin_unlock_bh(xfrm_policy_lock) xfrm_policy_kill(ret) // wide window, lock not held xfrm_hash_rebuild(): spin_lock_bh(xfrm_policy_lock) __xfrm_policy_inexact_flush(): kfree_rcu(bin) // bin freed spin_unlock_bh(xfrm_policy_lock) xfrm_policy_inexact_prune_bin(bin) // UAF: bin is freed Reproduction: 1. Build kernel >= 4.19 with CONFIG_KASAN=y, CONFIG_XFRM=y, CONFIG_USER_NS=y (for unprivileged reproduction) 2. Boot in a VM 3. Compile: gcc -O2 -o repro -static -pthread repro.c 4. Run as any unprivileged user: ./repro 30 4 (30 seconds duration, 4 threads per type) 5. Check dmesg for: BUG: KASAN: slab-use-after-free in xfrm_policy_bysel_ctx The reproducer is fully unprivileged: it uses unshare(CLONE_NEWUSER | CLONE_NEWNET) to get CAP_NET_ADMIN inside a user namespace, then races XFRM_MSG_DELPOLICY (by selector) against XFRM_MSG_NEWSPDINFO (threshold changes that trigger hash rebuilds). Note: the race is hard to trigger with vanilla kfree_rcu because the actual free is deferred past the typical stale-access window. Adding a cond_resched() delay after xfrm_policy_kill() or replacing kfree_rcu with kfree (for testing only) makes it reliably reproducible. The lifetime violation exists regardless of whether KASAN catches it on a given run. KASAN report (reproduced on 6.12.91 with immediate kfree for testing): BUG: KASAN: slab-use-after-free in xfrm_policy_bysel_ctx.cold+0x59/0xb8 Read of size 8 at addr ffff8881153c4700 by task repro_xfrm/387 Call Trace: xfrm_policy_bysel_ctx.cold+0x59/0xb8 xfrm_get_policy+0x7df/0xc00 xfrm_user_rcv_msg+0x41b/0x950 netlink_rcv_skb+0x16d/0x420 Fixes: 9cf545ebd5d8 ("xfrm: policy: implement selector-based inexact lookup") Signed-off-by: Sanghyun Park --- Hi, I'm Sanghyun Park, a security researcher. I found this while auditing the XFRM policy code. The bug has existed since 4.19 and affects all kernels since then. It is triggerable by any unprivileged user via user namespaces (CLONE_NEWUSER + CLONE_NEWNET), making it a potential LPE vector on systems with user namespaces enabled (all major distros by default: Ubuntu, Fedora, Debian, Arch, etc.). The C reproducer is attached separately (repro.c). net/xfrm/xfrm_policy.c | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/net/xfrm/xfrm_policy.c b/net/xfrm/xfrm_policy.c index fca07f8e60..fef6cff511 100644 --- a/net/xfrm/xfrm_policy.c +++ b/net/xfrm/xfrm_policy.c @@ -1156,15 +1156,6 @@ static void __xfrm_policy_inexact_prune_bin(struct xfrm_pol_inexact_bin *b, bool } } -static void xfrm_policy_inexact_prune_bin(struct xfrm_pol_inexact_bin *b) -{ - struct net *net = read_pnet(&b->k.net); - - spin_lock_bh(&net->xfrm.xfrm_policy_lock); - __xfrm_policy_inexact_prune_bin(b, false); - spin_unlock_bh(&net->xfrm.xfrm_policy_lock); -} - static void __xfrm_policy_inexact_flush(struct net *net) { struct xfrm_pol_inexact_bin *bin, *t; @@ -1707,12 +1698,14 @@ xfrm_policy_bysel_ctx(struct net *net, const struct xfrm_mark *mark, u32 if_id, } ret = pol; } + + if (bin && ret && delete) + __xfrm_policy_inexact_prune_bin(bin, false); + spin_unlock_bh(&net->xfrm.xfrm_policy_lock); if (ret && delete) xfrm_policy_kill(ret); - if (bin && delete) - xfrm_policy_inexact_prune_bin(bin); return ret; } EXPORT_SYMBOL(xfrm_policy_bysel_ctx);