mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: "Florent Revest" <florent.revest@linux.dev>
To: <bot+bpf-ci@kernel.org>, <bpf@vger.kernel.org>, <ast@kernel.org>,
	<daniel@iogearbox.net>, <andrii@kernel.org>
Cc: <martin.lau@linux.dev>, <eddyz87@gmail.com>, <memxor@gmail.com>,
	<song@kernel.org>, <yonghong.song@linux.dev>, <jolsa@kernel.org>,
	<kpsingh@kernel.org>, <john.fastabend@gmail.com>,
	<leon.hwang@linux.dev>, <zirajs7@gmail.com>,
	<rhkrqnwk98@gmail.com>, <puranjay@kernel.org>,
	<xukuohai@huaweicloud.com>, <iii@linux.ibm.com>,
	<hbathini@linux.ibm.com>, <chleroy@kernel.org>,
	<naveen@kernel.org>, <bjorn@kernel.org>, <pulehui@huawei.com>,
	<yangtiezhu@loongson.cn>, <hengqi.chen@gmail.com>,
	<linux-kernel@vger.kernel.org>, <martin.lau@kernel.org>,
	<eddyz87@gmail.com>, <yonghong.song@linux.dev>,
	<mason@kernel.org>, <ihor.solodrai@linux.dev>
Subject: Re: [PATCH bpf v3 2/3] bpf: Skip the progs of trampoline images that are being freed
Date: Fri, 25 Sep 2026 09:52:23 +0000	[thread overview]
Message-ID: <DLOAZXLJA4AS.1AJ6HN97DR26B@linux.dev> (raw)
In-Reply-To: <1af4e60e20bc3fa1547c0ad4a8d72b904535ec2a4c40d620d59142bb630742f3@mail.kernel.org>

