* [PATCH 6.1.y 0/2] bpf: backport CVE-2024-41045 and CVE-2024-42239 fixes
@ 2026-10-02 19:30 Artem Dinaburg
2026-10-02 19:30 ` [PATCH 6.1.y 1/2] bpf: Fail bpf_timer_cancel when callback is being cancelled Artem Dinaburg
2026-10-02 19:30 ` [PATCH 6.1.y 2/2] bpf: Defer work in bpf_timer_cancel_and_free Artem Dinaburg
0 siblings, 2 replies; 3+ messages in thread
From: Artem Dinaburg @ 2026-10-02 19:30 UTC (permalink / raw)
To: stable
Cc: Artem Dinaburg, Greg Kroah-Hartman, Sasha Levin,
Kumar Kartikeya Dwivedi, Dohyun Kim, Neel Natu,
Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Martin KaFai Lau, Song Liu, Yonghong Song, John Fastabend,
KP Singh, Stanislav Fomichev, Hao Luo, Jiri Olsa, bpf,
linux-kernel, Eduard Zingerman, Emil Tsalapatis, Ihor Solodrai,
netdev, toke
Hi Greg, Sasha, and bpf maintainers,
These patches are the small, ordered backport for CVE-2024-41045 and
CVE-2024-42239 on 6.1.y. The second patch depends on the first, so I kept
the upstream ordering instead of folding either change into the other.
The first patch is already present in 6.6.y. The 6.6.y backport of the
second patch is queued (6.6.158-rc1). Both patches are already present in
6.12.y, 6.18.y, and 7.2.y. Could you please queue this series for 6.1.y?
AI assistance: An LLM helped identify, adapt, and validate these backports;
I reviewed the resulting code and validation evidence.
Thanks,
Artem Dinaburg
Series:
1. bpf: Fail bpf_timer_cancel when callback is being cancelled
2. bpf: Defer work in bpf_timer_cancel_and_free
^ permalink raw reply [flat|nested] 3+ messages in thread
* [PATCH 6.1.y 1/2] bpf: Fail bpf_timer_cancel when callback is being cancelled
2026-10-02 19:30 [PATCH 6.1.y 0/2] bpf: backport CVE-2024-41045 and CVE-2024-42239 fixes Artem Dinaburg
@ 2026-10-02 19:30 ` Artem Dinaburg
2026-10-02 19:30 ` [PATCH 6.1.y 2/2] bpf: Defer work in bpf_timer_cancel_and_free Artem Dinaburg
1 sibling, 0 replies; 3+ messages in thread
From: Artem Dinaburg @ 2026-10-02 19:30 UTC (permalink / raw)
To: stable
Cc: Artem Dinaburg, Greg Kroah-Hartman, Sasha Levin,
Kumar Kartikeya Dwivedi, Dohyun Kim, Neel Natu,
Alexei Starovoitov, Daniel Borkmann, Andrii Nakryiko,
Martin KaFai Lau, Song Liu, Yonghong Song, John Fastabend,
KP Singh, Stanislav Fomichev, Hao Luo, Jiri Olsa, bpf,
linux-kernel, Eduard Zingerman, Emil Tsalapatis, Ihor Solodrai,
netdev, toke
From: Kumar Kartikeya Dwivedi <memxor@gmail.com>
[ Upstream commit d4523831f07a267a943f0dde844bf8ead7495f13 ]
Given a schedule:
timer1 cb timer2 cb
bpf_timer_cancel(timer2); bpf_timer_cancel(timer1);
Both bpf_timer_cancel calls would wait for the other callback to finish
executing, introducing a lockup.
Add an atomic_t count named 'cancelling' in bpf_hrtimer. This keeps
track of all in-flight cancellation requests for a given BPF timer.
Whenever cancelling a BPF timer, we must check if we have outstanding
cancellation requests, and if so, we must fail the operation with an
error (-EDEADLK) since cancellation is synchronous and waits for the
callback to finish executing. This implies that we can enter a deadlock
situation involving two or more timer callbacks executing in parallel
and attempting to cancel one another.
Note that we avoid incrementing the cancelling counter for the target
timer (the one being cancelled) if bpf_timer_cancel is not invoked from
a callback, to avoid spurious errors. The whole point of detecting
cur->cancelling and returning -EDEADLK is to not enter a busy wait loop
(which may or may not lead to a lockup). This does not apply in case the
caller is in a non-callback context, the other side can continue to
cancel as it sees fit without running into errors.
Background on prior attempts:
Earlier versions of this patch used a bool 'cancelling' bit and used the
following pattern under timer->lock to publish cancellation status.
lock(t->lock);
t->cancelling = true;
mb();
if (cur->cancelling)
return -EDEADLK;
unlock(t->lock);
hrtimer_cancel(t->timer);
t->cancelling = false;
The store outside the critical section could overwrite a parallel
requests t->cancelling assignment to true, to ensure the parallely
executing callback observes its cancellation status.
It would be necessary to clear this cancelling bit once hrtimer_cancel
is done, but lack of serialization introduced races. Another option was
explored where bpf_timer_start would clear the bit when (re)starting the
timer under timer->lock. This would ensure serialized access to the
cancelling bit, but may allow it to be cleared before in-flight
hrtimer_cancel has finished executing, such that lockups can occur
again.
Thus, we choose an atomic counter to keep track of all outstanding
cancellation requests and use it to prevent lockups in case callbacks
attempt to cancel each other while executing in parallel.
[ Backport to 6.1.y: mapped the atomic cancellation guard directly onto
the pre-refactor bpf_hrtimer layout. ]
Reported-by: Dohyun Kim <dohyunkim@google.com>
Reported-by: Neel Natu <neelnatu@google.com>
Fixes: b00628b1c7d5 ("bpf: Introduce bpf timers.")
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Link: https://lore.kernel.org/r/20240709185440.1104957-2-memxor@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Assisted-by: LLM
Signed-off-by: Artem Dinaburg <artem@trailofbits.com>
---
Hi Greg, Sasha, and bpf maintainers,
I am working through the small CVE backports still missing from 6.1.y.
This one addresses CVE-2024-42239. It detects callback-to-callback timer
cancellation cycles and returns -EDEADLK.
The fix is already present in 6.6.y, 6.12.y, 6.18.y, and 7.2.y, but not in
6.1.y.
The target-specific adjustment is recorded in the bracketed note above.
Could you please queue it for 6.1.y?
CVE: CVE-2024-42239
Upstream: d4523831f07a267a943f0dde844bf8ead7495f13
AI assistance: An LLM helped identify, adapt, and validate this backport; I
reviewed the resulting code and validation evidence.
Thanks,
Artem Dinaburg
kernel/bpf/helpers.c | 37 ++++++++++++++++++++++++++++++++++---
1 file changed, 34 insertions(+), 3 deletions(-)
diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index cf422c80b30b30..af16711a731ffe 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -1108,6 +1108,7 @@ const struct bpf_func_proto bpf_snprintf_proto = {
*/
struct bpf_hrtimer {
struct hrtimer timer;
+ atomic_t cancelling;
struct bpf_map *map;
struct bpf_prog *prog;
void __rcu *callback_fn;
@@ -1202,6 +1203,7 @@ BPF_CALL_3(bpf_timer_init, struct bpf_timer_kern *, timer, struct bpf_map *, map
t->map = map;
t->prog = NULL;
rcu_assign_pointer(t->callback_fn, NULL);
+ atomic_set(&t->cancelling, 0);
hrtimer_init(&t->timer, clockid, HRTIMER_MODE_REL_SOFT);
t->timer.function = bpf_timer_cb;
WRITE_ONCE(timer->timer, t);
@@ -1329,7 +1331,8 @@ static void drop_prog_refcnt(struct bpf_hrtimer *t)
BPF_CALL_1(bpf_timer_cancel, struct bpf_timer_kern *, timer)
{
- struct bpf_hrtimer *t;
+ struct bpf_hrtimer *t, *cur_t;
+ bool inc = false;
int ret = 0;
if (in_nmi())
@@ -1341,14 +1344,40 @@ BPF_CALL_1(bpf_timer_cancel, struct bpf_timer_kern *, timer)
ret = -EINVAL;
goto out;
}
- if (this_cpu_read(hrtimer_running) == t) {
+ cur_t = this_cpu_read(hrtimer_running);
+ if (cur_t == t) {
/* If bpf callback_fn is trying to bpf_timer_cancel()
* its own timer the hrtimer_cancel() will deadlock
- * since it waits for callback_fn to finish
+ * since it waits for callback_fn to finish.
+ */
+ ret = -EDEADLK;
+ goto out;
+ }
+
+ /* Only account in-flight cancellations when invoked from a timer
+ * callback, since we want to avoid waiting only if other _callbacks_
+ * are waiting on us, to avoid introducing lockups. Non-callback paths
+ * are ok, since nobody would synchronously wait for their completion.
+ */
+ if (!cur_t)
+ goto drop;
+ atomic_inc(&t->cancelling);
+ /* Need full barrier after relaxed atomic_inc */
+ smp_mb__after_atomic();
+ inc = true;
+ if (atomic_read(&cur_t->cancelling)) {
+ /* We're cancelling timer t, while some other timer callback is
+ * attempting to cancel us. In such a case, it might be possible
+ * that timer t belongs to the other callback, or some other
+ * callback waiting upon it (creating transitive dependencies
+ * upon us), and we will enter a deadlock if we continue
+ * cancelling and waiting for it synchronously, since it might
+ * do the same. Bail!
*/
ret = -EDEADLK;
goto out;
}
+drop:
drop_prog_refcnt(t);
out:
__bpf_spin_unlock_irqrestore(&timer->lock);
@@ -1356,6 +1385,8 @@ BPF_CALL_1(bpf_timer_cancel, struct bpf_timer_kern *, timer)
* if it was running.
*/
ret = ret ?: hrtimer_cancel(&t->timer);
+ if (inc)
+ atomic_dec(&t->cancelling);
rcu_read_unlock();
return ret;
}
--
2.39.5
^ permalink raw reply [flat|nested] 3+ messages in thread
* [PATCH 6.1.y 2/2] bpf: Defer work in bpf_timer_cancel_and_free
2026-10-02 19:30 [PATCH 6.1.y 0/2] bpf: backport CVE-2024-41045 and CVE-2024-42239 fixes Artem Dinaburg
2026-10-02 19:30 ` [PATCH 6.1.y 1/2] bpf: Fail bpf_timer_cancel when callback is being cancelled Artem Dinaburg
@ 2026-10-02 19:30 ` Artem Dinaburg
1 sibling, 0 replies; 3+ messages in thread
From: Artem Dinaburg @ 2026-10-02 19:30 UTC (permalink / raw)
To: stable
Cc: Artem Dinaburg, Greg Kroah-Hartman, Sasha Levin,
Kumar Kartikeya Dwivedi, Alexei Starovoitov, Daniel Borkmann,
Andrii Nakryiko, Martin KaFai Lau, Song Liu, Yonghong Song,
John Fastabend, KP Singh, Stanislav Fomichev, Hao Luo, Jiri Olsa,
bpf, linux-kernel, Eduard Zingerman, Emil Tsalapatis,
Ihor Solodrai, netdev, toke
From: Kumar Kartikeya Dwivedi <memxor@gmail.com>
[ Upstream commit a6fcd19d7eac1335eb76bc16b6a66b7f574d1d69 ]
Currently, the same case as previous patch (two timer callbacks trying
to cancel each other) can be invoked through bpf_map_update_elem as
well, or more precisely, freeing map elements containing timers. Since
this relies on hrtimer_cancel as well, it is prone to the same deadlock
situation as the previous patch.
It would be sufficient to use hrtimer_try_to_cancel to fix this problem,
as the timer cannot be enqueued after async_cancel_and_free. Once
async_cancel_and_free has been done, the timer must be reinitialized
before it can be armed again. The callback running in parallel trying to
arm the timer will fail, and freeing bpf_hrtimer without waiting is
sufficient (given kfree_rcu), and bpf_timer_cb will return
HRTIMER_NORESTART, preventing the timer from being rearmed again.
However, there exists a UAF scenario where the callback arms the timer
before entering this function, such that if cancellation fails (due to
timer callback invoking this routine, or the target timer callback
running concurrently). In such a case, if the timer expiration is
significantly far in the future, the RCU grace period expiration
happening before it will free the bpf_hrtimer state and along with it
the struct hrtimer, that is enqueued.
Hence, it is clear cancellation needs to occur after
async_cancel_and_free, and yet it cannot be done inline due to deadlock
issues. We thus modify bpf_timer_cancel_and_free to defer work to the
global workqueue, adding a work_struct alongside rcu_head (both used at
_different_ points of time, so can share space).
Update existing code comments to reflect the new state of affairs.
[ Backport to 6.1.y: mapped deferred cancellation onto the older pre-BPF-
workqueue timer object layout. ]
Fixes: b00628b1c7d5 ("bpf: Introduce bpf timers.")
Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
Link: https://lore.kernel.org/r/20240709185440.1104957-3-memxor@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
Assisted-by: LLM
Signed-off-by: Artem Dinaburg <artem@trailofbits.com>
---
Hi Greg, Sasha, and bpf maintainers,
I am working through the small CVE backports still missing from 6.1.y.
This one addresses CVE-2024-41045. It defers timer destruction so callbacks
cannot deadlock or leave an enqueued timer freed.
The corresponding 6.6.y backport is queued (6.6.158-rc1). The fix is
already present in 6.12.y, 6.18.y, and 7.2.y, but not in 6.1.y.
The target-specific adjustment is recorded in the bracketed note above.
Could you please queue it for 6.1.y?
CVE: CVE-2024-41045
Upstream: a6fcd19d7eac1335eb76bc16b6a66b7f574d1d69
AI assistance: An LLM helped identify, adapt, and validate this backport; I
reviewed the resulting code and validation evidence.
Thanks,
Artem Dinaburg
kernel/bpf/helpers.c | 58 +++++++++++++++++++++++++++++++++++---------
1 file changed, 46 insertions(+), 12 deletions(-)
diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index af16711a731ffe..e462962a1a12be 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -1113,7 +1113,10 @@ struct bpf_hrtimer {
struct bpf_prog *prog;
void __rcu *callback_fn;
void *value;
- struct rcu_head rcu;
+ union {
+ struct rcu_head rcu;
+ struct work_struct delete_work;
+ };
};
/* the actual struct hidden inside uapi struct bpf_timer */
@@ -1167,6 +1170,22 @@ static enum hrtimer_restart bpf_timer_cb(struct hrtimer *hrtimer)
return HRTIMER_NORESTART;
}
+static void bpf_timer_delete_work(struct work_struct *work)
+{
+ struct bpf_hrtimer *t = container_of(work, struct bpf_hrtimer,
+ delete_work);
+
+ /* Cancel the timer and wait for callback to complete if it was running.
+ * If hrtimer_cancel() can be safely called it's safe to call
+ * kfree_rcu(t) right after for both preallocated and non-preallocated
+ * maps. The timer->timer = NULL was already done and no code path can see
+ * address 't' anymore. A timer armed before bpf_timer_cancel_and_free()
+ * will have been cancelled.
+ */
+ hrtimer_cancel(&t->timer);
+ kfree_rcu(t, rcu);
+}
+
BPF_CALL_3(bpf_timer_init, struct bpf_timer_kern *, timer, struct bpf_map *, map,
u64, flags)
{
@@ -1204,6 +1223,7 @@ BPF_CALL_3(bpf_timer_init, struct bpf_timer_kern *, timer, struct bpf_map *, map
t->prog = NULL;
rcu_assign_pointer(t->callback_fn, NULL);
atomic_set(&t->cancelling, 0);
+ INIT_WORK(&t->delete_work, bpf_timer_delete_work);
hrtimer_init(&t->timer, clockid, HRTIMER_MODE_REL_SOFT);
t->timer.function = bpf_timer_cb;
WRITE_ONCE(timer->timer, t);
@@ -1424,14 +1444,8 @@ void bpf_timer_cancel_and_free(void *val)
__bpf_spin_unlock_irqrestore(&timer->lock);
if (!t)
return;
- /* Cancel the timer and wait for callback to complete if it was running.
- * If hrtimer_cancel() can be safely called it's safe to call kfree(t)
- * right after for both preallocated and non-preallocated maps.
- * The timer->timer = NULL was already done and no code path can
- * see address 't' anymore.
- *
- * Check that bpf_map_delete/update_elem() wasn't called from timer
- * callback_fn. In such case don't call hrtimer_cancel() (since it will
+ /* We check that bpf_map_delete/update_elem() was called from timer
+ * callback_fn. In such case we don't call hrtimer_cancel() (since it will
* deadlock) and don't call hrtimer_try_to_cancel() (since it will just
* return -1). Though callback_fn is still running on this cpu it's
* safe to do kfree(t) because bpf_timer_cb() read everything it needed
@@ -1439,10 +1453,30 @@ void bpf_timer_cancel_and_free(void *val)
* since timer->timer = NULL was already done. The timer will be
* effectively cancelled because bpf_timer_cb() will return
* HRTIMER_NORESTART.
+ *
+ * However, it is possible the timer callback_fn calling us armed the
+ * timer _before_ calling us, such that failing to cancel it here will
+ * cause it to possibly use struct hrtimer after freeing bpf_hrtimer.
+ * Therefore, we _need_ to cancel any outstanding timers before we do
+ * kfree_rcu, even though no more timers can be armed.
+ *
+ * Moreover, we need to schedule work even if timer does not belong to
+ * the calling callback_fn, as on two different CPUs, we can end up in a
+ * situation where both sides run in parallel, try to cancel one
+ * another, and we end up waiting on both sides in hrtimer_cancel
+ * without making forward progress, since timer1 depends on timer2
+ * callback to finish, and vice versa.
+ *
+ * CPU 1 (timer1_cb) CPU 2 (timer2_cb)
+ * bpf_timer_cancel_and_free(timer2) bpf_timer_cancel_and_free(timer1)
+ *
+ * To avoid these issues, punt to workqueue context when we are in a
+ * timer callback.
*/
- if (this_cpu_read(hrtimer_running) != t)
- hrtimer_cancel(&t->timer);
- kfree_rcu(t, rcu);
+ if (this_cpu_read(hrtimer_running))
+ queue_work(system_unbound_wq, &t->delete_work);
+ else
+ bpf_timer_delete_work(&t->delete_work);
}
BPF_CALL_2(bpf_kptr_xchg, void *, map_value, void *, ptr)
--
2.39.5
^ permalink raw reply [flat|nested] 3+ messages in thread
end of thread, other threads:[~2026-10-02 19:30 UTC | newest]
Thread overview: 3+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-02 19:30 [PATCH 6.1.y 0/2] bpf: backport CVE-2024-41045 and CVE-2024-42239 fixes Artem Dinaburg
2026-10-02 19:30 ` [PATCH 6.1.y 1/2] bpf: Fail bpf_timer_cancel when callback is being cancelled Artem Dinaburg
2026-10-02 19:30 ` [PATCH 6.1.y 2/2] bpf: Defer work in bpf_timer_cancel_and_free Artem Dinaburg
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®