All of lore.kernel.org
 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 an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.