BPF List
 help / color / mirror / Atom feed
From: Anton Protopopov <a.s.protopopov@gmail.com>
To: Daniel Borkmann <daniel@iogearbox.net>
Cc: ast@kernel.org, memxor@gmail.com, eddyz87@gmail.com,
	info@starlabs.sg, bpf@vger.kernel.org,
	James Burton <jamesburton@meta.com>,
	Nuoqi Gui <gnq25@mails.tsinghua.edu.cn>
Subject: Re: [PATCH bpf 4/6] bpf: Reject indirect jumps that leave their subprogram
Date: Thu, 10 Sep 2026 12:10:09 +0000	[thread overview]
Message-ID: <aqKeIVyaAXFXgT8Q@mail.gmail.com> (raw)
In-Reply-To: <20260909204035.24289-4-daniel@iogearbox.net>

On 26/09/09 10:40PM, Daniel Borkmann wrote:
> The jump table of a subprog is collected in compute_subprog_jts() from the
> insn_array maps of the program, and a map is attributed to the subprog that
> contains its first entry. check_indirect_jump() instead resolves the targets
> from the map the gotox register actually points to, bounded only by the
> index range of that register, and never relates them back to the subprog
> of the gotox.
> 
> The two disagree, so bpf_insn_successors() reports a subset of the edges the
> BPF program can take and a gotox can enter a subprog the CFG never walked.
> The x86 epilogue there pops the callee saved registers of its own subprog and
> leaves the ones pushed by the current prologue unrestored, handing rbx, r13,
> r14 and r15 to the kernel with the values the BPF program left in them.

Why only x86? Maybe just remove the three lines above?

> 
> Close both ends in check_indirect_jump(): confine the resolved targets to the
> subprog of the gotox, and require each of them to be present in the jump table
> the CFG walked, that is, in the successor set bpf_insn_successors() reported
> for this instruction. The latter is the invariant that actually has to hold,
> the former is kept because it names the problem the BPF program has.
> 
> Fixes: 493d9e0d6083 ("bpf, x86: add support for indirect jumps")
> Reported-by: James Burton <jamesburton@meta.com>
> Reported-by: Nuoqi Gui <gnq25@mails.tsinghua.edu.cn>
> Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
> ---
>  include/linux/bpf_verifier.h                  |  1 +
>  kernel/bpf/cfg.c                              | 35 +++++-----
>  kernel/bpf/verifier.c                         | 65 +++++++++++++++++++
>  .../selftests/bpf/progs/verifier_gotox.c      |  2 +-
>  4 files changed, 85 insertions(+), 18 deletions(-)
> 
> diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h
> index 301a47d2b272..baf2e17d7019 100644
> --- a/include/linux/bpf_verifier.h
> +++ b/include/linux/bpf_verifier.h
> @@ -826,6 +826,7 @@ struct bpf_subprog_info {
>  	bool keep_fastcall_stack: 1;
>  	bool changes_pkt_data: 1;
>  	bool might_sleep: 1;
> +	bool jt_spans_subprogs: 1;
>  	u8 arg_cnt:4;
>  
>  	enum priv_stack_mode priv_stack_mode;
> diff --git a/kernel/bpf/cfg.c b/kernel/bpf/cfg.c
> index 8aee94689229..879587af8d08 100644
> --- a/kernel/bpf/cfg.c
> +++ b/kernel/bpf/cfg.c
> @@ -315,6 +315,11 @@ static int compute_subprog_jts(struct bpf_verifier_env *env)
>  			kvfree(jt_cur);
>  			continue;
>  		}
> +		if (jt_cur->items[jt_cur->cnt - 1] >= (subprog + 1)->start) {
> +			subprog->jt_spans_subprogs = true;
> +			kvfree(jt_cur);
> +			continue;
> +		}
>  
>  		old_cnt = subprog->jt ? subprog->jt->cnt : 0;
>  		jt = bpf_iarray_realloc(subprog->jt, old_cnt + jt_cur->cnt);
> @@ -346,6 +351,7 @@ static void free_subprog_jts(struct bpf_verifier_env *env)
>  	for (i = 0; i < ARRAY_SIZE(env->subprog_info); i++) {
>  		kvfree(env->subprog_info[i].jt);
>  		env->subprog_info[i].jt = NULL;
> +		env->subprog_info[i].jt_spans_subprogs = false;
>  	}
>  	env->cfg.subprog_jts_ready = false;
>  }
> @@ -354,9 +360,8 @@ static struct bpf_iarray *
>  create_jt(int t, struct bpf_verifier_env *env)
>  {
>  	struct bpf_subprog_info *subprog;
> -	int subprog_start, subprog_end;
>  	struct bpf_iarray *jt;
> -	int i, err;
> +	int subprog_start, err;
>  
>  	if (!env->cfg.subprog_jts_ready) {
>  		err = compute_subprog_jts(env);
> @@ -366,7 +371,17 @@ create_jt(int t, struct bpf_verifier_env *env)
>  
>  	subprog = bpf_find_containing_subprog(env, t);
>  	subprog_start = subprog->start;
> -	subprog_end = (subprog + 1)->start;
> +
> +	if (subprog->jt_spans_subprogs) {
> +		verbose(env, "jump table of subprog starting at %u spans multiple subprogs\n",
> +			subprog_start);
> +		bpf_diag_program_structure(
> +			env, subprog_start, "jump table spans subprograms",
> +			"Keep every entry of a jump table inside one subprogram.",
> +			"A jump table found for the subprogram that starts at instruction %u reaches past its end at instruction %u.",
> +			subprog_start, (subprog + 1)->start);
> +		return ERR_PTR(-EINVAL);
> +	}
>  	if (!subprog->jt) {
>  		verbose(env, "no jump tables found for subprog starting at %u\n", subprog_start);
> @@ -383,20 +398,6 @@ create_jt(int t, struct bpf_verifier_env *env)
>  		return ERR_PTR(-ENOMEM);
>  	memcpy(jt->items, subprog->jt->items, subprog->jt->cnt << 2);
>  
> -	for (i = 0; i < jt->cnt; i++) {
> -		if (jt->items[i] < subprog_start || jt->items[i] >= subprog_end) {
> -			verbose(env, "jump table for insn %d points outside of the subprog [%u,%u]\n",
> -					t, subprog_start, subprog_end);
> -			bpf_diag_program_structure(
> -				env, t, "jump table target out of range",
> -				"Keep every jump-table target inside the same subprogram.",
> -				"The jump table for instruction %d points outside subprogram range [%u,%u).",
> -				t, subprog_start, subprog_end);
> -			kvfree(jt);
> -			return ERR_PTR(-EINVAL);
> -		}
> -	}
> -
>  	return jt;
>  }
>  
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 72a3f5998dd2..45234e2fbee6 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -18165,11 +18165,56 @@ static int indirect_jump_min_max_index(struct bpf_verifier_env *env,
>  	return 0;
>  }
>  
> +/* 'jt' is sorted and free of duplicates, see sort_insn_array_uniq() */
> +static bool jt_contains(const struct bpf_iarray *jt, u32 target)
> +{
> +	int l = 0, r = jt->cnt - 1, m;
> +
> +	while (l <= r) {
> +		m = l + (r - l) / 2;
> +		if (jt->items[m] == target)
> +			return true;
> +		if (jt->items[m] < target)
> +			l = m + 1;
> +		else
> +			r = m - 1;
> +	}
> +	return false;
> +}
> +
> +static int reject_gotox_out_of_subprog(struct bpf_verifier_env *env, u32 target,
> +				       u32 subprog_start, u32 subprog_end)
> +{
> +	verbose(env, "indirect jump from insn %d to %u leaves the subprog [%u,%u)\n",
> +		     env->insn_idx, target, subprog_start, subprog_end);
> +	bpf_diag_program_structure(
> +		env, env->insn_idx, "indirect jump leaves subprogram",
> +		"Keep every reachable jump-table target inside the subprogram of the indirect jump.",
> +		"Instruction %d can jump indirectly to instruction %u, which is outside its own subprogram [%u,%u).",
> +		env->insn_idx, target, subprog_start, subprog_end);
> +	return -EINVAL;
> +}
> +
> +static int reject_gotox_without_cfg_edge(struct bpf_verifier_env *env, u32 target)
> +{
> +	verbose(env, "indirect jump from insn %d to %u is not in the jump table of the subprog\n",
> +		     env->insn_idx, target);
> +	bpf_diag_program_structure(
> +		env, env->insn_idx, "indirect jump target without CFG edge",
> +		"Resolve indirect jumps through a jump table whose entries all fall inside the subprogram of the jump.",
> +		"Instruction %d can jump indirectly to instruction %u, which is not part of the jump table of its subprogram.",
> +		env->insn_idx, target);
> +	return -EINVAL;
> +}
> +
>  /* gotox *dst_reg */
>  static int check_indirect_jump(struct bpf_verifier_env *env, struct bpf_insn *insn)
>  {
>  	struct bpf_verifier_state *other_branch;
> +	struct bpf_subprog_info *subprog;
> +	u32 subprog_start, subprog_end;
>  	struct bpf_reg_state *dst_reg;
> +	struct bpf_iarray *jt;
>  	struct bpf_map *map;
>  	u32 min_index, max_index;
>  	int err = 0;
> @@ -18212,6 +18257,26 @@ static int check_indirect_jump(struct bpf_verifier_env *env, struct bpf_insn *in
>  		return -EINVAL;
>  	}
>  
> +	subprog = bpf_find_containing_subprog(env, env->insn_idx);
> +	if (verifier_bug_if(!subprog, env, "no subprog contains insn %d", env->insn_idx))
> +		return -EFAULT;
> +	subprog_start = subprog->start;
> +	subprog_end = (subprog + 1)->start;
> +
> +	jt = env->insn_aux_data[env->insn_idx].jt;
> +	if (verifier_bug_if(!jt, env, "no jump table for insn %d", env->insn_idx))
> +		return -EFAULT;
> +
> +	for (i = 0; i < n; i++) {

The items[] is sorted. Is this enough to just check 0-th and (n-1)-th elements?

> +		u32 target = env->gotox_tmp_buf->items[i];
> +
> +		if (target < subprog_start || target >= subprog_end)
> +			return reject_gotox_out_of_subprog(env, target, subprog_start,
> +							   subprog_end);
> +		if (!jt_contains(jt, target))
> +			return reject_gotox_without_cfg_edge(env, target);

Ah, I see, all elements should be checked because this check was added.

> +	}
> +
>  	for (i = 0; i < n - 1; i++) {
>  		mark_indirect_target(env, env->gotox_tmp_buf->items[i]);
>  		other_branch = push_stack(env, env->gotox_tmp_buf->items[i],
> diff --git a/tools/testing/selftests/bpf/progs/verifier_gotox.c b/tools/testing/selftests/bpf/progs/verifier_gotox.c
> index 5b18c9a27717..3567b29e2378 100644
> --- a/tools/testing/selftests/bpf/progs/verifier_gotox.c
> +++ b/tools/testing/selftests/bpf/progs/verifier_gotox.c
> @@ -318,7 +318,7 @@ __used static int test_subprog(void)
>  }
>  
>  SEC("socket")
> -__failure __msg("jump table for insn 4 points outside of the subprog [0,10]")
> +__failure __msg("jump table of subprog starting at 0 spans multiple subprogs")
>  __naked void jump_table_outside_subprog(void)
>  {
>  	asm volatile ("						\
> -- 
> 2.43.0

Acked-by: Anton Protopopov <a.s.protopopov@gmail.com>

  parent reply	other threads:[~2026-09-10 11:59 UTC|newest]

Thread overview: 21+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-09 20:40 [PATCH bpf 1/6] bpf: Avoid quadratic successor rescan in bpf_compute_scc Daniel Borkmann
2026-09-09 20:40 ` [PATCH bpf 2/6] bpf: Bound the number of indirect jump edges in a program Daniel Borkmann
2026-09-09 20:57   ` sashiko-bot
2026-09-10 11:15     ` Daniel Borkmann
2026-09-10 11:44   ` Anton Protopopov
2026-09-09 20:40 ` [PATCH bpf 3/6] bpf: Cache the jump table of a subprogram during CFG discovery Daniel Borkmann
2026-09-09 21:34   ` bot+bpf-ci
2026-09-10 11:21     ` Daniel Borkmann
2026-09-10 11:46   ` Anton Protopopov
2026-09-10 21:02   ` Eduard Zingerman
2026-09-09 20:40 ` [PATCH bpf 4/6] bpf: Reject indirect jumps that leave their subprogram Daniel Borkmann
2026-09-09 21:50   ` bot+bpf-ci
2026-09-10 12:10   ` Anton Protopopov [this message]
2026-09-10 19:37   ` Eduard Zingerman
2026-09-09 20:40 ` [PATCH bpf 5/6] selftests/bpf: Add tests for the indirect jump edge limit Daniel Borkmann
2026-09-09 21:34   ` bot+bpf-ci
2026-09-10 12:14   ` Anton Protopopov
2026-09-09 20:40 ` [PATCH bpf 6/6] selftests/bpf: Add tests for indirect jumps across subprograms Daniel Borkmann
2026-09-09 21:34   ` bot+bpf-ci
2026-09-10 12:22   ` Anton Protopopov
2026-09-10 18:54 ` [PATCH bpf 1/6] bpf: Avoid quadratic successor rescan in bpf_compute_scc Eduard Zingerman

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=aqKeIVyaAXFXgT8Q@mail.gmail.com \
    --to=a.s.protopopov@gmail.com \
    --cc=ast@kernel.org \
    --cc=bpf@vger.kernel.org \
    --cc=daniel@iogearbox.net \
    --cc=eddyz87@gmail.com \
    --cc=gnq25@mails.tsinghua.edu.cn \
    --cc=info@starlabs.sg \
    --cc=jamesburton@meta.com \
    --cc=memxor@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