From: Mahe Tardy <mahe.tardy@gmail.com>
To: Mani Tofigh <mt4022@columbia.edu>
Cc: Alexei Starovoitov <ast@kernel.org>,
Daniel Borkmann <daniel@iogearbox.net>,
Andrii Nakryiko <andrii@kernel.org>,
Eduard Zingerman <eddyz87@gmail.com>,
Kumar Kartikeya Dwivedi <memxor@gmail.com>,
Martin KaFai Lau <martin.lau@linux.dev>,
Song Liu <song@kernel.org>,
Yonghong Song <yonghong.song@linux.dev>,
Jiri Olsa <jolsa@kernel.org>,
Emil Tsalapatis <emil@etsalapatis.com>,
Ihor Solodrai <ihor.solodrai@linux.dev>,
Mykyta Yatsenko <yatsenko@meta.com>,
bpf@vger.kernel.org, linux-kernel@vger.kernel.org,
tz2294@columbia.edu
Subject: Re: [PATCH bpf] bpf: Fix bpf_timer callback running on a freed map
Date: Tue, 27 Oct 2026 19:48:23 +0100 [thread overview]
Message-ID: <auDx91tmsF-aKKdz@gmail.com> (raw)
In-Reply-To: <20261027-bpf-timer-freed-map-v1-1-30402b0ee99b@columbia.edu>
On Tue, Oct 27, 2026 at 01:03:42PM -0400, Mani Tofigh wrote:
> A bpf_timer in an inner map can fire after the map has been freed, and
> bpf_timer_cb() then reads the freed map:
>
> [ 50.709520] BUG: KASAN: slab-use-after-free in bpf_timer_cb+0x249/0x290
> [ 50.709540] Read of size 4 at addr ff11000003bc1030 by task bpftimer/72
> ...
> [ 50.710783] Freed by task 12:
> ...
> [ 50.710826] bpf_map_free+0x1a2/0x380
>
> When user space closes the map, its timers are cancelled and their
> callbacks are cleared. Since commit 1bfbc267ec91 ("bpf: Enable
> bpf_timer and bpf_wq in any context"), bpf_timer_set_callback() and
> bpf_timer_start() run without a lock, so a program that found the timer
> just before the close can still use it afterwards:
>
> Task A Task B Task C
> ------ ------ ------
> bpf_timer_start()
> finds the timer
> bpf_timer_set_callback()
> finds the timer
> close(map_fd)
> clears the callback
> cancels the timer
> sets the callback
> arms the timer
> deletes the map from
> the outer map
> map is freed
> the timer fires
> bpf_timer_cb() reads the freed map
>
> The timer is cancelled once more when it is finally freed, but that
> happens well after the inner map is freed, so a timer started this way
> can still fire on the freed map.
>
> Clear the timer's map pointer in bpf_async_cancel_and_free() before the
> map can be freed, and make the bpf_timer and bpf_wq callbacks return
> early when it is NULL. The map is not freed until callbacks that are
> already running have finished, so a callback either sees the map while
> it is still valid or sees NULL.
>
> The reproducer below calls the two helpers in a loop on two CPUs while a
> third CPU keeps creating an inner map with a timer, closing it and
> removing it from the outer map. This bug was identified and confirmed
> with the help of a deterministic hypervisor currently under development,
> but the non-deterministic C reproducer can typically trigger it in the
> 1-50s time range. With the fix, nothing is triggered after 600 seconds.
>
> Fixes: 1bfbc267ec91 ("bpf: Enable bpf_timer and bpf_wq in any context")
> Signed-off-by: Mani Tofigh <mt4022@columbia.edu>
Tested-by: Mahe Tardy <mahe.tardy@gmail.com>
See below for testings details.
Review-by: Mahe Tardy <mahe.tardy@gmail.com>
For the review part, I'm not a concurency expert but this looks good to
me, bpf_timer_cb() should run within RCU protection from the softirq
context that calls it and bpf_wq_work() is explicitely calling the
rcu_rea_lock()/unlock(), thus:
- the callbacks read the pointer before the cleanup clears it: it's fine
since it's not freed yet thanks to RCU protection (and for
bpf_wq_work(), the read was moved in the RCU section).
- the callbacks read the pointer after the cleanup clears it: it's NULL
and we have new if (!map) checks so that it returns early.
> ---
> This is a different use-after-free from the one fixed by "bpf:
> Dissociate struct_ops progs before the grace period of the map" in
> Qiliang Yuan's v6 struct_ops series [1]. There, a timer callback
> returns into the freed trampoline of a struct_ops map. Here, the
> callback reads the freed map that holds the timer.
>
> [1] https://lore.kernel.org/all/20261022-selftests-bpf-struct-ops-assoc-timer-v6-2-2c0f18e82582@gmail.com/
>
> Reproducer: https://github.com/manitofigh/files/blob/main/linux-repros/bpftimer.c
I tested the reproducer and indeed on bpf-next HEAD (which was
4169c9c90532 ("powerpc/irq: Fix build with CONFIG_PROC_FS=n")), I could
reproduce the issue quickly:
[+] bpftimer: bug: the kernel reported an error after 275 rounds, 0.5 s (taint 0 -> 32)
LB-RESULT bug rounds=275 seconds=0.5
with:
[ 16.088731] BUG: KASAN: slab-use-after-free in bpf_timer_cb+0x205/0x270
[ 16.088772] Read of size 4 at addr ffff888138615838 by task a.out/539
And after applying your fix:
bpftimer: maps and programs loaded, racing for up to 600 s
bpftimer: 17794 rounds, 30 s
[...]
bpftimer: 350440 rounds, 570 s
[+] bpftimer: no bug in 369186 rounds, 600.0 s
LB-RESULT none rounds=369186 seconds=600.0
> ---
> kernel/bpf/helpers.c | 11 +++++++----
> 1 file changed, 7 insertions(+), 4 deletions(-)
>
> diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
> index 648abe1ba63a..58e357302850 100644
> --- a/kernel/bpf/helpers.c
> +++ b/kernel/bpf/helpers.c
> @@ -1221,7 +1221,7 @@ static void bpf_async_refcount_put(struct bpf_async_cb *cb);
> static enum hrtimer_restart bpf_timer_cb(struct hrtimer *hrtimer)
> {
> struct bpf_hrtimer *t = container_of(hrtimer, struct bpf_hrtimer, timer);
> - struct bpf_map *map = t->cb.map;
> + struct bpf_map *map = READ_ONCE(t->cb.map);
> void *value = t->cb.value;
> bpf_callback_t callback_fn;
> void *key;
> @@ -1229,7 +1229,7 @@ static enum hrtimer_restart bpf_timer_cb(struct hrtimer *hrtimer)
>
> BTF_TYPE_EMIT(struct bpf_timer);
> callback_fn = rcu_dereference_check(t->cb.callback_fn, rcu_read_lock_bh_held());
> - if (!callback_fn)
> + if (!callback_fn || !map)
> goto out;
>
> /* bpf_timer_cb() runs in hrtimer_run_softirq. It doesn't migrate and
> @@ -1254,7 +1254,7 @@ static void bpf_wq_work(struct work_struct *work)
> {
> struct bpf_work *w = container_of(work, struct bpf_work, work);
> struct bpf_async_cb *cb = &w->cb;
> - struct bpf_map *map = cb->map;
> + struct bpf_map *map;
> bpf_callback_t callback_fn;
> void *value = cb->value;
> void *key;
> @@ -1265,8 +1265,9 @@ static void bpf_wq_work(struct work_struct *work)
> /* keeps the program that callback_fn belongs to */
> rcu_read_lock_trace();
>
> + map = READ_ONCE(cb->map);
> callback_fn = READ_ONCE(cb->callback_fn);
> - if (!callback_fn)
> + if (!callback_fn || !map)
> goto out;
>
> key = map_key_from_value(map, value, &idx);
> @@ -1733,6 +1734,8 @@ static void bpf_async_cancel_and_free(struct bpf_async_kern *async)
> if (!cb)
> return;
>
> + /* Callbacks that run after this see NULL and return. */
> + WRITE_ONCE(cb->map, NULL);
> bpf_async_update_prog_callback(cb, NULL, NULL);
> /*
> * No refcount_inc_not_zero(&cb->refcnt) here. Dropping the last
>
> ---
> base-commit: 195a3519ff27bff5575e05b5086e38ee92715776
> change-id: 20261026-bpf-timer-freed-map-ab8dbd268b51
>
> Best regards,
> --
> Mani Tofigh <mt4022@columbia.edu>
>
next prev parent reply other threads:[~2026-10-27 18:48 UTC|newest]
Thread overview: 3+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-10-27 17:03 Mani Tofigh via B4 Relay
2026-10-27 18:48 ` Mahe Tardy [this message]
2026-10-27 19:40 ` patchwork-bot+netdevbpf
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=auDx91tmsF-aKKdz@gmail.com \
--to=mahe.tardy@gmail.com \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=eddyz87@gmail.com \
--cc=emil@etsalapatis.com \
--cc=ihor.solodrai@linux.dev \
--cc=jolsa@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=martin.lau@linux.dev \
--cc=memxor@gmail.com \
--cc=mt4022@columbia.edu \
--cc=song@kernel.org \
--cc=tz2294@columbia.edu \
--cc=yatsenko@meta.com \
--cc=yonghong.song@linux.dev \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®