[PATCH bpf v1 RESEND] bpf: Fix timer_lockup deadlock using atomic_fetch_inc()

From: Tiezhu Yang

Date: Tue Sep 01 2026 - 20:51:28 EST


When testing the BPF selftest "sudo ./test_progs -t timer_lockup", there
is a kernel lockup and panic on LoongArch:

watchdog: BUG: soft lockup - CPU#1 stuck for 8s! [test_progs:39601]
Kernel panic - not syncing: softlockup: hung tasks
...
Call Trace:
...
[<9000000000c40e98>] panic+0x44/0x48
[<9000000000e8b000>] watchdog_timer_fn+0x500/0x520
[<9000000000dfd9a4>] __hrtimer_run_queues+0xc4/0x530
[<9000000000dffe90>] hrtimer_interrupt+0x140/0x320
...
[<9000000002ad9e4c>] _raw_spin_unlock_irqrestore+0x8c/0xc0
[<9000000000dfe9e0>] hrtimer_try_to_cancel.part.0+0x70/0x350
[<9000000000dfed58>] hrtimer_cancel+0x38/0x80
[<9000000000f6d944>] bpf_timer_cancel+0x94/0x1e0
[<ffff80000200fad0>] bpf_prog_108ab87b32f22e44_timer_cb1+0xb0/0xfc
[<9000000000f6b838>] bpf_timer_cb+0x98/0x170
[<9000000000dfdaac>] __hrtimer_run_queues+0x1cc/0x530
[<9000000000dfde94>] hrtimer_run_softirq+0x84/0xd0
...
[<900000000271d9e0>] bpf_test_run+0x1c0/0x5c0
[<900000000271f548>] bpf_prog_test_run_skb+0x6e8/0xe20
[<9000000000f39940>] __sys_bpf+0x1690/0x2c50
[<9000000000f3af28>] sys_bpf+0x28/0x40
[<9000000002ac2d68>] do_syscall+0x108/0x5e0
[<9000000000c6a850>] handle_syscall+0xd0/0x170

In bpf_timer_cancel() of kernel/bpf/helpers.c, it explicitly notes that
"Need full barrier after relaxed atomic_inc" to ensure global visibility
of the cancelling state before performing the lockless dependency checks.

However, on weakly-ordered architectures such as LoongArch, the current
combination of a relaxed atomic_inc() followed by smp_mb__after_atomic()
fails to guarantee the physical store-load ordering because the latter
currently expands to an empty compiler barrier rather than a hardware
data barrier on LoongArch.

This allows a subsequent read to bypass the prior write due to store-load
reordering, enabling concurrent CPUs to simultaneously bypass the software
deadlock detection, enter hrtimer_cancel(), and then trigger a severe ABBA
deadlock in the hrtimer core.

Instead of relying on arch-specific macro implementations which may vary
in strictness, fix this issue directly in the BPF core helper by replacing
atomic_inc() and smp_mb__after_atomic() with a single atomic_fetch_inc()
to provide full ordering natively.

This ensures that the BPF core satisfies its strict store-load ordering
requirement in a self-contained manner to eliminate the deadlock under
weak memory models, and also hardens the defensive programming in the
BPF core, making the lockless deadlock detection logic immune to any
platform-level barrier interpretation variations.

With this patch, the BPF timer_lockup selftest was stressed for 10000
consecutive loops on a physical LoongArch machine without encountering
any further lockups or warnings on LoongArch:

for i in {1..10000}; do sudo ./test_progs -t timer_lockup; done

Reported-by: Vincent Li <vincent.mc.li@xxxxxxxxx>
Closes: https://lore.kernel.org/loongarch/CAK3+h2xOSEZUHhou7N2cRL-aGrZCNSm45g+P7thObMe+fpgYCA@xxxxxxxxxxxxxx/
Fixes: d4523831f07a ("bpf: Fail bpf_timer_cancel when callback is being cancelled")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Tiezhu Yang <yangtiezhu@xxxxxxxxxxx>
---
Resend due to
"Can not connect to recipient's server because of unstable network or firewall filter."

kernel/bpf/helpers.c | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)

diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index b3cc5c8fc875..7d89dc143883 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -1591,9 +1591,7 @@ BPF_CALL_1(bpf_timer_cancel, struct bpf_async_kern *, async)
*/
if (!cur_t)
goto drop;
- atomic_inc(&t->cancelling);
- /* Need full barrier after relaxed atomic_inc */
- smp_mb__after_atomic();
+ atomic_fetch_inc(&t->cancelling);
inc = true;
if (atomic_read(&cur_t->cancelling)) {
/* We're cancelling timer t, while some other timer callback is
--
2.42.0