BPF List
 help / color / mirror / Atom feed
* [PATCH bpf] bpf: fix UAF of trampoline progs before image release
@ 2026-08-15  7:19 Junseo Lim
  2026-08-15  8:03 ` bot+bpf-ci
  2026-08-17  4:54 ` Leon Hwang
  0 siblings, 2 replies; 3+ messages in thread
From: Junseo Lim @ 2026-08-15  7:19 UTC (permalink / raw)
  To: Alexei Starovoitov, Daniel Borkmann, Eduard Zingerman,
	Kumar Kartikeya Dwivedi, Andrii Nakryiko
  Cc: John Fastabend, Martin KaFai Lau, Song Liu, Yonghong Song,
	Jiri Olsa, Emil Tsalapatis, bpf, Sechang Lim, Paul E. McKenney

arch_prepare_bpf_trampoline() embeds program pointers in the generated
image and passes them to __bpf_prog_enter_recur(). After
bpf_trampoline_update() replaces the image, bpf_tramp_image_put() can keep
the old image executable past a normal RCU grace period.

A detached non-sleepable prog can therefore be freed before a preempted
task reaches rcu_read_lock_dont_migrate() in __bpf_prog_enter_recur(),
leading to a use-after-free.

Keep image-local prog refs and drop them from bpf_tramp_image_free().

Fixes: e21aa341785c ("bpf: Fix fexit trampoline.")
Reported-by: Sechang Lim <rhkrqnwk98@gmail.com>
Signed-off-by: Junseo Lim <zirajs7@gmail.com>
---
This issue was found by a custom fuzzer developed by
Sechang Lim <rhkrqnwk98@gmail.com>.

This issue was reproduced on bpf/master commit a13307e97d5c.
Below is the KASAN report:

    ==================================================================
    BUG: KASAN: vmalloc-out-of-bounds in __bpf_prog_enter_recur+0x3a5/0x3f0
    Read of size 8 at addr ffffc90000081040 by task candidate/114
    
    CPU: 0 UID: 0 PID: 114 Comm: candidate Not tainted 7.2.0-rc6-00341-ga13307e97d5c #120 PREEMPT(full)
    Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS Arch Linux 1.17.0-2-2 04/01/2014
    Call Trace:
     <TASK>
     dump_stack_lvl+0xb0/0x110
     print_report+0x14b/0x4a4
     ? preempt_schedule_notrace_thunk+0x16/0x30
     ? srso_alias_return_thunk+0x5/0xfbef5
     ? preempt_schedule_notrace_thunk+0x16/0x30
     kasan_report+0x108/0x130
     ? __bpf_prog_enter_recur+0x3a5/0x3f0
     ? __bpf_prog_enter_recur+0x3a5/0x3f0
     __bpf_prog_enter_recur+0x3a5/0x3f0
     bpf_trampoline_6442508591+0x32/0xa7
     __x64_sys_futex+0x9/0x410
     do_syscall_64+0xae/0x5e0
     ? srso_alias_return_thunk+0x5/0xfbef5
     ? srso_alias_return_thunk+0x5/0xfbef5
     entry_SYSCALL_64_after_hwframe+0x76/0x7e
    RIP: 0033:0x429f4d
    Code: d5 48 8d 3c 0a eb 91 66 0f 1f 44 00 00 f3 0f 1e fa 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 e8 ff ff ff f7 d8 64 89 01 48
    RSP: 002b:00007f2332067128 EFLAGS: 00000246 ORIG_RAX: 00000000000000ca
    RAX: ffffffffffffffda RBX: 00007f2332067ce4 RCX: 0000000000429f4d
    RDX: 0000000000000000 RSI: 0000000000000001 RDI: 0000000000000000
    RBP: 00007f23320672b0 R08: 0000000000000000 R09: 0000000000000000
    R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000021
    R13: 00007ffd9ad0b660 R14: 0000000000000010 R15: 00007ffd9ad0b757
     </TASK>
    
    The buggy address belongs to a vmalloc virtual mapping
    Memory state around the buggy address:
     ffffc90000080f00: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
     ffffc90000080f80: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
    >ffffc90000081000: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
                                               ^
     ffffc90000081080: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
     ffffc90000081100: f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8 f8
    ==================================================================

 include/linux/bpf.h     |  5 +++++
 kernel/bpf/trampoline.c | 20 ++++++++++++++++++++
 2 files changed, 25 insertions(+)

diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index 7719f6528445..bacb6bc2e27b 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -1376,6 +1376,11 @@ struct bpf_tramp_image {
 		struct rcu_head rcu;
 		struct work_struct work;
 	};
+#ifdef CONFIG_PREEMPTION
+	/* Programs called from this image must outlive deferred image freeing. */
+	struct bpf_prog *progs[BPF_MAX_TRAMP_LINKS];
+	int nr_progs;
+#endif
 };
 
 struct bpf_trampoline {
diff --git a/kernel/bpf/trampoline.c b/kernel/bpf/trampoline.c
index 1a721fc4bef5..ff6b331c965b 100644
--- a/kernel/bpf/trampoline.c
+++ b/kernel/bpf/trampoline.c
@@ -531,6 +531,13 @@ bpf_trampoline_get_progs(const struct bpf_trampoline *tr, int *total, bool *ip_a
 
 static void bpf_tramp_image_free(struct bpf_tramp_image *im)
 {
+#ifdef CONFIG_PREEMPTION
+	int i;
+
+	for (i = 0; i < im->nr_progs; i++)
+		bpf_prog_put(im->progs[i]);
+#endif
+
 	bpf_image_ksym_del(&im->ksym);
 	arch_free_bpf_trampoline(im->image, im->size);
 	bpf_jit_uncharge_modmem(im->size);
@@ -678,6 +685,9 @@ static int bpf_trampoline_update(struct bpf_trampoline *tr, bool lock_direct_mut
 	u32 orig_flags = tr->flags;
 	bool ip_arg = false;
 	int err, total, size;
+#ifdef CONFIG_PREEMPTION
+	int i, kind;
+#endif
 
 	tnodes = bpf_trampoline_get_progs(tr, &total, &ip_arg);
 	if (IS_ERR(tnodes))
@@ -740,6 +750,16 @@ static int bpf_trampoline_update(struct bpf_trampoline *tr, bool lock_direct_mut
 		goto out;
 	}
 
+#ifdef CONFIG_PREEMPTION
+	for (kind = 0; kind < BPF_TRAMP_MAX; kind++)
+		for (i = 0; i < tnodes[kind].nr_nodes; i++) {
+			struct bpf_prog *prog = tnodes[kind].nodes[i]->link->prog;
+
+			bpf_prog_inc(prog);
+			im->progs[im->nr_progs++] = prog;
+		}
+#endif
+
 	err = arch_prepare_bpf_trampoline(im, im->image, im->image + size,
 					  &tr->func.model, tr->flags, tnodes,
 					  tr->func.addr);
-- 
2.55.0


^ permalink raw reply related	[flat|nested] 3+ messages in thread

* Re: [PATCH bpf] bpf: fix UAF of trampoline progs before image release
  2026-08-15  7:19 [PATCH bpf] bpf: fix UAF of trampoline progs before image release Junseo Lim
@ 2026-08-15  8:03 ` bot+bpf-ci
  2026-08-17  4:54 ` Leon Hwang
  1 sibling, 0 replies; 3+ messages in thread
From: bot+bpf-ci @ 2026-08-15  8:03 UTC (permalink / raw)
  To: zirajs7, ast, daniel, eddyz87, memxor, andrii
  Cc: john.fastabend, martin.lau, song, yonghong.song, jolsa, emil, bpf,
	rhkrqnwk98, paulmck, ast, andrii, daniel, martin.lau, eddyz87,
	yonghong.song, clm, ihor.solodrai

[-- Attachment #1: Type: text/plain, Size: 2728 bytes --]

> diff --git a/include/linux/bpf.h b/include/linux/bpf.h
> index 7719f6528445..bacb6bc2e27b 100644
> --- a/include/linux/bpf.h
> +++ b/include/linux/bpf.h
> @@ -1376,6 +1376,11 @@ struct bpf_tramp_image {
>  		struct rcu_head rcu;
>  		struct work_struct work;
>  	};
> +#ifdef CONFIG_PREEMPTION
> +	/* Programs called from this image must outlive deferred image freeing. */
> +	struct bpf_prog *progs[BPF_MAX_TRAMP_LINKS];
> +	int nr_progs;
> +#endif
>  };

This isn't a bug, but since struct bpf_tramp_image is also instantiated on
the stack by arch_bpf_trampoline_size() on several architectures, would a
separately kmalloc'd array of size total (or reusing the tnodes array that
bpf_trampoline_update() already has) keep both the struct and those stack
frames from growing by around 312 bytes?

> diff --git a/kernel/bpf/trampoline.c b/kernel/bpf/trampoline.c
> index 1a721fc4bef5..ff6b331c965b 100644
> --- a/kernel/bpf/trampoline.c
> +++ b/kernel/bpf/trampoline.c
> @@ -531,6 +531,13 @@ bpf_trampoline_get_progs(const struct bpf_trampoline *tr, int *total, bool *ip_a
>
>  static void bpf_tramp_image_free(struct bpf_tramp_image *im)
>  {
> +#ifdef CONFIG_PREEMPTION
> +	int i;
> +
> +	for (i = 0; i < im->nr_progs; i++)
> +		bpf_prog_put(im->progs[i]);
> +#endif
> +
>  	bpf_image_ksym_del(&im->ksym);
>  	arch_free_bpf_trampoline(im->image, im->size);
>  	bpf_jit_uncharge_modmem(im->size);
> @@ -678,6 +685,9 @@ static int bpf_trampoline_update(struct bpf_trampoline *tr, bool lock_direct_mut
>  	u32 orig_flags = tr->flags;
>  	bool ip_arg = false;
>  	int err, total, size;
> +#ifdef CONFIG_PREEMPTION
> +	int i, kind;
> +#endif
>
>  	tnodes = bpf_trampoline_get_progs(tr, &total, &ip_arg);
>  	if (IS_ERR(tnodes))
> @@ -740,6 +750,16 @@ static int bpf_trampoline_update(struct bpf_trampoline *tr, bool lock_direct_mut
>  		goto out;
>  	}
>
> +#ifdef CONFIG_PREEMPTION
> +	for (kind = 0; kind < BPF_TRAMP_MAX; kind++)
> +		for (i = 0; i < tnodes[kind].nr_nodes; i++) {
> +			struct bpf_prog *prog = tnodes[kind].nodes[i]->link->prog;
> +
> +			bpf_prog_inc(prog);
> +			im->progs[im->nr_progs++] = prog;
> +		}
> +#endif
> +
>  	err = arch_prepare_bpf_trampoline(im, im->image, im->image + size,
>  					  &tr->func.model, tr->flags, tnodes,
>  					  tr->func.addr);

This isn't a bug, but could the changelog say a sentence about why the
new references are only needed under CONFIG_PREEMPTION, mirroring the
!PREEMPT reasoning already written out in bpf_tramp_image_put()?


---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/31872362891

^ permalink raw reply	[flat|nested] 3+ messages in thread

* Re: [PATCH bpf] bpf: fix UAF of trampoline progs before image release
  2026-08-15  7:19 [PATCH bpf] bpf: fix UAF of trampoline progs before image release Junseo Lim
  2026-08-15  8:03 ` bot+bpf-ci
@ 2026-08-17  4:54 ` Leon Hwang
  1 sibling, 0 replies; 3+ messages in thread
From: Leon Hwang @ 2026-08-17  4:54 UTC (permalink / raw)
  To: Junseo Lim, Alexei Starovoitov, Daniel Borkmann, Eduard Zingerman,
	Kumar Kartikeya Dwivedi, Andrii Nakryiko
  Cc: John Fastabend, Martin KaFai Lau, Song Liu, Yonghong Song,
	Jiri Olsa, Emil Tsalapatis, bpf, Sechang Lim, Paul E. McKenney

On 15/8/26 15:19, Junseo Lim wrote:
[...]
> diff --git a/include/linux/bpf.h b/include/linux/bpf.h
> index 7719f6528445..bacb6bc2e27b 100644
> --- a/include/linux/bpf.h
> +++ b/include/linux/bpf.h
> @@ -1376,6 +1376,11 @@ struct bpf_tramp_image {
>  		struct rcu_head rcu;
>  		struct work_struct work;
>  	};
> +#ifdef CONFIG_PREEMPTION
> +	/* Programs called from this image must outlive deferred image freeing. */
> +	struct bpf_prog *progs[BPF_MAX_TRAMP_LINKS];
> +	int nr_progs;
> +#endif
>  };
>  
>  struct bpf_trampoline {
> diff --git a/kernel/bpf/trampoline.c b/kernel/bpf/trampoline.c
> index 1a721fc4bef5..ff6b331c965b 100644
> --- a/kernel/bpf/trampoline.c
> +++ b/kernel/bpf/trampoline.c
> @@ -531,6 +531,13 @@ bpf_trampoline_get_progs(const struct bpf_trampoline *tr, int *total, bool *ip_a
>  
>  static void bpf_tramp_image_free(struct bpf_tramp_image *im)
>  {
> +#ifdef CONFIG_PREEMPTION
> +	int i;
> +
> +	for (i = 0; i < im->nr_progs; i++)
> +		bpf_prog_put(im->progs[i]);
> +#endif

Looks better to factor out a helper for it, e.g.
bpf_tramp_image_put_progs().

> +
>  	bpf_image_ksym_del(&im->ksym);
>  	arch_free_bpf_trampoline(im->image, im->size);
>  	bpf_jit_uncharge_modmem(im->size);
> @@ -678,6 +685,9 @@ static int bpf_trampoline_update(struct bpf_trampoline *tr, bool lock_direct_mut
>  	u32 orig_flags = tr->flags;
>  	bool ip_arg = false;
>  	int err, total, size;
> +#ifdef CONFIG_PREEMPTION
> +	int i, kind;
> +#endif
>  
>  	tnodes = bpf_trampoline_get_progs(tr, &total, &ip_arg);
>  	if (IS_ERR(tnodes))
> @@ -740,6 +750,16 @@ static int bpf_trampoline_update(struct bpf_trampoline *tr, bool lock_direct_mut
>  		goto out;
>  	}
>  
> +#ifdef CONFIG_PREEMPTION
> +	for (kind = 0; kind < BPF_TRAMP_MAX; kind++)
> +		for (i = 0; i < tnodes[kind].nr_nodes; i++) {
> +			struct bpf_prog *prog = tnodes[kind].nodes[i]->link->prog;
> +
> +			bpf_prog_inc(prog);
> +			im->progs[im->nr_progs++] = prog;
> +		}
> +#endif

Ditto.

Thanks,
Leon

> +
>  	err = arch_prepare_bpf_trampoline(im, im->image, im->image + size,
>  					  &tr->func.model, tr->flags, tnodes,
>  					  tr->func.addr);


^ permalink raw reply	[flat|nested] 3+ messages in thread

end of thread, other threads:[~2026-08-17  4:54 UTC | newest]

Thread overview: 3+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-15  7:19 [PATCH bpf] bpf: fix UAF of trampoline progs before image release Junseo Lim
2026-08-15  8:03 ` bot+bpf-ci
2026-08-17  4:54 ` Leon Hwang

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox