Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 23 additions & 6 deletions kernel/bpf/helpers.c
Original file line number Diff line number Diff line change
Expand Up @@ -1427,10 +1427,20 @@ static int bpf_async_update_prog_callback(struct bpf_async_cb *cb,
return 0;
}

static DEFINE_PER_CPU(int, async_cb_running);

static int bpf_async_schedule_op(struct bpf_async_cb *cb, enum bpf_async_op op,
u64 nsec, u32 timer_mode)
{
WARN_ON_ONCE(!in_hardirq());
/*
* Do not schedule another operation on this cpu if it's in irq_work
* callback that is processing async_cmds queue. Otherwise the following
* loop is possible:
* bpf_timer_start() -> bpf_async_schedule_op() -> irq_work_queue().
* irqrestore -> bpf_async_irq_worker() -> tracepoint -> bpf_timer_start().
*/
if (this_cpu_read(async_cb_running))
return -EDEADLK;

struct bpf_async_cmd *cmd = kmalloc_nolock(sizeof(*cmd), 0, NUMA_NO_NODE);

Expand Down Expand Up @@ -1473,6 +1483,11 @@ static const struct bpf_func_proto bpf_timer_set_callback_proto = {
.arg2_type = ARG_PTR_TO_FUNC,
};

static bool defer_timer_wq_op(void)
{
return in_hardirq() || irqs_disabled();
}

BPF_CALL_3(bpf_timer_start, struct bpf_async_kern *, async, u64, nsecs, u64, flags)
{
struct bpf_hrtimer *t;
Expand Down Expand Up @@ -1500,7 +1515,7 @@ BPF_CALL_3(bpf_timer_start, struct bpf_async_kern *, async, u64, nsecs, u64, fla
if (!refcount_inc_not_zero(&t->cb.refcnt))
return -ENOENT;

if (!in_hardirq()) {
if (!defer_timer_wq_op()) {
hrtimer_start(&t->timer, ns_to_ktime(nsecs), mode);
bpf_async_refcount_put(&t->cb);
return 0;
Expand All @@ -1524,7 +1539,7 @@ BPF_CALL_1(bpf_timer_cancel, struct bpf_async_kern *, async)
bool inc = false;
int ret = 0;

if (in_hardirq())
if (defer_timer_wq_op())
return -EOPNOTSUPP;

t = READ_ONCE(async->timer);
Expand Down Expand Up @@ -1625,13 +1640,15 @@ static void bpf_async_irq_worker(struct irq_work *work)
return;

list = llist_reverse_order(list);
this_cpu_inc(async_cb_running);
llist_for_each_safe(pos, n, list) {
struct bpf_async_cmd *cmd;

cmd = container_of(pos, struct bpf_async_cmd, node);
bpf_async_process_op(cb, cmd->op, cmd->nsec, cmd->mode);
kfree_nolock(cmd);
}
this_cpu_dec(async_cb_running);
}

static void bpf_async_cancel_and_free(struct bpf_async_kern *async)
Expand All @@ -1650,7 +1667,7 @@ static void bpf_async_cancel_and_free(struct bpf_async_kern *async)
* refcnt. Either synchronously or asynchronously in irq_work.
*/

if (!in_hardirq()) {
if (!defer_timer_wq_op()) {
bpf_async_process_op(cb, BPF_ASYNC_CANCEL, 0, 0);
} else {
(void)bpf_async_schedule_op(cb, BPF_ASYNC_CANCEL, 0, 0);
Expand Down Expand Up @@ -3161,7 +3178,7 @@ __bpf_kfunc int bpf_wq_start(struct bpf_wq *wq, unsigned int flags)
if (!refcount_inc_not_zero(&w->cb.refcnt))
return -ENOENT;

if (!in_hardirq()) {
if (!defer_timer_wq_op()) {
schedule_work(&w->work);
bpf_async_refcount_put(&w->cb);
return 0;
Expand Down Expand Up @@ -4461,7 +4478,7 @@ __bpf_kfunc int bpf_timer_cancel_async(struct bpf_timer *timer)
if (!refcount_inc_not_zero(&cb->refcnt))
return -ENOENT;

if (!in_hardirq()) {
if (!defer_timer_wq_op()) {
struct bpf_hrtimer *t = container_of(cb, struct bpf_hrtimer, cb);

ret = hrtimer_try_to_cancel(&t->timer);
Expand Down
33 changes: 33 additions & 0 deletions tools/testing/selftests/bpf/prog_tests/timer_start_deadlock.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
#include <test_progs.h>
#include "timer_start_deadlock.skel.h"

void test_timer_start_deadlock(void)
{
struct timer_start_deadlock *skel;
int err, prog_fd;
LIBBPF_OPTS(bpf_test_run_opts, opts);

skel = timer_start_deadlock__open_and_load();
if (!ASSERT_OK_PTR(skel, "skel_open_and_load"))
return;

err = timer_start_deadlock__attach(skel);
if (!ASSERT_OK(err, "skel_attach"))
goto cleanup;

prog_fd = bpf_program__fd(skel->progs.start_timer);

/*
* Run the syscall program that attempts to deadlock.
* If the kernel deadlocks, this call will never return.
*/
err = bpf_prog_test_run_opts(prog_fd, &opts);
ASSERT_OK(err, "prog_test_run");
ASSERT_EQ(opts.retval, 0, "prog_retval");

ASSERT_EQ(skel->bss->tp_called, 1, "tp_called");
cleanup:
timer_start_deadlock__destroy(skel);
}
70 changes: 70 additions & 0 deletions tools/testing/selftests/bpf/progs/timer_start_deadlock.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// SPDX-License-Identifier: GPL-2.0
/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
#include <vmlinux.h>
#include <bpf/bpf_helpers.h>
#include <bpf/bpf_tracing.h>

#define CLOCK_MONOTONIC 1

char _license[] SEC("license") = "GPL";

struct elem {
struct bpf_timer timer;
};

struct {
__uint(type, BPF_MAP_TYPE_ARRAY);
__uint(max_entries, 1);
__type(key, int);
__type(value, struct elem);
} timer_map SEC(".maps");

volatile int in_timer_start;
volatile int tp_called;

static int timer_cb(void *map, int *key, struct elem *value)
{
return 0;
}

SEC("tp_btf/hrtimer_cancel")
int BPF_PROG(tp_hrtimer_cancel, struct hrtimer *hrtimer)
{
struct bpf_timer *timer;
int key = 0;

if (!in_timer_start)
return 0;

tp_called = 1;
timer = bpf_map_lookup_elem(&timer_map, &key);

/*
* Call bpf_timer_start() from the tracepoint within hrtimer logic
* on the same timer to make sure it doesn't deadlock.
*/
bpf_timer_start(timer, 1000000000, 0);
return 0;
}

SEC("syscall")
int start_timer(void *ctx)
{
struct bpf_timer *timer;
int key = 0;

timer = bpf_map_lookup_elem(&timer_map, &key);
/* claude may complain here that there is no NULL check. Ignoring it. */
bpf_timer_init(timer, &timer_map, CLOCK_MONOTONIC);
bpf_timer_set_callback(timer, timer_cb);

/*
* call hrtimer_start() twice, so that 2nd call does
* remove_hrtimer() and trace_hrtimer_cancel() tracepoint.
*/
in_timer_start = 1;
bpf_timer_start(timer, 1000000000, 0);
bpf_timer_start(timer, 1000000000, 0);
in_timer_start = 0;
return 0;
}
Loading