From: sashiko-bot@kernel.org
To: "Alexei Starovoitov" <alexei.starovoitov@gmail.com>, bpf@vger.kernel.org
Subject: Re: [PATCH bpf-next v2 05/35] bpf: Add join of verifier states
Date: Thu, 05 Nov 2026 21:40:22 +0000 [thread overview]
Message-ID: <sashiko-outbox-200254@kernel.org> (raw)
In-Reply-To: <20261105204513.2230344-6-alexei.starovoitov@gmail.com>
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [Medium] bpf: join_reg() zeroing dst->delta breaks explore_alu_limits in join_check()
- [Medium] bpf: asymmetric unallocated and poisoned stack handling in join_func()
- [Medium] bpf: asymmetric out_stack_arg_cnt check in join_func() blocks valid joins
--
commit c295ead9d4bf38d9324eb911d159793936c6f2a1
Author: Alexei Starovoitov <ast@kernel.org>
bpf: Add join of verifier states
This commit adds the second half of kernel/bpf/join.c and builds it to
implement bpf_states_join(), which joins two BPF verifier states frame by
frame at an 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]
When !js->env->allow_uninit_stack (for programs loaded without CAP_PERFMON),
can this stack slot handling in join_func() and join_slot() cause asymmetric
behavior depending on which state has the larger allocated_stack?
First, when bpf_stack_nr_slots(fa) < bpf_stack_nr_slots(fb), the loop over
slots i in [bpf_stack_nr_slots(fa), nslots_b) only rejects special slots and
non-scalar spills:
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;
}
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 not allocated
that slot, bpf_states_join() succeeds and leaves slot i unallocated in fd.
If a later path predicated on the branch that came from fb reads fp - (i + 1)
* 8, check_stack_slot_within_bounds() will reject the valid program with
-EACCES because fd->allocated_stack is too small and !env->allow_uninit_stack.
Second, when bpf_stack_nr_slots(fa) > bpf_stack_nr_slots(fb),
bpf_clean_verifier_state() marks dead stack slots in fa as STACK_POISON
without growing fb->allocated_stack. For i >= nslots_b, join_func() passes
sb = NULL into join_slot(), which substitutes invalid_slot (all
STACK_INVALID) and checks:
if (!js->env->allow_uninit_stack)
return !memcmp(sa->slot_type, sb->slot_type, sizeof(sa->slot_type));
Because memcmp() treats STACK_POISON in sa and STACK_INVALID in invalid_slot
as unequal, dead stack slots in fa prevent joining the two states.
Should join_slot() treat stype_is_invalid() bytes (STACK_INVALID and
STACK_POISON) as equivalent when !js->env->allow_uninit_stack, and should the
trailing loop in join_func() also reject non-invalid slots in fb when
!js->env->allow_uninit_stack?
> +
> + if (fa->out_stack_arg_cnt < fb->out_stack_arg_cnt)
> + return false;
[Severity: Medium]
Why does join_func() reject the join when fa->out_stack_arg_cnt <
fb->out_stack_arg_cnt?
In check_stack_arg_write(), grow_stack_arg_slots() grows out_stack_arg_cnt as
a high-water mark, and invalidate_outgoing_stack_args() or bpf_flow_return()
marks stack_arg_regs[0..out_stack_arg_cnt - 1] as NOT_INIT after a call
without resetting out_stack_arg_cnt to 0.
If state a reaches a loop header or join point before any call with stack
arguments (fa->out_stack_arg_cnt == 0) and state b arrives after calling a
function with stack arguments (fb->out_stack_arg_cnt > 0, with all slots
NOT_INIT), fa->out_stack_arg_cnt < fb->out_stack_arg_cnt prevents joining or
widening the two states.
Since slots beyond fa->out_stack_arg_cnt are implicitly NOT_INIT in both fa
and fd (and stack_arg_safe() in bpf_state_subsumes() already accepts that),
could join_func() allow fa->out_stack_arg_cnt < fb->out_stack_arg_cnt when
the extra slots in fb are NOT_INIT or incoming?
[ ... ]
> +/* 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]
Can join_check() trigger a false verifier_bug() and return -EFAULT when
env->explore_alu_limits is enabled and join_reg() drops an inconsistent
scalar ID link?
In kernel/bpf/join.c:join_reg():
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;
When env->explore_alu_limits is true and two SCALAR_VALUE registers ra and rb
have identical value ranges and the same non-zero delta (from BPF_ADD_CONST64
or BPF_ADD_CONST32), reg_value_equal(ra, rb) succeeds because memcmp(ra, rb,
offsetof(struct bpf_reg_state, id)) is 0.
If their IDs do not form a valid bijection across the state, jid_good(js,
ra->id, rb->id) returns false in pass 2, and join_reg() sets dst->id = 0 and
dst->delta = 0.
However, delta is located before id in struct bpf_reg_state. When
bpf_states_join() then calls join_check() -> bpf_state_subsumes() ->
states_equal() -> func_states_equal() -> regsafe(), regsafe() checks:
case SCALAR_VALUE:
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 rold->delta (dst->delta == 0) no longer matches rcur->delta
(ra->delta != 0), memcmp() fails, causing join_check() to fire verifier_bug()
and fail verification with -EFAULT.
Should join_reg() return false in pass 2 when js->env->explore_alu_limits is
set and dst->delta would be cleared alongside dst->id?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261105204513.2230344-1-alexei.starovoitov@gmail.com?part=5
next prev parent reply other threads:[~2026-11-05 21:40 UTC|newest]
Thread overview: 66+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-05 20:44 [PATCH bpf-next v2 00/35] bpf: A verifier that joins states Alexei Starovoitov
2026-11-05 20:44 ` [PATCH bpf-next v2 01/35] bpf: Add may_goto to the loops that the verifier marks Alexei Starovoitov
2026-11-05 21:14 ` sashiko-bot
2026-11-05 20:44 ` [PATCH bpf-next v2 02/35] bpf: Limit depth and time of recursive subprogs at run time Alexei Starovoitov
2026-11-05 21:35 ` bot+bpf-ci
2026-11-05 20:44 ` [PATCH bpf-next v2 03/35] bpf: Add union and widening of the range of a register Alexei Starovoitov
2026-11-05 21:34 ` bot+bpf-ci
2026-11-05 20:44 ` [PATCH bpf-next v2 04/35] bpf: Add join of registers and stack slots Alexei Starovoitov
2026-11-05 21:16 ` sashiko-bot
2026-11-05 21:35 ` bot+bpf-ci
2026-11-05 20:44 ` [PATCH bpf-next v2 05/35] bpf: Add join of verifier states Alexei Starovoitov
2026-11-05 21:35 ` bot+bpf-ci
2026-11-05 21:40 ` sashiko-bot [this message]
2026-11-05 20:44 ` [PATCH bpf-next v2 06/35] bpf: Tell which arguments of a call a join must leave alone Alexei Starovoitov
2026-11-05 20:44 ` [PATCH bpf-next v2 07/35] bpf: Add the data structures of the main pass over the control flow graph Alexei Starovoitov
2026-11-05 20:44 ` [PATCH bpf-next v2 08/35] bpf: Order the insns, find what stops a loop and where may_goto can go Alexei Starovoitov
2026-11-05 20:44 ` [PATCH bpf-next v2 09/35] bpf: Find the registers that hold a number or a pointer to arena Alexei Starovoitov
2026-11-05 21:35 ` bot+bpf-ci
2026-11-05 21:40 ` sashiko-bot
2026-11-05 20:44 ` [PATCH bpf-next v2 10/35] bpf: Find the registers and stack slots that later checks depend on Alexei Starovoitov
2026-11-05 21:35 ` bot+bpf-ci
2026-11-05 20:44 ` [PATCH bpf-next v2 11/35] bpf: Add the sets of states at an insn and the entries of functions Alexei Starovoitov
2026-11-05 20:44 ` [PATCH bpf-next v2 12/35] bpf: Compare states of different iterations of a loop Alexei Starovoitov
2026-11-05 20:44 ` [PATCH bpf-next v2 13/35] bpf: Decide which states are not joined Alexei Starovoitov
2026-11-05 21:35 ` bot+bpf-ci
2026-11-05 20:44 ` [PATCH bpf-next v2 14/35] bpf: Merge the state that arrives into the set at an insn Alexei Starovoitov
2026-11-05 21:35 ` bot+bpf-ci
2026-11-05 20:44 ` [PATCH bpf-next v2 15/35] bpf: Move states along edges, into loops and subprogs and out of them Alexei Starovoitov
2026-11-05 21:35 ` bot+bpf-ci
2026-11-05 20:44 ` [PATCH bpf-next v2 16/35] bpf: Make the state after a call from the caller's state and an exit state Alexei Starovoitov
2026-11-05 20:44 ` [PATCH bpf-next v2 17/35] bpf: Enter and leave loops in the main pass, build flow.c Alexei Starovoitov
2026-11-05 21:35 ` bot+bpf-ci
2026-11-05 20:44 ` [PATCH bpf-next v2 18/35] bpf: Tell the passes over the graph where an added may_goto goes Alexei Starovoitov
2026-11-05 22:19 ` sashiko-bot
2026-11-05 20:44 ` [PATCH bpf-next v2 19/35] bpf: Verify programs as a fixpoint over the control flow graph Alexei Starovoitov
2026-11-05 21:35 ` bot+bpf-ci
2026-11-05 22:45 ` sashiko-bot
2026-11-05 20:44 ` [PATCH bpf-next v2 20/35] bpf: Keep the count of a loop when a register has no closed form Alexei Starovoitov
2026-11-05 21:35 ` bot+bpf-ci
2026-11-05 20:44 ` [PATCH bpf-next v2 21/35] bpf: Verify static subprogs on their own Alexei Starovoitov
2026-11-05 21:51 ` bot+bpf-ci
2026-11-05 22:45 ` sashiko-bot
2026-11-05 20:44 ` [PATCH bpf-next v2 22/35] bpf: Allow recursive subprogs Alexei Starovoitov
2026-11-05 21:51 ` bot+bpf-ci
2026-11-05 23:09 ` sashiko-bot
2026-11-05 20:45 ` [PATCH bpf-next v2 23/35] bpf: Remove is_state_visited(), bpf_compute_scc() and iter.depth Alexei Starovoitov
2026-11-05 20:45 ` [PATCH bpf-next v2 24/35] bpf: Remove precision tracking Alexei Starovoitov
2026-11-05 20:45 ` [PATCH bpf-next v2 25/35] bpf: Remove prune points, levels of state comparison and pop_stack() Alexei Starovoitov
2026-11-05 20:45 ` [PATCH bpf-next v2 26/35] bpf: Update comments about paths, pruning and explored states Alexei Starovoitov
2026-11-05 20:45 ` [PATCH bpf-next v2 27/35] bpf: Track a pointer to one of a set of functions Alexei Starovoitov
2026-11-05 20:45 ` [PATCH bpf-next v2 28/35] bpf: callx reads what the subprogs whose address is taken read Alexei Starovoitov
2026-11-05 20:45 ` [PATCH bpf-next v2 29/35] selftests/bpf: Adjust tests to log lines that are gone Alexei Starovoitov
2026-11-05 21:35 ` bot+bpf-ci
2026-11-05 20:45 ` [PATCH bpf-next v2 30/35] selftests/bpf: Adjust tests to new verdicts, messages and order Alexei Starovoitov
2026-11-05 21:35 ` bot+bpf-ci
2026-11-05 20:45 ` [PATCH bpf-next v2 31/35] selftests/bpf: Test states that are not joined Alexei Starovoitov
2026-11-05 21:35 ` bot+bpf-ci
2026-11-05 20:45 ` [PATCH bpf-next v2 32/35] selftests/bpf: Test static subprogs that are verified on their own Alexei Starovoitov
2026-11-05 21:35 ` bot+bpf-ci
2026-11-05 20:45 ` [PATCH bpf-next v2 33/35] selftests/bpf: Test loops that are counted, walked or widened Alexei Starovoitov
2026-11-05 20:45 ` [PATCH bpf-next v2 34/35] selftests/bpf: Test progs that run out of budget Alexei Starovoitov
2026-11-05 21:35 ` bot+bpf-ci
2026-11-05 23:19 ` sashiko-bot
2026-11-05 20:45 ` [PATCH bpf-next v2 35/35] selftests/bpf: Test callx of one of a set of functions Alexei Starovoitov
2026-11-05 23:17 ` sashiko-bot
2026-11-06 0:34 ` [PATCH bpf-next v2 00/35] bpf: A verifier that joins states Alexei Starovoitov
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-200254@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