BPF List
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Alexei Starovoitov" <alexei.starovoitov@gmail.com>, bpf@vger.kernel.org
Subject: Re: [PATCH bpf-next v3 05/45] bpf: Add join of verifier states
Date: Fri, 06 Nov 2026 04:25:56 +0000	[thread overview]
Message-ID: <sashiko-outbox-200717@kernel.org> (raw)
In-Reply-To: <20261106035111.3315285-6-alexei.starovoitov@gmail.com>

Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Medium] bpf: asymmetric unallocated and poisoned stack handling in join_func()
- [Medium] bpf: join_reg() zeroing dst->delta breaks explore_alu_limits in join_check()
- [Medium] bpf: asymmetric out_stack_arg_cnt check in join_func() blocks valid joins
--

commit 1d1d176d0db968688ddcdf9cb26ad2c24d9339f4
Author: Alexei Starovoitov <ast@kernel.org>

bpf: Add join of verifier states

This commit adds the second half of kernel/bpf/join.c and enables building it.
It implements bpf_states_join() to merge two verifier states frame by frame at
a single instruction and verifies that the joined state subsumes both inputs.

> diff --git a/kernel/bpf/join.c b/kernel/bpf/join.c
> index fe513b7a8e884..4da051767e1d9 100644
> --- a/kernel/bpf/join.c
> +++ b/kernel/bpf/join.c
> @@ -398,3 +398,250 @@ static bool join_slot(struct bpf_join_state *js, struct bpf_stack_state *ds,
>  	bpf_mark_reg_not_init(js->env, &ds->spilled_ptr);
>  	return true;
>  }
> +

[ ... ]