On Thu Sep 24, 2026 at 6:07 PM UTC,  wrote:
> > diff --git a/arch/x86/net/bpf_jit_comp.c b/arch/x86/net/bpf_jit_comp.c
> > index 2853e87797a7..7ef5c8efc882 100644
> > --- a/arch/x86/net/bpf_jit_comp.c
> > +++ b/arch/x86/net/bpf_jit_comp.c
> > @@ -3217,16 +3217,21 @@ static void restore_regs(const struct btf_func_model *m, u8 **prog,
> >  }
> >
> >  static int invoke_bpf_prog(const struct btf_func_model *m, u8 **pprog,
> > +			   struct bpf_tramp_image *im,
> >  			   struct bpf_tramp_node *node, int stack_size,
> >  			   int run_ctx_off, bool save_ret,
> >  			   void *image, void *rw_image)
> >  {
> >  	u8 *prog = *pprog;
> > -	u8 *jmp_insn;
> > +	u8 *jmp_insn, *skip;
> >  	int ctx_cookie_off = offsetof(struct bpf_tramp_run_ctx, bpf_cookie);
> >  	struct bpf_prog *p = node->link->prog;
> >  	u64 cookie = node->cookie;
> >
> > +	/* nop, patched to skip this prog when the image is put */
> > +	skip = image + (prog - (u8 *)rw_image);
> > +	emit_nops(&prog, X86_PATCH_SIZE);
> > +
>
> The patch adds a 5-byte nop in front of every prog call sequence on x86
> but keeps BPF_MAX_TRAMP_LINKS at 38. This adds 38 * 5 - 5 = 185 bytes to
> the worst case image size, which was already close to PAGE_SIZE.
>
> Example worst case: 19 fsession progs (38 link slots because each counts
> twice) attached to a function with 7-9 argument registers, each calling
> bpf_session_cookie() and bpf_get_func_ip() with a 64-bit link cookie.
>
> With 7 args the image reaches about 4040-4043 bytes, above
> PAGE_SIZE - BPF_INSN_SAFETY = 4032. With 10 args it reaches about
> 4106-4109 bytes. An attach that used to succeed now fails with -EFAULT
> and a WARN_ON_ONCE() splat.
>
> The size pass writes to a single PAGE_SIZE execmem buffer with only an
> end-of-pass WARN_ON_ONCE check:
>
> arch/x86/net/bpf_jit_comp.c:arch_bpf_trampoline_size() {
>     image = execmem_alloc(EXECMEM_MODULE_DATA, PAGE_SIZE);
>     ret = __arch_prepare_bpf_trampoline(&im, image, image + PAGE_SIZE, ...);
>
> arch/x86/net/bpf_jit_comp.c:__arch_prepare_bpf_trampoline() {
>     if (WARN_ON_ONCE(prog > (u8 *)rw_image_end - BPF_INSN_SAFETY)) {
>         ret = -EFAULT;
>         goto cleanup;
>     }
>
> With 10 args the size pass writes past the end of the execmem buffer
> before the check is reached.
>
> The commit message says the extra nops break the page budget only on
> arm64 and loongarch. Should x86 get a lower BPF_MAX_TRAMP_LINKS too, or
> should the x86 size pass bound-check as it emits?

Yep, v4 will lower it to 36.

> [ ... ]
>
> > @@ -3368,7 +3377,7 @@ static int invoke_bpf_mod_ret(const struct btf_func_model *m, u8 **pprog,
> >  	emit_mov_imm32(&prog, false, BPF_REG_0, 0);
> >  	emit_stx(&prog, BPF_DW, BPF_REG_FP, BPF_REG_0, -8);
> >  	for (i = 0; i < tl->nr_nodes; i++) {
> > -		if (invoke_bpf_prog(m, &prog, tl->nodes[i], stack_size, run_ctx_off, true,
> > +		if (invoke_bpf_prog(m, &prog, im, tl->nodes[i], stack_size, run_ctx_off, true,
> >  				    image, rw_image))
> >  			return -EINVAL;
> >
> >  		/* mod_ret prog stored return value into [rbp - 8]. Emit:
> >  		 * if (*(u64 *)(rbp - 8) !=  0)
> >  		 *	goto do_fexit;
> >  		 */
>
> invoke_bpf_mod_ret() now passes im to invoke_bpf_prog(), so every
> fmod_ret prog gets a skip nop that bpf_tramp_image_put() patches to a
> jump. The skip target is before the cmp/jne check. A skipped fmod_ret
> prog therefore leaves the previous return value in place (0 from the
> initial mov after xor eax,eax), and the original function is called.
>
> Before this change only fexit progs were skipped in a put image, and
> those only observe. fmod_ret progs always ran. Any trampoline update puts
> the old image, including attaching or detaching an unrelated prog.
>
> BPF LSM progs on int hooks become BPF_TRAMP_MODIFY_RETURN:
>
> kernel/bpf/verifier.c:bpf_attach_type_to_tramp() {
>     case BPF_LSM_MAC:
>         if (!prog->aux->attach_func_proto->type)
>             return BPF_TRAMP_FEXIT;
>         else
>             return BPF_TRAMP_MODIFY_RETURN;
>
> Hooks such as bpf_lsm_file_open and bpf_lsm_bprm_check_security are in
> sleepable_lsm_hooks. A possible sequence:
>
>   task T in image I of bpf_lsm_file_open,
>     sleeping in sleepable LSM prog S (returns 0)
>                                      attach another prog to the hook
>                                       -> new image, bpf_tramp_image_put(I)
>                                       -> nop before enforcing prog P patched to jmp
>   T returns from S
>   jmp over P (P is still attached)
>   cmp [rbp-8], 0 -> equal, call bpf_lsm_file_open() -> 0
>   open allowed although P would have denied it
>
> On a preemptible kernel the window exists even without sleepable progs:
> anywhere between trampoline entry and P's nop.
>
> The commit message says still-attached progs are skipped "like they
> already skipped its fexit progs". For fmod_ret, though, skipping changes
> the traced function's return value instead of just losing an event.
>
> Is it intended that a concurrent attach or detach on the same hook can
> bypass an attached LSM or fmod_ret prog?

As discussed with Alexei, v4 goes back to only skipping the prog that is
detached.

> > diff --git a/include/linux/bpf.h b/include/linux/bpf.h
> > index d4f732996b47..9c61803380c2 100644
> > --- a/include/linux/bpf.h
> > +++ b/include/linux/bpf.h
> > @@ -1258,11 +1258,15 @@ struct btf_func_model {
> >  #define BPF_TRAMP_F_INDIRECT		BIT(8)
> >
> >  /* Each call __bpf_prog_enter + call bpf_func + call __bpf_prog_exit is ~50
> > - * bytes on x86.
> > + * bytes on x86. The trampoline image has to fit in PAGE_SIZE.
> >   */
> >  enum {
> > -#if defined(__s390x__)
> > +#if defined(__s390x__) || defined(__powerpc64__)
> >  	BPF_MAX_TRAMP_LINKS = 27,
> > +#elif defined(__loongarch__)
> > +	BPF_MAX_TRAMP_LINKS = 33,
> > +#elif defined(__aarch64__)
> > +	BPF_MAX_TRAMP_LINKS = 37,
> >  #else
> >  	BPF_MAX_TRAMP_LINKS = 38,
> >  #endif
>
> BPF_MAX_TRAMP_LINKS drops from 38 to 27 for every __powerpc64__ build
> (and to 37 for every __aarch64__ build), whatever the page size. The
> updated comment says the constraint is that the image has to fit in
> PAGE_SIZE, and bpf_trampoline_update() does check the size against
> PAGE_SIZE.
>
> PPC_64K_PAGES is the default for PPC_BOOK3S_64, and 38 progs fit easily
> in a 64K image. On common ppc64le configurations (and arm64 with 16K/64K
> pages), attaching the 28th to 38th prog to one function now fails with
> -E2BIG where it used to work.
>
> The commit message's "already didn't on powerpc" only holds for 4K pages.
>
> Could the limit depend on PAGE_SIZE instead, keeping 38 when the page is
> larger than 4K?

Alexei prefers to leave that for later, so v4 doesn't change it.

> > diff --git a/kernel/bpf/trampoline.c b/kernel/bpf/trampoline.c
> > index 9d69c066a817..0d7aa1b0fe30 100644
> > --- a/kernel/bpf/trampoline.c
> > +++ b/kernel/bpf/trampoline.c
>
> [ ... ]
>
> > @@ -611,6 +612,8 @@ static void __bpf_tramp_image_put_rcu_tasks(struct rcu_head *rcu)
> >
> >  static void bpf_tramp_image_put(struct bpf_tramp_image *im)
> >  {
> > +	int i, err;
> > +
> >  	/* The trampoline image that calls original function is using:
> >  	 * rcu_read_lock_trace to protect sleepable bpf progs
> >  	 * rcu_read_lock to protect normal bpf progs
> > @@ -621,10 +624,17 @@ static void bpf_tramp_image_put(struct bpf_tramp_image *im)
> >  	 *
> >  	 * The trampoline is unreachable before bpf_tramp_image_put().
> >  	 *
> > -	 * First, patch the trampoline to avoid calling into fexit progs.
> > -	 * The progs will be freed even if the original function is still
> > -	 * executing or sleeping.
> > -	 * In case of CONFIG_PREEMPT=y use call_rcu_tasks() to wait on
> > +	 * First, patch the trampoline to avoid calling into progs. The progs
> > +	 * will be freed even if a task is still in the trampoline, e.g.
> > +	 * sleeping in the original function or in a sleepable prog.
> > +	 */
> > +	for (i = 0; i < im->nr_skips; i++) {
> > +		err = bpf_arch_text_poke(im->skips[i].nop, BPF_MOD_NOP,
> > +					 BPF_MOD_JUMP, NULL, im->skips[i].target);
> > +		WARN_ON_ONCE(err);
> > +	}
>
> Now that every prog nop in the old image is patched, a task still in that
> image also skips the fmod_ret progs that are still attached, not only
> fentry/fexit progs.
>
> Before this change only ip_after_call was patched. It sits after the
> original function call, so fentry and fmod_ret progs always ran and only
> fexit progs could be skipped.
>
> The commit message justifies skipping attached progs "like they already
> skipped its fexit progs". That comparison holds for observe-only
> fentry/fexit progs, but fmod_ret progs decide the return value and
> whether the original function runs.
>
> BPF LSM progs on int-returning hooks are attached as
> BPF_TRAMP_MODIFY_RETURN. Scenario:
>
> 1. Task T enters image I_old for bpf_lsm_file_open. It is either
>    preempted or sleeping in an earlier sleepable lsm.s prog, e.g. blocked
>    in bpf_copy_from_user(). Either way it has not yet reached the nop of
>    enforcing LSM prog L.
>
> 2. Someone attaches or detaches any other prog on the same trampoline.
>    bpf_trampoline_update() -> modify_fentry() -> bpf_tramp_image_put(I_old)
>    turns all nops of I_old into jumps, including L's.
>
> 3. T resumes and jumps over L's call sequence. In x86 invoke_bpf_mod_ret()
>    the result slot [rbp-8] was zeroed and nothing stores to it, so the
>    following cmp/jne does not branch to do_fexit. The original
>    bpf_lsm_file_open() stub is called and returns 0.
>
> The hook fails open for T even though L is still attached and would have
> denied access. The sequential pokes can also leave T running some
> fmod_ret progs but not others. The sleepable case makes the window as
> long as the earlier prog blocks.
>
> Should fmod_ret progs that are still attached keep running, with only the
> progs being removed skipped? Otherwise, is this fail-open behaviour for
> BPF LSM and fmod_ret acceptable, and should the commit message and comment
> say so?
>
> > +
> > +	/* In case of CONFIG_PREEMPT=y use call_rcu_tasks() to wait on
> >  	 * first few asm instructions to execute and call into
> >  	 * __bpf_tramp_enter->percpu_ref_get.
> >  	 * Then use percpu_ref_kill to wait for the trampoline and the original
>
> This isn't a bug, but the multi-line comment has the opening slash-star on
> the same line as the comment text. BPF subsystem style in kernel/bpf/ has
> the opening slash-star on its own line for new multi-line comments:
>
> 	/*
> 	 * In case of CONFIG_PREEMPT=y use call_rcu_tasks() to wait on
> 	 * first few asm instructions to execute and call into

Fixed in v4.

>
> ---
> 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/36034251810


  reply	other threads:[~2026-09-25  9:52 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-24 17:05 [PATCH bpf v3 0/3] bpf: Fix use-after-free of progs detached from busy trampolines Florent Revest (Anthropic)
2026-09-24 17:05 ` [PATCH bpf v3 1/3] bpf: Wait for an RCU tasks grace period before freeing trampoline progs Florent Revest (Anthropic)
2026-09-24 17:05 ` [PATCH bpf v3 2/3] bpf: Skip the progs of trampoline images that are being freed Florent Revest (Anthropic)
2026-09-24 18:07   ` bot+bpf-ci
2026-09-25  9:52     ` Florent Revest [this message]
2026-09-25  0:35   ` Alexei Starovoitov
2026-09-25  9:23     ` Florent Revest
2026-09-24 17:05 ` [PATCH bpf v3 3/3] selftests/bpf: Detach a trampoline prog while a task sleeps before it Florent Revest (Anthropic)
2026-09-24 17:53   ` bot+bpf-ci
2026-09-25  9:59     ` Florent Revest

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=DLOAZXLJA4AS.1AJ6HN97DR26B@linux.dev \
    --to=florent.revest@linux.dev \
    --cc=andrii@kernel.org \
    --cc=ast@kernel.org \
    --cc=bjorn@kernel.org \
    --cc=bot+bpf-ci@kernel.org \
    --cc=bpf@vger.kernel.org \
    --cc=chleroy@kernel.org \
    --cc=daniel@iogearbox.net \
    --cc=eddyz87@gmail.com \
    --cc=hbathini@linux.ibm.com \
    --cc=hengqi.chen@gmail.com \
    --cc=ihor.solodrai@linux.dev \
    --cc=iii@linux.ibm.com \
    --cc=john.fastabend@gmail.com \
    --cc=jolsa@kernel.org \
    --cc=kpsingh@kernel.org \
    --cc=leon.hwang@linux.dev \
    --cc=linux-kernel@vger.kernel.org \
    --cc=martin.lau@kernel.org \
    --cc=martin.lau@linux.dev \
    --cc=mason@kernel.org \
    --cc=memxor@gmail.com \
    --cc=naveen@kernel.org \
    --cc=pulehui@huawei.com \
    --cc=puranjay@kernel.org \
    --cc=rhkrqnwk98@gmail.com \
    --cc=song@kernel.org \
    --cc=xukuohai@huaweicloud.com \
    --cc=yangtiezhu@loongson.cn \
    --cc=yonghong.song@linux.dev \
    --cc=zirajs7@gmail.com \
    /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®