Summary
sk_psock_backlog() uses sk_psock_get(psock->sk) to take a reference before processing queued skbs, but this looks up whatever psock is currently attached to the socket (sk->sk_user_data), which can differ from the psock running the backlog. On the refcount underflow path, this triggers a refcount_warn_saturate WARNING that the new kernel_splats CI check correctly flags, failing otherwise-passing test runs.
Failure Details
- Test / Component:
sockmap_redir — all subtests pass, but kernel_splats check detects a WARNING in dmesg
- Frequency: Rare/intermittent — observed in 1 of 12 runs examined (Aug 24, 2026); same PR passing clean Aug 21
- Failure mode: Refcount underflow (REFCOUNT_SUB_UAF) in psock backlog worker, caught by dmesg scanning
- Affected architectures: x86_64 (observed); likely all architectures
- CI runs observed:
Root Cause Analysis
The race exists in net/core/skmsg.c:sk_psock_backlog() (line 693):
if (!sk_psock_get(psock->sk)) /* BUG: may increment WRONG psock's refcnt */
return;
...
sk_psock_put(psock->sk, psock); /* decrements THIS psock's refcnt */
sk_psock_get(psock->sk) calls sk_psock(sk) which reads rcu_dereference(sk->sk_user_data). If the socket was re-mapped between sk_psock_drop() (which sets sk_user_data = NULL under sk_callback_lock) and sk_psock_stop() (which clears SK_PSOCK_TX_ENABLED AFTER releasing the lock), a new psock can be attached to the socket in that window.
Race timeline:
- Old psock (P1) refcount → 0, triggering
sk_psock_drop()
sk_psock_drop() sets sk_user_data = NULL (under lock), then releases lock
- Another CPU creates new psock P2 for same socket via
sk_psock_init() (acquires same lock, sees NULL, creates P2)
- P1's backlog fires:
SK_PSOCK_TX_ENABLED still set (step 3b sk_psock_stop() hasn't run yet)
sk_psock_get(P1->sk) → finds P2 via sk_user_data → increments P2->refcnt
sk_psock_put(P1->sk, P1) → decrements P1->refcnt (already 0) → UNDERFLOW!
This was introduced by commit 8259eb0e06d8 ("bpf, sockmap: Avoid using sk_socket after free when sending") which added the sk_psock_get(psock->sk) call to synchronize with sock_map_close().
Proposed Fix
Replace sk_psock_get(psock->sk) with refcount_inc_not_zero(&psock->refcnt) to directly increment the backlog's own psock refcount. This:
- Eliminates the mismatch: always increments/decrements the same psock
- Preserves the synchronization property: while refcount > 0,
sk_psock_drop() hasn't been called, so sk_user_data still points to this psock, meaning sock_map_close()'s sk_psock_get(sk) will find it and call cancel_delayed_work_sync()
- Correctly fails (
returns false) if refcount is already 0, triggering early return
See output/0001-bpf-sockmap-Fix-psock-refcount-mismatch-in-sk_psock_backlog.patch.
Impact
Without the fix, the kernel_splats CI check will intermittently fail sockmap_redir test runs despite all tests passing. This creates misleading CI signal that developers must manually investigate and dismiss. The underlying refcount underflow also indicates a potential use-after-free if the psock is freed while backlog is still processing.
References
Summary
sk_psock_backlog()usessk_psock_get(psock->sk)to take a reference before processing queued skbs, but this looks up whatever psock is currently attached to the socket (sk->sk_user_data), which can differ from the psock running the backlog. On the refcount underflow path, this triggers arefcount_warn_saturateWARNING that the newkernel_splatsCI check correctly flags, failing otherwise-passing test runs.Failure Details
sockmap_redir— all subtests pass, butkernel_splatscheck detects a WARNING in dmesgRoot Cause Analysis
The race exists in
net/core/skmsg.c:sk_psock_backlog()(line 693):sk_psock_get(psock->sk)callssk_psock(sk)which readsrcu_dereference(sk->sk_user_data). If the socket was re-mapped betweensk_psock_drop()(which setssk_user_data = NULLundersk_callback_lock) andsk_psock_stop()(which clearsSK_PSOCK_TX_ENABLEDAFTER releasing the lock), a new psock can be attached to the socket in that window.Race timeline:
sk_psock_drop()sk_psock_drop()setssk_user_data = NULL(under lock), then releases locksk_psock_init()(acquires same lock, sees NULL, creates P2)SK_PSOCK_TX_ENABLEDstill set (step 3bsk_psock_stop()hasn't run yet)sk_psock_get(P1->sk)→ finds P2 viask_user_data→ increments P2->refcntsk_psock_put(P1->sk, P1)→ decrements P1->refcnt (already 0) → UNDERFLOW!This was introduced by commit
8259eb0e06d8("bpf, sockmap: Avoid using sk_socket after free when sending") which added thesk_psock_get(psock->sk)call to synchronize withsock_map_close().Proposed Fix
Replace
sk_psock_get(psock->sk)withrefcount_inc_not_zero(&psock->refcnt)to directly increment the backlog's own psock refcount. This:sk_psock_drop()hasn't been called, sosk_user_datastill points to this psock, meaningsock_map_close()'ssk_psock_get(sk)will find it and callcancel_delayed_work_sync()returns false) if refcount is already 0, triggering early returnSee
output/0001-bpf-sockmap-Fix-psock-refcount-mismatch-in-sk_psock_backlog.patch.Impact
Without the fix, the
kernel_splatsCI check will intermittently failsockmap_redirtest runs despite all tests passing. This creates misleading CI signal that developers must manually investigate and dismiss. The underlying refcount underflow also indicates a potential use-after-free if the psock is freed while backlog is still processing.References
8259eb0e06d8("bpf, sockmap: Avoid using sk_socket after free when sending") — introduced the vulnerable patternkernel_splatscheck:github/libbpf/ci/run-vmtest/check-kernel-splats.sh