> +static bool join_func(struct bpf_join_state *js, struct bpf_func_state *fd,
> +		      const struct bpf_func_state *fa, const struct bpf_func_state *fb,
> +		      u16 live_regs, int pass)
> +{

[ ... ]

> +	nslots_b = bpf_stack_nr_slots(fb);
> +	for (i = 0; i < bpf_stack_nr_slots(fa); i++) {
> +		const struct bpf_stack_state *sb = i < nslots_b ? bpf_stack_slot(fb, i) : NULL;
> +
> +		js->place = MAX_BPF_REG + i;
> +		if (!join_slot(js, bpf_stack_slot(fd, i), bpf_stack_slot(fa, i), sb, pass))
> +			return false;
> +	}
> +	js->place = -1;
> +	/* Slots that exist only in 'b' are STACK_INVALID in the result. */
> +	for (i = bpf_stack_nr_slots(fa); pass == 1 && i < nslots_b; i++) {
> +		const struct bpf_stack_state *sb = bpf_stack_slot(fb, i);
> +
> +		if (slot_is_special(sb) ||
> +		    (slot_has_spill(sb) && sb->spilled_ptr.type != SCALAR_VALUE))
> +			return false;
> +	}

[Severity: Medium]
As reported by Sashiko on v2 [1], how does bpf_states_join() -> join_func() ->
join_slot() handle stack slots when !env->allow_uninit_stack?

First, in the loop over slots that exist only in fb (for i >=
bpf_stack_nr_slots(fa)), join_func() only rejects special slots and non-scalar
spills, without checking !js->env->allow_uninit_stack. If fb has a live
initialized slot at i >= bpf_stack_nr_slots(fa) (such as a scalar STACK_SPILL,
STACK_MISC, or STACK_ZERO) while fa has allocated_stack <= i * 8, the join
succeeds and leaves slot i unallocated in fd. If the program later reads slot
i under a condition that only holds along path b, won't
check_stack_slot_within_bounds() falsely reject a valid program with -EACCES
when !env->allow_uninit_stack?

Second, when bpf_clean_verifier_state() marks dead stack slots in fa as
STACK_POISON via __clean_func_state(), and slot i is unallocated in fb (so
join_slot() substitutes invalid_slot with STACK_INVALID), join_slot() runs:

	if (!js->env->allow_uninit_stack)
		return !memcmp(sa->slot_type, sb->slot_type, sizeof(sa->slot_type));

Won't memcmp() treat dead STACK_POISON bytes in sa and unallocated
STACK_INVALID bytes in sb as unequal, causing dead stack slots to prevent a
join when !env->allow_uninit_stack?

> +
> +	if (fa->out_stack_arg_cnt < fb->out_stack_arg_cnt)
> +		return false;

[Severity: Medium]
As reported by Sashiko on v2 [2], should join_func() reject the join whenever
fa->out_stack_arg_cnt < fb->out_stack_arg_cnt?

Since out_stack_arg_cnt is an allocation high-water mark grown by
grow_stack_arg_slots() in check_stack_arg_write(), it is not reset to 0 when
invalidate_outgoing_stack_args() or bpf_flow_return() marks
stack_arg_regs[0..out_stack_arg_cnt - 1] as NOT_INIT after a call returns.

If state a reaches a loop header or branch target before any call with stack
arguments (fa->out_stack_arg_cnt == 0) and state b arrives after a call with
stack arguments whose slots are now NOT_INIT (fb->out_stack_arg_cnt > 0),
won't this check prevent joining or widening the states even though slots at
i >= fa->out_stack_arg_cnt are implicitly NOT_INIT in fa and fd?

[ ... ]

> +/* The result has to describe everything that an input describes */
> +static int join_check(struct bpf_join_state *js, struct bpf_verifier_state *dst,
> +		      const struct bpf_verifier_state *in, const char *which)
> +{
> +	struct bpf_verifier_env *env = js->env;
> +
> +	if (bpf_state_subsumes(env, dst, (struct bpf_verifier_state *)in))
> +		return 0;
> +	/* check_ids() has no room left: that says nothing about the join */
> +	if (env->idmap_scratch.cnt == BPF_ID_MAP_SIZE)
> +		return 1;
> +	bpf_log(&env->log, "join:");
> +	print_verifier_state(env, dst, dst->curframe, true);
> +	bpf_log(&env->log, "input %s:", which);
> +	print_verifier_state(env, in, in->curframe, true);
> +	verifier_bug(env, "join of states at insn %u does not subsume input '%s'",
> +		     dst->insn_idx, which);
> +	return -EFAULT;
> +}

[Severity: Medium]
As reported by Sashiko on v2 [3], can bpf_states_join() trigger verifier_bug()
in join_check() when env->explore_alu_limits is true?

In join_reg() for SCALAR_VALUE:

	case SCALAR_VALUE:
		if (js->env->explore_alu_limits && !reg_value_equal(ra, rb))
			return false;
		if (pass == 1) {
			jid_note(js, ra->id, rb->id, scalar_link_consistent(ra, rb));
			return true;
		}
		join_range(js, dst, ra, rb);
		/* a link that exists on one side only is dropped */
		if (ra->id && !jid_good(js, ra->id, rb->id)) {
			dst->id = 0;
			dst->delta = 0;
		}
		return true;

Two SCALAR_VALUE registers ra and rb with equal ranges and the same non-zero
delta pass reg_value_equal(ra, rb). If their IDs do not form a valid bijection
across the state (so jid_good(js, ra->id, rb->id) is false in pass 2),
join_reg() clears dst->id = 0 and dst->delta = 0.

When join_check() subsequently calls bpf_state_subsumes() -> states_equal() ->
func_states_equal() -> regsafe(), regsafe() checks:

	if (env->explore_alu_limits) {
		return memcmp(rold, rcur, offsetof(struct bpf_reg_state, id)) == 0 &&
		       check_scalar_ids(rold->id, rcur->id, idmap);
	}

Because delta lies before id in struct bpf_reg_state, won't memcmp() fail when
comparing dst->delta (0) against ra->delta (non-zero), causing join_check() to
hit verifier_bug() and return -EFAULT?

[1] https://lore.kernel.org/r/20261105204513.2230344-6-alexei.starovoitov@gmail.com
[2] https://lore.kernel.org/r/20261105204513.2230344-6-alexei.starovoitov@gmail.com
[3] https://lore.kernel.org/r/20261105204513.2230344-6-alexei.starovoitov@gmail.com

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20261106035111.3315285-1-alexei.starovoitov@gmail.com?part=5

  reply	other threads:[~2026-11-06  4:25 UTC|newest]

Thread overview: 77+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-11-06  3:50 [PATCH bpf-next v3 00/45] bpf: A verifier that joins states Alexei Starovoitov
2026-11-06  3:50 ` [PATCH bpf-next v3 01/45] bpf: Add may_goto to the loops that the verifier marks Alexei Starovoitov
2026-11-06  4:22   ` sashiko-bot
2026-11-06  4:38   ` bot+bpf-ci
2026-11-06  3:50 ` [PATCH bpf-next v3 02/45] bpf: Limit depth and time of recursive subprogs at run time Alexei Starovoitov
2026-11-06  3:50 ` [PATCH bpf-next v3 03/45] bpf: Add union and widening of the range of a register Alexei Starovoitov
2026-11-06  3:50 ` [PATCH bpf-next v3 04/45] bpf: Add join of registers and stack slots Alexei Starovoitov
2026-11-06  4:39   ` sashiko-bot
2026-11-06  3:50 ` [PATCH bpf-next v3 05/45] bpf: Add join of verifier states Alexei Starovoitov
2026-11-06  4:25   ` sashiko-bot [this message]
2026-11-06  4:38   ` bot+bpf-ci
2026-11-06  3:50 ` [PATCH bpf-next v3 06/45] bpf: Tell which arguments of a call a join must leave alone Alexei Starovoitov
2026-11-06  3:50 ` [PATCH bpf-next v3 07/45] bpf: Add the data structures of the main pass over the control flow graph Alexei Starovoitov
2026-11-06  4:24   ` bot+bpf-ci
2026-11-06  3:50 ` [PATCH bpf-next v3 08/45] bpf: Order the insns, find what stops a loop and where may_goto can go Alexei Starovoitov
2026-11-06  3:50 ` [PATCH bpf-next v3 09/45] bpf: Find the registers that hold a number or a pointer to arena Alexei Starovoitov
2026-11-06  3:50 ` [PATCH bpf-next v3 10/45] bpf: Find the registers and stack slots that later checks depend on Alexei Starovoitov
2026-11-06  4:08   ` sashiko-bot
2026-11-06  3:50 ` [PATCH bpf-next v3 11/45] bpf: Add the sets of states at an insn and the entries of functions Alexei Starovoitov
2026-11-06  3:50 ` [PATCH bpf-next v3 12/45] bpf: Compare states of different iterations of a loop Alexei Starovoitov
2026-11-06  3:50 ` [PATCH bpf-next v3 13/45] bpf: Decide which states are not joined Alexei Starovoitov
2026-11-06  4:38   ` bot+bpf-ci
2026-11-06  3:50 ` [PATCH bpf-next v3 14/45] bpf: Merge the state that arrives into the set at an insn Alexei Starovoitov
2026-11-06  4:24   ` bot+bpf-ci
2026-11-06  3:50 ` [PATCH bpf-next v3 15/45] bpf: Move states along edges, into loops and subprogs and out of them Alexei Starovoitov
2026-11-06  3:50 ` [PATCH bpf-next v3 16/45] bpf: Make the state after a call from the caller's state and an exit state Alexei Starovoitov
2026-11-06  4:34   ` sashiko-bot
2026-11-06  3:50 ` [PATCH bpf-next v3 17/45] bpf: Enter and leave loops in the main pass, build flow.c Alexei Starovoitov
2026-11-06  4:38   ` bot+bpf-ci
2026-11-06  3:50 ` [PATCH bpf-next v3 18/45] bpf: Tell the passes over the graph where an added may_goto goes Alexei Starovoitov
2026-11-06  4:26   ` sashiko-bot
2026-11-06  3:50 ` [PATCH bpf-next v3 19/45] bpf: Verify programs as a fixpoint over the control flow graph Alexei Starovoitov
2026-11-06  4:38   ` bot+bpf-ci
2026-11-06  4:55   ` sashiko-bot
2026-11-06  3:50 ` [PATCH bpf-next v3 20/45] bpf: Keep the count of a loop when a register has no closed form Alexei Starovoitov
2026-11-06  4:38   ` bot+bpf-ci
2026-11-06  3:50 ` [PATCH bpf-next v3 21/45] bpf: Verify static subprogs on their own Alexei Starovoitov
2026-11-06  4:50   ` bot+bpf-ci
2026-11-06  4:53   ` sashiko-bot
2026-11-06  3:50 ` [PATCH bpf-next v3 22/45] bpf: Allow recursive subprogs Alexei Starovoitov
2026-11-06  4:38   ` bot+bpf-ci
2026-11-06  5:09   ` sashiko-bot
2026-11-06  3:50 ` [PATCH bpf-next v3 23/45] bpf: Remove is_state_visited(), bpf_compute_scc() and iter.depth Alexei Starovoitov
2026-11-06  3:50 ` [PATCH bpf-next v3 24/45] bpf: Remove precision tracking Alexei Starovoitov
2026-11-06  4:24   ` bot+bpf-ci
2026-11-06  3:50 ` [PATCH bpf-next v3 25/45] bpf: Remove prune points, levels of state comparison and pop_stack() Alexei Starovoitov
2026-11-06  3:50 ` [PATCH bpf-next v3 26/45] bpf: Update comments about paths, pruning and explored states Alexei Starovoitov
2026-11-06  4:19   ` sashiko-bot
2026-11-06  4:24   ` bot+bpf-ci
2026-11-06  3:50 ` [PATCH bpf-next v3 27/45] bpf: Track a pointer to one of a set of functions Alexei Starovoitov
2026-11-06  4:49   ` sashiko-bot
2026-11-06  3:50 ` [PATCH bpf-next v3 28/45] bpf: callx reads what the subprogs whose address is taken read Alexei Starovoitov
2026-11-06  3:50 ` [PATCH bpf-next v3 29/45] bpf: Don't take what bpf_core_cast() returns for a number Alexei Starovoitov
2026-11-06  3:50 ` [PATCH bpf-next v3 30/45] bpf: Don't widen away a register that only the loop sets Alexei Starovoitov
2026-11-06  3:50 ` [PATCH bpf-next v3 31/45] bpf: A base that nothing is known about can be the context Alexei Starovoitov
2026-11-06  3:50 ` [PATCH bpf-next v3 32/45] bpf: Keep the frames of the caller for a subprog that loads data_end Alexei Starovoitov
2026-11-06  4:24   ` bot+bpf-ci
2026-11-06  3:50 ` [PATCH bpf-next v3 33/45] bpf: Keep the frames of the caller for ctx of struct_ops with a reference Alexei Starovoitov
2026-11-06  4:24   ` bot+bpf-ci
2026-11-06  3:50 ` [PATCH bpf-next v3 34/45] bpf: R0 is live at the exit that an added may_goto goes to Alexei Starovoitov
2026-11-06  3:50 ` [PATCH bpf-next v3 35/45] bpf: Keep the step of a number in the union of two ranges Alexei Starovoitov
2026-11-06  3:51 ` [PATCH bpf-next v3 36/45] bpf: Keep the first iteration of a counted loop apart Alexei Starovoitov
2026-11-06  4:38   ` bot+bpf-ci
2026-11-06  3:51 ` [PATCH bpf-next v3 37/45] bpf: What a call returns in each iteration is like what the loop loads Alexei Starovoitov
2026-11-06  3:51 ` [PATCH bpf-next v3 38/45] bpf: Find where the jumps of a loop stop a number Alexei Starovoitov
2026-11-06  3:51 ` [PATCH bpf-next v3 39/45] selftests/bpf: Adjust tests to log lines that are gone Alexei Starovoitov
2026-11-06  4:38   ` bot+bpf-ci
2026-11-06  3:51 ` [PATCH bpf-next v3 40/45] selftests/bpf: Adjust tests to new verdicts, messages and order Alexei Starovoitov
2026-11-06  4:25   ` bot+bpf-ci
2026-11-06  4:34   ` sashiko-bot
2026-11-06  3:51 ` [PATCH bpf-next v3 41/45] selftests/bpf: Test states that are not joined Alexei Starovoitov
2026-11-06  3:51 ` [PATCH bpf-next v3 42/45] selftests/bpf: Test static subprogs that are verified on their own Alexei Starovoitov
2026-11-06  3:51 ` [PATCH bpf-next v3 43/45] selftests/bpf: Test loops that are counted, walked or widened Alexei Starovoitov
2026-11-06  3:51 ` [PATCH bpf-next v3 44/45] selftests/bpf: Test progs that run out of budget Alexei Starovoitov
2026-11-06  3:51 ` [PATCH bpf-next v3 45/45] selftests/bpf: Test callx of one of a set of functions Alexei Starovoitov
2026-11-06  4:52   ` sashiko-bot
2026-11-06 18:08 ` [PATCH bpf-next v3 00/45] bpf: A verifier that joins states 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=sashiko-outbox-200717@kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=alexei.starovoitov@gmail.com \
    --cc=bpf@vger.kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    /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