* [PATCH bpf-next v1 00/10] bpf: replace path-sensitive with path-insensitive live stack analysis
@ 2025-09-11 1:04 Eduard Zingerman
2025-09-11 1:04 ` [PATCH bpf-next v1 01/10] bpf: bpf_verifier_state->cleaned flag instead of REG_LIVE_DONE Eduard Zingerman
` (10 more replies)
0 siblings, 11 replies; 22+ messages in thread
From: Eduard Zingerman @ 2025-09-11 1:04 UTC (permalink / raw)
To: bpf, ast, andrii; +Cc: daniel, martin.lau, kernel-team, yonghong.song, eddyz87
Consider the following program, assuming checkpoint is created for a
state at instruction (3):
1: call bpf_get_prandom_u32()
2: *(u64 *)(r10 - 8) = 42
-- checkpoint #1 --
3: if r0 != 0 goto +1
4: exit;
5: r0 = *(u64 *)(r10 - 8)
6: exit
The verifier processes this program by exploring two paths:
- 1 -> 2 -> 3 -> 4
- 1 -> 2 -> 3 -> 5 -> 6
When instruction (5) is processed, the current liveness tracking
mechanism moves up the register parent links and records a "read" mark
for stack slot -8 at checkpoint #1, stopping because of the "write"
mark recorded at instruction (2).
This patch set replaces the existing liveness tracking mechanism with
a path-insensitive data flow analysis. The program above is processed
as follows:
- a data structure representing live stack slots for
instructions 1-6 in frame #0 is allocated;
- when instruction (2) is processed, record that slot -8 is written at
instruction (2) in frame #0;
- when instruction (5) is processed, record that slot -8 is read at
instruction (5) in frame #0;
- when instruction (6) is processed, propagate read mark for slot -8
up the control flow graph to instructions 3 and 2.
The key difference is that the new mechanism operates on a control
flow graph and associates read and write marks with pairs of (call
chain, instruction index). In contrast, the old mechanism operates on
verifier states and register parent links, associating read and write
marks with verifier states.
Motivation
==========
As it stands, this patch set makes liveness tracking slightly less
precise, as it no longer distinguishes individual program paths taken
by the verifier during symbolic execution.
See the "Impact on verification performance" section for details.
However, this change is intended as a stepping stone toward the
following goals:
- Short term, integrate precision tracking into liveness analysis and
remove the following code:
- verifier backedge states accumulation in is_state_visited();
- most of the logic for precision tracking;
- jump history tracking.
- Long term, help with more efficient loop verification handling.
Why integrating precision tracking?
-----------------------------------
In a sense, precision tracking is very similar to liveness tracking.
The data flow equations for liveness tracking look as follows:
live_after =
U [state[s].live_before for s in insn_successors(i)]
state[i].live_before =
(live_after / state[i].must_write) U state[i].may_read
While data flow equations for precision tracking look as follows:
precise_after =
U [state[s].precise_before for s in insn_successors(i)]
// if some of the instruction outputs are precise,
// assume its inputs to be precise
induced_precise =
⎧ state[i].may_read if (state[i].may_write ∩ precise_after) ≠ ∅
⎨
⎩ ∅ otherwise
state[i].precise_before =
(precise_after / state[i].must_write) ∩ induced_precise
Where:
- `may_read` set represents a union of all possibly read slots
(any slot in `may_read` set might be by the instruction);
- `must_write` set represents an intersection of all possibly written slots
(any slot in `must_write` set is guaranteed to be written by the instruction).
- `may_write` set represents a union of all possibly written slots
(any slot in `may_write` set might be written by the instruction).
This means that precision tracking can be implemented as a logical
extension of liveness tracking:
- track registers as well as stack slots;
- add bit masks to represent `precise_before` and `may_write`;
- add above equations for `precise_before` computation;
- (linked registers require some additional consideration).
Such extension would allow removal of:
- precision propagation logic in verifier.c:
- backtrack_insn()
- mark_chain_precision()
- propagate_{precision,backedges}()
- push_jmp_history() and related data structures, which are only used
by precision tracking;
- add_scc_backedge() and related backedge state accumulation in
is_state_visited(), superseded by per-callchain function state
accumulated by liveness analysis.
The hope here is that unifying liveness and precision tracking will
reduce overall amount of code and make it easier to reason about.
How this helps with loops?
--------------------------
As it stands, this patch set shares the same deficiency as the current
liveness tracking mechanism. Liveness marks on stack slots cannot be
used to prune states when processing iterator-based loops:
- such states still have branches to be explored;
- meaning that not all stack slot reads have been discovered.
For example:
1: while(iter_next()) {
2: if (...)
3: r0 = *(u64 *)(r10 - 8)
4: if (...)
5: r0 = *(u64 *)(r10 - 16)
6: ...
7: }
For any checkpoint state created at instruction (1), it is only
possible to rely on read marks for slots fp[-8] and fp[-16] once all
child states of (1) have been explored. Thus, when the verifier
transitions from (7) to (1), it cannot rely on read marks.
However, sacrificing path-sensitivity makes it possible to run
analysis defined in this patch set before main verification pass,
if estimates for value ranges are available.
E.g. for the following program:
1: while(iter_next()) {
2: r0 = r10
3: r0 += r2
4: r0 = *(u64 *)(r2 + 0)
5: ...
6: }
If an estimate for `r2` range is available before the main
verification pass, it can be used to populate read marks at
instruction (4) and run the liveness analysis. Thus making
conservative liveness information available during loops verification.
Such estimates can be provided by some form of value range analysis.
Value range analysis is also necessary to address loop verification
from another angle: computing boundaries for loop induction variables
and iteration counts.
The hope here is that the new liveness tracking mechanism will support
the broader goal of making loop verification more efficient.
Validation
==========
The change was tested on three program sets:
- bpf selftests
- sched_ext
- Meta's internal set of programs
Commit [#8] enables a special mode where both the current and new
liveness analyses are enabled simultaneously. This mode signals an
error if the new algorithm considers a stack slot dead while the
current algorithm assumes it is alive. This mode was very useful for
debugging. At the time of posting, no such errors have been reported
for the above program sets.
[#8] "bpf: signal error if old liveness is more conservative than new"
Impact on memory consumption
============================
Debug patch [1] extends the kernel and veristat to count the amount of
memory allocated for storing analysis data. This patch is not included
in the submission. The maximal observed impact for the above program
sets is 2.6Mb.
Data below is shown in bytes.
For bpf selftests top 5 consumers look as follows:
File Program liveness mem
----------------------- ---------------- ------------
pyperf180.bpf.o on_event 2629740
pyperf600.bpf.o on_event 2287662
pyperf100.bpf.o on_event 1427022
test_verif_scale3.bpf.o balancer_ingress 1121283
pyperf_subprogs.bpf.o on_event 756900
For sched_ext top 5 consumers loog as follows:
File Program liveness mem
--------- ------------------------------- ------------
bpf.bpf.o lavd_enqueue 164686
bpf.bpf.o lavd_select_cpu 157393
bpf.bpf.o layered_enqueue 154817
bpf.bpf.o lavd_init 127865
bpf.bpf.o layered_dispatch 110129
For Meta's internal set of programs top consumer is 1Mb.
[1] https://github.com/kernel-patches/bpf/commit/085588e787b7998a296eb320666897d80bca7c08
Impact on verification performance
==================================
Veristat results below are reported using
`-f insns_pct>1 -f !insns<500` filter and -t option
(BPF_F_TEST_STATE_FREQ flag).
master vs patch-set, selftests (out of ~4K programs)
----------------------------------------------------
File Program Insns (A) Insns (B) Insns (DIFF)
-------------------------------- -------------------------------------- --------- --------- ---------------
cpumask_success.bpf.o test_global_mask_nested_deep_array_rcu 1622 1655 +33 (+2.03%)
strobemeta_bpf_loop.bpf.o on_event 2163 2684 +521 (+24.09%)
test_cls_redirect.bpf.o cls_redirect 36001 42515 +6514 (+18.09%)
test_cls_redirect_dynptr.bpf.o cls_redirect 2299 2339 +40 (+1.74%)
test_cls_redirect_subprogs.bpf.o cls_redirect 69545 78497 +8952 (+12.87%)
test_l4lb_noinline.bpf.o balancer_ingress 2993 3084 +91 (+3.04%)
test_xdp_noinline.bpf.o balancer_ingress_v4 3539 3616 +77 (+2.18%)
test_xdp_noinline.bpf.o balancer_ingress_v6 3608 3685 +77 (+2.13%)
master vs patch-set, sched_ext (out of 148 programs)
----------------------------------------------------
File Program Insns (A) Insns (B) Insns (DIFF)
--------- ---------------- --------- --------- ---------------
bpf.bpf.o chaos_dispatch 2257 2287 +30 (+1.33%)
bpf.bpf.o lavd_enqueue 20735 22101 +1366 (+6.59%)
bpf.bpf.o lavd_select_cpu 22100 24409 +2309 (+10.45%)
bpf.bpf.o layered_dispatch 25051 25606 +555 (+2.22%)
bpf.bpf.o p2dq_dispatch 961 990 +29 (+3.02%)
bpf.bpf.o rusty_quiescent 526 534 +8 (+1.52%)
bpf.bpf.o rusty_runnable 541 547 +6 (+1.11%)
Perf report
===========
In relative terms, the analysis does not consume much CPU time.
For example, here is a perf report collected for pyperf180 selftest:
# Children Self Command Shared Object Symbol
# ........ ........ ........ .................... ........................................
...
1.22% 1.22% veristat [kernel.kallsyms] [k] bpf_update_live_stack
...
Eduard Zingerman (10):
bpf: bpf_verifier_state->cleaned flag instead of REG_LIVE_DONE
bpf: use compute_live_registers() info in clean_func_state
bpf: remove redundant REG_LIVE_READ check in stacksafe()
bpf: declare a few utility functions as internal api
bpf: compute instructions postorder per subprogram
bpf: callchain sensitive stack liveness tracking using CFG
bpf: enable callchain sensitive stack liveness tracking
bpf: signal error if old liveness is more conservative than new
bpf: disable and remove registers chain based liveness
bpf: table based bpf_insn_successors()
Documentation/bpf/verifier.rst | 264 -------
include/linux/bpf_verifier.h | 52 +-
kernel/bpf/Makefile | 2 +-
kernel/bpf/liveness.c | 723 ++++++++++++++++++
kernel/bpf/log.c | 28 +-
kernel/bpf/verifier.c | 564 ++++----------
.../testing/selftests/bpf/prog_tests/align.c | 178 ++---
.../selftests/bpf/prog_tests/spin_lock.c | 12 +-
.../selftests/bpf/prog_tests/test_veristat.c | 44 +-
.../selftests/bpf/progs/exceptions_assert.c | 34 +-
.../selftests/bpf/progs/iters_state_safety.c | 4 +-
.../selftests/bpf/progs/iters_testmod_seq.c | 6 +-
.../bpf/progs/mem_rdonly_untrusted.c | 4 +-
.../selftests/bpf/progs/verifier_bounds.c | 38 +-
.../bpf/progs/verifier_global_ptr_args.c | 4 +-
.../selftests/bpf/progs/verifier_ldsx.c | 2 +-
.../selftests/bpf/progs/verifier_precision.c | 16 +-
.../selftests/bpf/progs/verifier_scalar_ids.c | 10 +-
.../selftests/bpf/progs/verifier_spill_fill.c | 40 +-
.../bpf/progs/verifier_subprog_precision.c | 6 +-
.../selftests/bpf/verifier/bpf_st_mem.c | 4 +-
21 files changed, 1107 insertions(+), 928 deletions(-)
create mode 100644 kernel/bpf/liveness.c
--
2.47.3
^ permalink raw reply [flat|nested] 22+ messages in thread* [PATCH bpf-next v1 01/10] bpf: bpf_verifier_state->cleaned flag instead of REG_LIVE_DONE 2025-09-11 1:04 [PATCH bpf-next v1 00/10] bpf: replace path-sensitive with path-insensitive live stack analysis Eduard Zingerman @ 2025-09-11 1:04 ` Eduard Zingerman 2025-09-11 1:04 ` [PATCH bpf-next v1 02/10] bpf: use compute_live_registers() info in clean_func_state Eduard Zingerman ` (9 subsequent siblings) 10 siblings, 0 replies; 22+ messages in thread From: Eduard Zingerman @ 2025-09-11 1:04 UTC (permalink / raw) To: bpf, ast, andrii; +Cc: daniel, martin.lau, kernel-team, yonghong.song, eddyz87 Prepare for bpf_reg_state->live field removal by introducing a separate flag to track if clean_verifier_state() had been applied to the state. No functional changes. Signed-off-by: Eduard Zingerman <eddyz87@gmail.com> --- include/linux/bpf_verifier.h | 2 +- kernel/bpf/log.c | 6 ++---- kernel/bpf/verifier.c | 13 ++++--------- 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h index 020de62bd09c..ac16da8b49dc 100644 --- a/include/linux/bpf_verifier.h +++ b/include/linux/bpf_verifier.h @@ -45,7 +45,6 @@ enum bpf_reg_liveness { REG_LIVE_READ64 = 0x2, /* likewise, but full 64-bit content matters */ REG_LIVE_READ = REG_LIVE_READ32 | REG_LIVE_READ64, REG_LIVE_WRITTEN = 0x4, /* reg was written first, screening off later reads */ - REG_LIVE_DONE = 0x8, /* liveness won't be updating this register anymore */ }; #define ITER_PREFIX "bpf_iter_" @@ -445,6 +444,7 @@ struct bpf_verifier_state { bool speculative; bool in_sleepable; + bool cleaned; /* first and last insn idx of this verifier state */ u32 first_insn_idx; diff --git a/kernel/bpf/log.c b/kernel/bpf/log.c index e4983c1303e7..0d6d7bfb2fd0 100644 --- a/kernel/bpf/log.c +++ b/kernel/bpf/log.c @@ -545,14 +545,12 @@ static char slot_type_char[] = { static void print_liveness(struct bpf_verifier_env *env, enum bpf_reg_liveness live) { - if (live & (REG_LIVE_READ | REG_LIVE_WRITTEN | REG_LIVE_DONE)) - verbose(env, "_"); + if (live & (REG_LIVE_READ | REG_LIVE_WRITTEN)) + verbose(env, "_"); if (live & REG_LIVE_READ) verbose(env, "r"); if (live & REG_LIVE_WRITTEN) verbose(env, "w"); - if (live & REG_LIVE_DONE) - verbose(env, "D"); } #define UNUM_MAX_DECIMAL U16_MAX diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index b9394f8fac0e..46a6d69de309 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -1758,6 +1758,7 @@ static int copy_verifier_state(struct bpf_verifier_state *dst_state, return err; dst_state->speculative = src->speculative; dst_state->in_sleepable = src->in_sleepable; + dst_state->cleaned = src->cleaned; dst_state->curframe = src->curframe; dst_state->branches = src->branches; dst_state->parent = src->parent; @@ -3574,11 +3575,6 @@ static int mark_reg_read(struct bpf_verifier_env *env, /* if read wasn't screened by an earlier write ... */ if (writes && state->live & REG_LIVE_WRITTEN) break; - if (verifier_bug_if(parent->live & REG_LIVE_DONE, env, - "type %s var_off %lld off %d", - reg_type_str(env, parent->type), - parent->var_off.value, parent->off)) - return -EFAULT; /* The first condition is more likely to be true than the * second, checked it first. */ @@ -18472,7 +18468,6 @@ static void clean_func_state(struct bpf_verifier_env *env, for (i = 0; i < BPF_REG_FP; i++) { live = st->regs[i].live; /* liveness must not touch this register anymore */ - st->regs[i].live |= REG_LIVE_DONE; if (!(live & REG_LIVE_READ)) /* since the register is unused, clear its state * to make further comparison simpler @@ -18483,7 +18478,6 @@ static void clean_func_state(struct bpf_verifier_env *env, for (i = 0; i < st->allocated_stack / BPF_REG_SIZE; i++) { live = st->stack[i].spilled_ptr.live; /* liveness must not touch this stack slot anymore */ - st->stack[i].spilled_ptr.live |= REG_LIVE_DONE; if (!(live & REG_LIVE_READ)) { __mark_reg_not_init(env, &st->stack[i].spilled_ptr); for (j = 0; j < BPF_REG_SIZE; j++) @@ -18497,6 +18491,7 @@ static void clean_verifier_state(struct bpf_verifier_env *env, { int i; + st->cleaned = true; for (i = 0; i <= st->curframe; i++) clean_func_state(env, st->frame[i]); } @@ -18524,7 +18519,7 @@ static void clean_verifier_state(struct bpf_verifier_env *env, * their final liveness marks are already propagated. * Hence when the verifier completes the search of state list in is_state_visited() * we can call this clean_live_states() function to mark all liveness states - * as REG_LIVE_DONE to indicate that 'parent' pointers of 'struct bpf_reg_state' + * as st->cleaned to indicate that 'parent' pointers of 'struct bpf_reg_state' * will not be used. * This function also clears the registers and stack for states that !READ * to simplify state merging. @@ -18547,7 +18542,7 @@ static void clean_live_states(struct bpf_verifier_env *env, int insn, if (sl->state.insn_idx != insn || !same_callsites(&sl->state, cur)) continue; - if (sl->state.frame[0]->regs[0].live & REG_LIVE_DONE) + if (sl->state.cleaned) /* all regs in this state in all frames were already marked */ continue; if (incomplete_read_marks(env, &sl->state)) -- 2.47.3 ^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH bpf-next v1 02/10] bpf: use compute_live_registers() info in clean_func_state 2025-09-11 1:04 [PATCH bpf-next v1 00/10] bpf: replace path-sensitive with path-insensitive live stack analysis Eduard Zingerman 2025-09-11 1:04 ` [PATCH bpf-next v1 01/10] bpf: bpf_verifier_state->cleaned flag instead of REG_LIVE_DONE Eduard Zingerman @ 2025-09-11 1:04 ` Eduard Zingerman 2025-09-11 1:04 ` [PATCH bpf-next v1 03/10] bpf: remove redundant REG_LIVE_READ check in stacksafe() Eduard Zingerman ` (8 subsequent siblings) 10 siblings, 0 replies; 22+ messages in thread From: Eduard Zingerman @ 2025-09-11 1:04 UTC (permalink / raw) To: bpf, ast, andrii; +Cc: daniel, martin.lau, kernel-team, yonghong.song, eddyz87 Prepare for bpf_reg_state->live field removal by leveraging insn_aux_data->live_regs_before instead of bpf_reg_state->live in compute_live_registers(). This is similar to logic in func_states_equal(). No changes in verification performance for selftests or sched_ext. Signed-off-by: Eduard Zingerman <eddyz87@gmail.com> --- kernel/bpf/verifier.c | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 46a6d69de309..698e6a24d2a2 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -18460,15 +18460,16 @@ static bool check_scalar_ids(u32 old_id, u32 cur_id, struct bpf_idmap *idmap) } static void clean_func_state(struct bpf_verifier_env *env, - struct bpf_func_state *st) + struct bpf_func_state *st, + u32 ip) { + u16 live_regs = env->insn_aux_data[ip].live_regs_before; enum bpf_reg_liveness live; int i, j; for (i = 0; i < BPF_REG_FP; i++) { - live = st->regs[i].live; /* liveness must not touch this register anymore */ - if (!(live & REG_LIVE_READ)) + if (!(live_regs & BIT(i))) /* since the register is unused, clear its state * to make further comparison simpler */ @@ -18489,11 +18490,13 @@ static void clean_func_state(struct bpf_verifier_env *env, static void clean_verifier_state(struct bpf_verifier_env *env, struct bpf_verifier_state *st) { - int i; + int i, ip; st->cleaned = true; - for (i = 0; i <= st->curframe; i++) - clean_func_state(env, st->frame[i]); + for (i = 0; i <= st->curframe; i++) { + ip = frame_insn_idx(st, i); + clean_func_state(env, st->frame[i], ip); + } } /* the parentage chains form a tree. -- 2.47.3 ^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH bpf-next v1 03/10] bpf: remove redundant REG_LIVE_READ check in stacksafe() 2025-09-11 1:04 [PATCH bpf-next v1 00/10] bpf: replace path-sensitive with path-insensitive live stack analysis Eduard Zingerman 2025-09-11 1:04 ` [PATCH bpf-next v1 01/10] bpf: bpf_verifier_state->cleaned flag instead of REG_LIVE_DONE Eduard Zingerman 2025-09-11 1:04 ` [PATCH bpf-next v1 02/10] bpf: use compute_live_registers() info in clean_func_state Eduard Zingerman @ 2025-09-11 1:04 ` Eduard Zingerman 2025-09-11 1:04 ` [PATCH bpf-next v1 04/10] bpf: declare a few utility functions as internal api Eduard Zingerman ` (7 subsequent siblings) 10 siblings, 0 replies; 22+ messages in thread From: Eduard Zingerman @ 2025-09-11 1:04 UTC (permalink / raw) To: bpf, ast, andrii; +Cc: daniel, martin.lau, kernel-team, yonghong.song, eddyz87 stacksafe() is called in exact == NOT_EXACT mode only for states that had been porcessed by clean_verifier_states(). The latter replaces dead stack spills with a series of STACK_INVALID masks. Such masks are already handled by stacksafe(). Signed-off-by: Eduard Zingerman <eddyz87@gmail.com> --- kernel/bpf/verifier.c | 7 ------- 1 file changed, 7 deletions(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 698e6a24d2a2..8673b955a6cd 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -18756,13 +18756,6 @@ static bool stacksafe(struct bpf_verifier_env *env, struct bpf_func_state *old, cur->stack[spi].slot_type[i % BPF_REG_SIZE])) return false; - if (!(old->stack[spi].spilled_ptr.live & REG_LIVE_READ) - && exact == NOT_EXACT) { - i += BPF_REG_SIZE - 1; - /* explored state didn't use this */ - continue; - } - if (old->stack[spi].slot_type[i % BPF_REG_SIZE] == STACK_INVALID) continue; -- 2.47.3 ^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH bpf-next v1 04/10] bpf: declare a few utility functions as internal api 2025-09-11 1:04 [PATCH bpf-next v1 00/10] bpf: replace path-sensitive with path-insensitive live stack analysis Eduard Zingerman ` (2 preceding siblings ...) 2025-09-11 1:04 ` [PATCH bpf-next v1 03/10] bpf: remove redundant REG_LIVE_READ check in stacksafe() Eduard Zingerman @ 2025-09-11 1:04 ` Eduard Zingerman 2025-09-11 1:04 ` [PATCH bpf-next v1 05/10] bpf: compute instructions postorder per subprogram Eduard Zingerman ` (6 subsequent siblings) 10 siblings, 0 replies; 22+ messages in thread From: Eduard Zingerman @ 2025-09-11 1:04 UTC (permalink / raw) To: bpf, ast, andrii; +Cc: daniel, martin.lau, kernel-team, yonghong.song, eddyz87 Namely, rename the following functions and add prototypes to bpf_verifier.h: - find_containing_subprog -> bpf_find_containing_subprog - insn_successors -> bpf_insn_successors - calls_callback -> bpf_calls_callback - fmt_stack_mask -> bpf_fmt_stack_mask Signed-off-by: Eduard Zingerman <eddyz87@gmail.com> --- include/linux/bpf_verifier.h | 5 +++++ kernel/bpf/verifier.c | 34 ++++++++++++++++------------------ 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h index ac16da8b49dc..93563564bde5 100644 --- a/include/linux/bpf_verifier.h +++ b/include/linux/bpf_verifier.h @@ -1065,4 +1065,9 @@ void print_verifier_state(struct bpf_verifier_env *env, const struct bpf_verifie void print_insn_state(struct bpf_verifier_env *env, const struct bpf_verifier_state *vstate, u32 frameno); +struct bpf_subprog_info *bpf_find_containing_subprog(struct bpf_verifier_env *env, int off); +int bpf_insn_successors(struct bpf_prog *prog, u32 idx, u32 succ[2]); +void bpf_fmt_stack_mask(char *buf, ssize_t buf_sz, u64 stack_mask); +bool bpf_calls_callback(struct bpf_verifier_env *env, int insn_idx); + #endif /* _LINUX_BPF_VERIFIER_H */ diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 8673b955a6cd..5658e1e1d5c5 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -2964,7 +2964,7 @@ static int cmp_subprogs(const void *a, const void *b) } /* Find subprogram that contains instruction at 'off' */ -static struct bpf_subprog_info *find_containing_subprog(struct bpf_verifier_env *env, int off) +struct bpf_subprog_info *bpf_find_containing_subprog(struct bpf_verifier_env *env, int off) { struct bpf_subprog_info *vals = env->subprog_info; int l, r, m; @@ -2989,7 +2989,7 @@ static int find_subprog(struct bpf_verifier_env *env, int off) { struct bpf_subprog_info *p; - p = find_containing_subprog(env, off); + p = bpf_find_containing_subprog(env, off); if (!p || p->start != off) return -ENOENT; return p - env->subprog_info; @@ -4196,7 +4196,7 @@ static void fmt_reg_mask(char *buf, ssize_t buf_sz, u32 reg_mask) } } /* format stack slots bitmask, e.g., "-8,-24,-40" for 0x15 mask */ -static void fmt_stack_mask(char *buf, ssize_t buf_sz, u64 stack_mask) +void bpf_fmt_stack_mask(char *buf, ssize_t buf_sz, u64 stack_mask) { DECLARE_BITMAP(mask, 64); bool first = true; @@ -4251,8 +4251,6 @@ static void bt_sync_linked_regs(struct backtrack_state *bt, struct bpf_jmp_histo } } -static bool calls_callback(struct bpf_verifier_env *env, int insn_idx); - /* For given verifier state backtrack_insn() is called from the last insn to * the first insn. Its purpose is to compute a bitmask of registers and * stack slots that needs precision in the parent verifier state. @@ -4279,7 +4277,7 @@ static int backtrack_insn(struct bpf_verifier_env *env, int idx, int subseq_idx, fmt_reg_mask(env->tmp_str_buf, TMP_STR_BUF_LEN, bt_reg_mask(bt)); verbose(env, "mark_precise: frame%d: regs=%s ", bt->frame, env->tmp_str_buf); - fmt_stack_mask(env->tmp_str_buf, TMP_STR_BUF_LEN, bt_stack_mask(bt)); + bpf_fmt_stack_mask(env->tmp_str_buf, TMP_STR_BUF_LEN, bt_stack_mask(bt)); verbose(env, "stack=%s before ", env->tmp_str_buf); verbose(env, "%d: ", idx); verbose_insn(env, insn); @@ -4480,7 +4478,7 @@ static int backtrack_insn(struct bpf_verifier_env *env, int idx, int subseq_idx, * backtracking, as these registers are set by the function * invoking callback. */ - if (subseq_idx >= 0 && calls_callback(env, subseq_idx)) + if (subseq_idx >= 0 && bpf_calls_callback(env, subseq_idx)) for (i = BPF_REG_1; i <= BPF_REG_5; i++) bt_clear_reg(bt, i); if (bt_reg_mask(bt) & BPF_REGMASK_ARGS) { @@ -4919,7 +4917,7 @@ static int __mark_chain_precision(struct bpf_verifier_env *env, bt_frame_reg_mask(bt, fr)); verbose(env, "mark_precise: frame%d: parent state regs=%s ", fr, env->tmp_str_buf); - fmt_stack_mask(env->tmp_str_buf, TMP_STR_BUF_LEN, + bpf_fmt_stack_mask(env->tmp_str_buf, TMP_STR_BUF_LEN, bt_frame_stack_mask(bt, fr)); verbose(env, "stack=%s: ", env->tmp_str_buf); print_verifier_state(env, st, fr, true); @@ -11004,7 +11002,7 @@ static int prepare_func_exit(struct bpf_verifier_env *env, int *insn_idx) "At callback return", "R0"); return -EINVAL; } - if (!calls_callback(env, callee->callsite)) { + if (!bpf_calls_callback(env, callee->callsite)) { verifier_bug(env, "in callback at %d, callsite %d !calls_callback", *insn_idx, callee->callsite); return -EFAULT; @@ -17269,7 +17267,7 @@ static void mark_subprog_changes_pkt_data(struct bpf_verifier_env *env, int off) { struct bpf_subprog_info *subprog; - subprog = find_containing_subprog(env, off); + subprog = bpf_find_containing_subprog(env, off); subprog->changes_pkt_data = true; } @@ -17277,7 +17275,7 @@ static void mark_subprog_might_sleep(struct bpf_verifier_env *env, int off) { struct bpf_subprog_info *subprog; - subprog = find_containing_subprog(env, off); + subprog = bpf_find_containing_subprog(env, off); subprog->might_sleep = true; } @@ -17291,8 +17289,8 @@ static void merge_callee_effects(struct bpf_verifier_env *env, int t, int w) { struct bpf_subprog_info *caller, *callee; - caller = find_containing_subprog(env, t); - callee = find_containing_subprog(env, w); + caller = bpf_find_containing_subprog(env, t); + callee = bpf_find_containing_subprog(env, w); caller->changes_pkt_data |= callee->changes_pkt_data; caller->might_sleep |= callee->might_sleep; } @@ -17362,7 +17360,7 @@ static void mark_calls_callback(struct bpf_verifier_env *env, int idx) env->insn_aux_data[idx].calls_callback = true; } -static bool calls_callback(struct bpf_verifier_env *env, int insn_idx) +bool bpf_calls_callback(struct bpf_verifier_env *env, int insn_idx) { return env->insn_aux_data[insn_idx].calls_callback; } @@ -19410,7 +19408,7 @@ static int is_state_visited(struct bpf_verifier_env *env, int insn_idx) goto hit; } } - if (calls_callback(env, insn_idx)) { + if (bpf_calls_callback(env, insn_idx)) { if (states_equal(env, &sl->state, cur, RANGE_WITHIN)) goto hit; goto skip_inf_loop_check; @@ -24136,7 +24134,7 @@ static bool can_jump(struct bpf_insn *insn) return false; } -static int insn_successors(struct bpf_prog *prog, u32 idx, u32 succ[2]) +int bpf_insn_successors(struct bpf_prog *prog, u32 idx, u32 succ[2]) { struct bpf_insn *insn = &prog->insnsi[idx]; int i = 0, insn_sz; @@ -24352,7 +24350,7 @@ static int compute_live_registers(struct bpf_verifier_env *env) u16 new_out = 0; u16 new_in = 0; - succ_num = insn_successors(env->prog, insn_idx, succ); + succ_num = bpf_insn_successors(env->prog, insn_idx, succ); for (int s = 0; s < succ_num; ++s) new_out |= state[succ[s]].in; new_in = (new_out & ~live->def) | live->use; @@ -24521,7 +24519,7 @@ static int compute_scc(struct bpf_verifier_env *env) stack[stack_sz++] = w; } /* Visit 'w' successors */ - succ_cnt = insn_successors(env->prog, w, succ); + succ_cnt = bpf_insn_successors(env->prog, w, succ); for (j = 0; j < succ_cnt; ++j) { if (pre[succ[j]]) { low[w] = min(low[w], low[succ[j]]); -- 2.47.3 ^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH bpf-next v1 05/10] bpf: compute instructions postorder per subprogram 2025-09-11 1:04 [PATCH bpf-next v1 00/10] bpf: replace path-sensitive with path-insensitive live stack analysis Eduard Zingerman ` (3 preceding siblings ...) 2025-09-11 1:04 ` [PATCH bpf-next v1 04/10] bpf: declare a few utility functions as internal api Eduard Zingerman @ 2025-09-11 1:04 ` Eduard Zingerman 2025-09-11 1:04 ` [PATCH bpf-next v1 06/10] bpf: callchain sensitive stack liveness tracking using CFG Eduard Zingerman ` (5 subsequent siblings) 10 siblings, 0 replies; 22+ messages in thread From: Eduard Zingerman @ 2025-09-11 1:04 UTC (permalink / raw) To: bpf, ast, andrii; +Cc: daniel, martin.lau, kernel-team, yonghong.song, eddyz87 The next patch would require doing postorder traversal of individual subprograms. Facilitate this by moving env->cfg.insn_postorder computation from check_cfg() to a separate pass, as check_cfg() descends into called subprograms (and it needs to, because of merge_callee_effects() logic). env->cfg.insn_postorder is used only by compute_live_registers(), this function does not track cross subprogram dependencies, thus the change does not affect it's operation. Signed-off-by: Eduard Zingerman <eddyz87@gmail.com> --- include/linux/bpf_verifier.h | 6 +++- kernel/bpf/verifier.c | 67 +++++++++++++++++++++++++++++------- 2 files changed, 59 insertions(+), 14 deletions(-) diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h index 93563564bde5..bd87e80f9423 100644 --- a/include/linux/bpf_verifier.h +++ b/include/linux/bpf_verifier.h @@ -665,6 +665,7 @@ struct bpf_subprog_info { /* 'start' has to be the first field otherwise find_subprog() won't work */ u32 start; /* insn idx of function entry point */ u32 linfo_idx; /* The idx to the main_prog->aux->linfo */ + u32 postorder_start; /* The idx to the env->cfg.insn_postorder */ u16 stack_depth; /* max. stack depth used by this function */ u16 stack_extra; /* offsets in range [stack_depth .. fastcall_stack_off) @@ -794,7 +795,10 @@ struct bpf_verifier_env { struct { int *insn_state; int *insn_stack; - /* vector of instruction indexes sorted in post-order */ + /* + * vector of instruction indexes sorted in post-order, grouped by subprogram, + * see bpf_subprog_info->postorder_start. + */ int *insn_postorder; int cur_stack; /* current position in the insn_postorder vector */ diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 5658e1e1d5c5..bdcc20d2fab6 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -17834,7 +17834,7 @@ static int visit_insn(int t, struct bpf_verifier_env *env) static int check_cfg(struct bpf_verifier_env *env) { int insn_cnt = env->prog->len; - int *insn_stack, *insn_state, *insn_postorder; + int *insn_stack, *insn_state; int ex_insn_beg, i, ret = 0; insn_state = env->cfg.insn_state = kvcalloc(insn_cnt, sizeof(int), GFP_KERNEL_ACCOUNT); @@ -17847,14 +17847,6 @@ static int check_cfg(struct bpf_verifier_env *env) return -ENOMEM; } - insn_postorder = env->cfg.insn_postorder = - kvcalloc(insn_cnt, sizeof(int), GFP_KERNEL_ACCOUNT); - if (!insn_postorder) { - kvfree(insn_state); - kvfree(insn_stack); - return -ENOMEM; - } - ex_insn_beg = env->exception_callback_subprog ? env->subprog_info[env->exception_callback_subprog].start : 0; @@ -17872,7 +17864,6 @@ static int check_cfg(struct bpf_verifier_env *env) case DONE_EXPLORING: insn_state[t] = EXPLORED; env->cfg.cur_stack--; - insn_postorder[env->cfg.cur_postorder++] = t; break; case KEEP_EXPLORING: break; @@ -17926,6 +17917,55 @@ static int check_cfg(struct bpf_verifier_env *env) return ret; } +/* + * For each subprogram 'i' fill array env->cfg.insn_subprogram sub-range + * [env->subprog_info[i].postorder_start, env->subprog_info[i+1].postorder_start) + * with indices of 'i' instructions in postorder. + */ +static int compute_postorder(struct bpf_verifier_env *env) +{ + u32 cur_postorder, i, top, stack_sz, s, succ_cnt, succ[2]; + int *stack = NULL, *postorder = NULL, *state = NULL; + + postorder = kvcalloc(env->prog->len, sizeof(int), GFP_KERNEL_ACCOUNT); + state = kvcalloc(env->prog->len, sizeof(int), GFP_KERNEL_ACCOUNT); + stack = kvcalloc(env->prog->len, sizeof(int), GFP_KERNEL_ACCOUNT); + if (!postorder || !state || !stack) { + kvfree(postorder); + kvfree(state); + kvfree(stack); + return -ENOMEM; + } + cur_postorder = 0; + for (i = 0; i < env->subprog_cnt; i++) { + env->subprog_info[i].postorder_start = cur_postorder; + stack[0] = env->subprog_info[i].start; + stack_sz = 1; + do { + top = stack[stack_sz - 1]; + if (state[top] & EXPLORED) { + postorder[cur_postorder++] = top; + stack_sz--; + continue; + } + succ_cnt = bpf_insn_successors(env->prog, top, succ); + for (s = 0; s < succ_cnt; ++s) { + if (!state[succ[s]]) { + stack[stack_sz++] = succ[s]; + state[succ[s]] |= DISCOVERED; + } + } + state[top] |= EXPLORED; + } while (stack_sz); + } + env->subprog_info[i].postorder_start = cur_postorder; + env->cfg.insn_postorder = postorder; + env->cfg.cur_postorder = cur_postorder; + kvfree(stack); + kvfree(state); + return 0; +} + static int check_abnormal_return(struct bpf_verifier_env *env) { int i; @@ -24387,9 +24427,6 @@ static int compute_live_registers(struct bpf_verifier_env *env) out: kvfree(state); - kvfree(env->cfg.insn_postorder); - env->cfg.insn_postorder = NULL; - env->cfg.cur_postorder = 0; return err; } @@ -24692,6 +24729,10 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr, __u3 if (ret < 0) goto skip_full_check; + ret = compute_postorder(env); + if (ret < 0) + goto skip_full_check; + ret = check_attach_btf_id(env); if (ret) goto skip_full_check; -- 2.47.3 ^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH bpf-next v1 06/10] bpf: callchain sensitive stack liveness tracking using CFG 2025-09-11 1:04 [PATCH bpf-next v1 00/10] bpf: replace path-sensitive with path-insensitive live stack analysis Eduard Zingerman ` (4 preceding siblings ...) 2025-09-11 1:04 ` [PATCH bpf-next v1 05/10] bpf: compute instructions postorder per subprogram Eduard Zingerman @ 2025-09-11 1:04 ` Eduard Zingerman 2025-09-11 1:04 ` [PATCH bpf-next v1 07/10] bpf: enable callchain sensitive stack liveness tracking Eduard Zingerman ` (4 subsequent siblings) 10 siblings, 0 replies; 22+ messages in thread From: Eduard Zingerman @ 2025-09-11 1:04 UTC (permalink / raw) To: bpf, ast, andrii; +Cc: daniel, martin.lau, kernel-team, yonghong.song, eddyz87 This commit adds a flow-sensitive, context-sensitive, path-insensitive data flow analysis for live stack slots: - flow-sensitive: uses program control flow graph to compute data flow values; - context-sensitive: collects data flow values for each possible call chain in a program; - path-insensitive: does not distinguish between separate control flow graph paths reaching the same instruction. Compared to the current path-sensitive analysis, this approach trades some precision for not having to enumerate every path in the program. This gives a theoretical capability to run the analysis before main verification pass. See cover letter for motivation. The basic idea is as follows: - Data flow values indicate stack slots that might be read and stack slots that are definitely written. - Data flow values are collected for each (call chain, instruction number) combination in the program. - Within a subprogram, data flow values are propagated using control flow graph. - Data flow values are transferred from entry instructions of callee subprograms to call sites in caller subprograms. In other words, a tree of all possible call chains is constructed. Each node of this tree represents a subprogram. Read and write marks are collected for each instruction of each node. Live stack slots are first computed for lower level nodes. Then, information about outer stack slots that might be read or are definitely written by a subprogram is propagated one level up, to the corresponding call instructions of the upper nodes. Procedure repeats until root node is processed. In the absence of value range analysis, stack read/write marks are collected during main verification pass, and data flow computation is triggered each time verifier.c:states_equal() needs to query the information. Implementation details are documented in kernel/bpf/liveness.c. Quantitative data about verification performance changes and memory consumption is in the cover letter. Signed-off-by: Eduard Zingerman <eddyz87@gmail.com> --- include/linux/bpf_verifier.h | 14 + kernel/bpf/Makefile | 2 +- kernel/bpf/liveness.c | 672 +++++++++++++++++++++++++++++++++++ 3 files changed, 687 insertions(+), 1 deletion(-) create mode 100644 kernel/bpf/liveness.c diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h index bd87e80f9423..2e3bdd50e2ba 100644 --- a/include/linux/bpf_verifier.h +++ b/include/linux/bpf_verifier.h @@ -745,6 +745,8 @@ struct bpf_scc_info { struct bpf_scc_visit visits[]; }; +struct bpf_liveness; + /* single container for all structs * one verifier_env per bpf_check() call */ @@ -846,6 +848,7 @@ struct bpf_verifier_env { struct bpf_insn insn_buf[INSN_BUF_SIZE]; struct bpf_insn epilogue_buf[INSN_BUF_SIZE]; struct bpf_scc_callchain callchain_buf; + struct bpf_liveness *liveness; /* array of pointers to bpf_scc_info indexed by SCC id */ struct bpf_scc_info **scc_info; u32 scc_cnt; @@ -1074,4 +1077,15 @@ int bpf_insn_successors(struct bpf_prog *prog, u32 idx, u32 succ[2]); void bpf_fmt_stack_mask(char *buf, ssize_t buf_sz, u64 stack_mask); bool bpf_calls_callback(struct bpf_verifier_env *env, int insn_idx); +int bpf_stack_liveness_init(struct bpf_verifier_env *env); +void bpf_stack_liveness_free(struct bpf_verifier_env *env); +int bpf_update_live_stack(struct bpf_verifier_env *env); +int bpf_mark_stack_read(struct bpf_verifier_env *env, u32 frameno, u32 insn_idx, u64 mask); +void bpf_mark_stack_write(struct bpf_verifier_env *env, u32 frameno, u64 mask); +int bpf_reset_stack_write_marks(struct bpf_verifier_env *env, u32 insn_idx); +int bpf_commit_stack_write_marks(struct bpf_verifier_env *env); +int bpf_live_stack_query_init(struct bpf_verifier_env *env, struct bpf_verifier_state *st); +bool bpf_stack_slot_alive(struct bpf_verifier_env *env, u32 frameno, u32 spi); +void bpf_reset_live_stack_callchain(struct bpf_verifier_env *env); + #endif /* _LINUX_BPF_VERIFIER_H */ diff --git a/kernel/bpf/Makefile b/kernel/bpf/Makefile index 269c04a24664..5df982b316ec 100644 --- a/kernel/bpf/Makefile +++ b/kernel/bpf/Makefile @@ -6,7 +6,7 @@ cflags-nogcse-$(CONFIG_X86)$(CONFIG_CC_IS_GCC) := -fno-gcse endif CFLAGS_core.o += -Wno-override-init $(cflags-nogcse-yy) -obj-$(CONFIG_BPF_SYSCALL) += syscall.o verifier.o inode.o helpers.o tnum.o log.o token.o +obj-$(CONFIG_BPF_SYSCALL) += syscall.o verifier.o inode.o helpers.o tnum.o log.o token.o liveness.o obj-$(CONFIG_BPF_SYSCALL) += bpf_iter.o map_iter.o task_iter.o prog_iter.o link_iter.o obj-$(CONFIG_BPF_SYSCALL) += hashtab.o arraymap.o percpu_freelist.o bpf_lru_list.o lpm_trie.o map_in_map.o bloom_filter.o obj-$(CONFIG_BPF_SYSCALL) += local_storage.o queue_stack_maps.o ringbuf.o diff --git a/kernel/bpf/liveness.c b/kernel/bpf/liveness.c new file mode 100644 index 000000000000..2b2e909ec944 --- /dev/null +++ b/kernel/bpf/liveness.c @@ -0,0 +1,672 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* Copyright (c) 2025 Meta Platforms, Inc. and affiliates. */ + +#include <linux/bpf_verifier.h> +#include <linux/hashtable.h> +#include <linux/jhash.h> +#include <linux/slab.h> + +/* + * This file implements live stack slots analysis. After accumulating + * stack usage data, the analysis answers queries about whether a + * particular stack slot may be read by an instruction or any of it's + * successors. This data is consumed by the verifier states caching + * mechanism to decide which stack slots are important when looking for a + * visited state corresponding to the current state. + * + * The analysis is call chain sensitive, meaning that data is collected + * and queried for tuples (call chain, subprogram instruction index). + * Such sensitivity allows identifying if some subprogram call always + * leads to writes in the caller's stack. + * + * The basic idea is as follows: + * - As the verifier accumulates a set of visited states, the analysis instance + * accumulates a conservative estimate of stack slots that can be read + * or must be written for each visited tuple (call chain, instruction index). + * - If several states happen to visit the same instruction with the same + * call chain, stack usage information for the corresponding tuple is joined: + * - "may_read" set represents a union of all possibly read slots + * (any slot in "may_read" set might be read at or after the instruction); + * - "must_write" set represents an intersection of all possibly written slots + * (any slot in "must_write" set is guaranteed to be written by the instruction). + * - The analysis is split into two phases: + * - read and write marks accumulation; + * - read and write marks propagation. + * - The propagation phase is a textbook live variable data flow analysis: + * + * state[cc, i].live_after = U [state[cc, s].live_before for s in insn_successors(i)] + * state[cc, i].live_before = + * (state[cc, i].live_after / state[cc, i].must_write) U state[i].may_read + * + * Where: + * - `U` stands for set union + * - `/` stands for set difference; + * - `cc` stands for a call chain; + * - `i` and `s` are instruction indexes; + * + * The above equations are computed for each call chain and instruction + * index until state stops changing. + * - Additionally, in order to transfer "must_write" information from a + * subprogram to call instructions invoking this subprogram, + * the "must_write_acc" set is tracked for each (cc, i) tuple. + * A set of stack slots that are guaranteed to be written by this + * instruction or any of its successors (within the subprogram). + * The equation for "must_write_acc" propagation looks as follows: + * + * state[cc, i].must_write_acc = + * ∩ [state[cc, s].must_write_acc for s in insn_successors(i)] + * U state[cc, i].must_write + * + * (An intersection of all "must_write_acc" for instruction successors + * plus all "must_write" slots for the instruction itself). + * - After the propagation phase completes for a subprogram, information from + * (cc, 0) tuple (subprogram entry) is transferred to the caller's call chain: + * - "must_write_acc" set is intersected with the call site's "must_write" set; + * - "may_read" set is added to the call site's "may_read" set. + * - Any live stack queries must be taken after the propagation phase. + * - Accumulation and propagation phases can be entered multiple times, + * at any point in time: + * - "may_read" set only grows; + * - "must_write" set only shrinks; + * - for each visited verifier state with zero branches, all relevant + * read and write marks are already recorded by the analysis instance. + * + * Technically, the analysis is facilitated by the following data structures: + * - Call chain: for given verifier state, the call chain is a tuple of call + * instruction indexes leading to the current subprogram plus the subprogram + * entry point index. + * - Function instance: for a given call chain, for each instruction in + * the current subprogram, a mapping between instruction index and a + * set of "may_read", "must_write" and other marks accumulated for this + * instruction. + * - A hash table mapping call chains to function instances. + */ + +struct callchain { + u32 callsites[MAX_CALL_FRAMES]; /* instruction pointer for each frame */ + /* cached subprog_info[*].start for functions owning the frames: + * - sp_starts[curframe] used to get insn relative index within current function; + * - sp_starts[0..current-1] used for fast callchain_frame_up(). + */ + u32 sp_starts[MAX_CALL_FRAMES]; + u32 curframe; /* depth of callsites and sp_starts arrays */ +}; + +struct per_frame_masks { + u64 may_read; /* stack slots that may be read by this instruction */ + u64 must_write; /* stack slots written by this instruction */ + u64 must_write_acc; /* stack slots written by this instruction and its successors */ + u64 live_before; /* stack slots that may be read by this insn and its successors */ +}; + +/* + * A function instance created for a specific callchain. + * Encapsulates read and write marks for each instruction in the function. + * Marks are tracked for each frame in the callchain. + */ +struct func_instance { + struct hlist_node hl_node; + struct callchain callchain; + u32 insn_cnt; /* cached number of insns in the function */ + bool updated; + bool must_write_dropped; + /* Per frame, per instruction masks, frames allocated lazily. */ + struct per_frame_masks *frames[MAX_CALL_FRAMES]; + /* For each instruction a flag telling if "must_write" had been initialized for it. */ + bool *must_write_set; +}; + +struct live_stack_query { + struct func_instance *instances[MAX_CALL_FRAMES]; /* valid in range [0..curframe] */ + u32 curframe; + u32 insn_idx; +}; + +struct bpf_liveness { + DECLARE_HASHTABLE(func_instances, 8); /* maps callchain to func_instance */ + struct live_stack_query live_stack_query; /* cache to avoid repetitive ht lookups */ + /* Cached instance corresponding to env->cur_state, avoids per-instruction ht lookup */ + struct func_instance *cur_instance; + /* + * Below fields are used to accumulate stack write marks for instruction at + * @write_insn_idx before submitting the marks to @cur_instance. + */ + u64 write_masks_acc[MAX_CALL_FRAMES]; + u32 write_insn_idx; +}; + +/* Compute callchain corresponding to state @st at depth @frameno */ +static void compute_callchain(struct bpf_verifier_env *env, struct bpf_verifier_state *st, + struct callchain *callchain, u32 frameno) +{ + struct bpf_subprog_info *subprog_info = env->subprog_info; + u32 i; + + memset(callchain, 0, sizeof(*callchain)); + for (i = 0; i <= frameno; i++) { + callchain->sp_starts[i] = subprog_info[st->frame[i]->subprogno].start; + if (i < st->curframe) + callchain->callsites[i] = st->frame[i + 1]->callsite; + } + callchain->curframe = frameno; + callchain->callsites[callchain->curframe] = callchain->sp_starts[callchain->curframe]; +} + +static u32 hash_callchain(struct callchain *callchain) +{ + return jhash2(callchain->callsites, callchain->curframe, 0); +} + +static bool same_callsites(struct callchain *a, struct callchain *b) +{ + int i; + + if (a->curframe != b->curframe) + return false; + for (i = a->curframe; i >= 0; i--) + if (a->callsites[i] != b->callsites[i]) + return false; + return true; +} + +/* + * Find existing or allocate new function instance corresponding to @callchain. + * Instances are accumulated in env->liveness->func_instances and persist + * until the end of the verification process. + */ +static struct func_instance *__lookup_instance(struct bpf_verifier_env *env, + struct callchain *callchain) +{ + struct bpf_liveness *liveness = env->liveness; + struct bpf_subprog_info *subprog; + struct func_instance *result; + u32 subprog_sz, size, key; + + key = hash_callchain(callchain); + hash_for_each_possible(liveness->func_instances, result, hl_node, key) + if (same_callsites(&result->callchain, callchain)) + return result; + + subprog = bpf_find_containing_subprog(env, callchain->sp_starts[callchain->curframe]); + subprog_sz = (subprog + 1)->start - subprog->start; + size = sizeof(struct func_instance); + result = kvzalloc(size, GFP_KERNEL_ACCOUNT); + if (!result) + return ERR_PTR(-ENOMEM); + result->must_write_set = kvcalloc(subprog_sz, sizeof(*result->must_write_set), + GFP_KERNEL_ACCOUNT); + if (!result->must_write_set) + return ERR_PTR(-ENOMEM); + memcpy(&result->callchain, callchain, sizeof(*callchain)); + result->insn_cnt = subprog_sz; + hash_add(liveness->func_instances, &result->hl_node, key); + return result; +} + +static struct func_instance *lookup_instance(struct bpf_verifier_env *env, + struct bpf_verifier_state *st, + u32 frameno) +{ + struct callchain callchain; + + compute_callchain(env, st, &callchain, frameno); + return __lookup_instance(env, &callchain); +} + +int bpf_stack_liveness_init(struct bpf_verifier_env *env) +{ + env->liveness = kvzalloc(sizeof(*env->liveness), GFP_KERNEL_ACCOUNT); + if (!env->liveness) + return -ENOMEM; + hash_init(env->liveness->func_instances); + return 0; +} + +void bpf_stack_liveness_free(struct bpf_verifier_env *env) +{ + struct func_instance *instance; + struct hlist_node *tmp; + int bkt, i; + + if (!env->liveness) + return; + hash_for_each_safe(env->liveness->func_instances, bkt, tmp, instance, hl_node) { + for (i = 0; i <= instance->callchain.curframe; i++) + kvfree(instance->frames[i]); + kvfree(instance->must_write_set); + kvfree(instance); + } + kvfree(env->liveness); +} + +/* + * Convert absolute instruction index @insn_idx to an index relative + * to start of the function corresponding to @instance. + */ +static int relative_idx(struct func_instance *instance, u32 insn_idx) +{ + return insn_idx - instance->callchain.sp_starts[instance->callchain.curframe]; +} + +static struct per_frame_masks *get_frame_masks(struct func_instance *instance, + u32 frame, u32 insn_idx) +{ + if (!instance->frames[frame]) + return NULL; + + return &instance->frames[frame][relative_idx(instance, insn_idx)]; +} + +static struct per_frame_masks *alloc_frame_masks(struct bpf_verifier_env *env, + struct func_instance *instance, + u32 frame, u32 insn_idx) +{ + struct per_frame_masks *arr; + + if (!instance->frames[frame]) { + arr = kvcalloc(instance->insn_cnt, sizeof(*arr), GFP_KERNEL_ACCOUNT); + instance->frames[frame] = arr; + if (!arr) + return ERR_PTR(-ENOMEM); + } + return get_frame_masks(instance, frame, insn_idx); +} + +void bpf_reset_live_stack_callchain(struct bpf_verifier_env *env) +{ + env->liveness->cur_instance = NULL; +} + +/* If @env->liveness->cur_instance is null, set it to instance corresponding to @env->cur_state. */ +static int ensure_cur_instance(struct bpf_verifier_env *env) +{ + struct bpf_liveness *liveness = env->liveness; + struct func_instance *instance; + + if (liveness->cur_instance) + return 0; + + instance = lookup_instance(env, env->cur_state, env->cur_state->curframe); + if (IS_ERR(instance)) + return PTR_ERR(instance); + + liveness->cur_instance = instance; + return 0; +} + +/* Accumulate may_read masks for @frame at @insn_idx */ +static int mark_stack_read(struct bpf_verifier_env *env, + struct func_instance *instance, u32 frame, u32 insn_idx, u64 mask) +{ + struct per_frame_masks *masks; + u64 new_may_read; + + masks = alloc_frame_masks(env, instance, frame, insn_idx); + if (IS_ERR(masks)) + return PTR_ERR(masks); + new_may_read = masks->may_read | mask; + if (new_may_read != masks->may_read && + ((new_may_read | masks->live_before) != masks->live_before)) + instance->updated = true; + masks->may_read |= mask; + return 0; +} + +int bpf_mark_stack_read(struct bpf_verifier_env *env, u32 frame, u32 insn_idx, u64 mask) +{ + int err; + + err = ensure_cur_instance(env); + err = err ?: mark_stack_read(env, env->liveness->cur_instance, frame, insn_idx, mask); + return err; +} + +static void reset_stack_write_marks(struct bpf_verifier_env *env, + struct func_instance *instance, u32 insn_idx) +{ + struct bpf_liveness *liveness = env->liveness; + int i; + + liveness->write_insn_idx = insn_idx; + for (i = 0; i <= instance->callchain.curframe; i++) + liveness->write_masks_acc[i] = 0; +} + +int bpf_reset_stack_write_marks(struct bpf_verifier_env *env, u32 insn_idx) +{ + struct bpf_liveness *liveness = env->liveness; + int err; + + err = ensure_cur_instance(env); + if (err) + return err; + + reset_stack_write_marks(env, liveness->cur_instance, insn_idx); + return 0; +} + +void bpf_mark_stack_write(struct bpf_verifier_env *env, u32 frame, u64 mask) +{ + env->liveness->write_masks_acc[frame] |= mask; +} + +/* + * Merge stack writes marks in @env->liveness->write_masks_acc + * with information already in @env->liveness->cur_instance. + */ +int bpf_commit_stack_write_marks(struct bpf_verifier_env *env) +{ + struct bpf_liveness *liveness = env->liveness; + struct func_instance *instance = liveness->cur_instance; + u32 idx, frame, curframe, old_must_write; + struct per_frame_masks *masks; + u64 mask; + + if (!instance) + return 0; + + curframe = instance->callchain.curframe; + idx = relative_idx(instance, liveness->write_insn_idx); + for (frame = 0; frame <= curframe; frame++) { + mask = liveness->write_masks_acc[frame]; + /* avoid allocating frames for zero masks */ + if (mask == 0 && !instance->must_write_set[idx]) + continue; + masks = alloc_frame_masks(env, instance, frame, liveness->write_insn_idx); + if (IS_ERR(masks)) + return PTR_ERR(masks); + old_must_write = masks->must_write; + /* + * If instruction at this callchain is seen for a first time, set must_write equal + * to @mask. Otherwise take intersection with the previous value. + */ + if (instance->must_write_set[idx]) + mask &= old_must_write; + if (old_must_write != mask) { + masks->must_write = mask; + instance->updated = true; + } + if (old_must_write & ~mask) + instance->must_write_dropped = true; + } + instance->must_write_set[idx] = true; + liveness->write_insn_idx = 0; + return 0; +} + +static char *fmt_callchain(struct bpf_verifier_env *env, struct callchain *callchain) +{ + char *buf_end = env->tmp_str_buf + sizeof(env->tmp_str_buf); + char *buf = env->tmp_str_buf; + int i; + + buf += snprintf(buf, buf_end - buf, "("); + for (i = 0; i <= callchain->curframe; i++) + buf += snprintf(buf, buf_end - buf, "%s%d", i ? "," : "", callchain->callsites[i]); + snprintf(buf, buf_end - buf, ")"); + return env->tmp_str_buf; +} + +static void log_mask_change(struct bpf_verifier_env *env, struct callchain *callchain, + char *pfx, u32 frame, u32 insn_idx, u64 old, u64 new) +{ + u64 changed_bits = old ^ new; + u64 new_ones = new & changed_bits; + u64 new_zeros = ~new & changed_bits; + + if (!changed_bits) + return; + bpf_log(&env->log, "%s frame %d insn %d ", fmt_callchain(env, callchain), frame, insn_idx); + if (new_ones) { + bpf_fmt_stack_mask(env->tmp_str_buf, sizeof(env->tmp_str_buf), new_ones); + bpf_log(&env->log, "+%s %s ", pfx, env->tmp_str_buf); + } + if (new_zeros) { + bpf_fmt_stack_mask(env->tmp_str_buf, sizeof(env->tmp_str_buf), new_zeros); + bpf_log(&env->log, "-%s %s", pfx, env->tmp_str_buf); + } + bpf_log(&env->log, "\n"); +} + +static struct func_instance *get_outer_instance(struct bpf_verifier_env *env, + struct func_instance *instance) +{ + struct callchain callchain = instance->callchain; + + /* Adjust @callchain to represent callchain one frame up */ + callchain.callsites[callchain.curframe] = 0; + callchain.sp_starts[callchain.curframe] = 0; + callchain.curframe--; + callchain.callsites[callchain.curframe] = callchain.sp_starts[callchain.curframe]; + return __lookup_instance(env, &callchain); +} + +static u32 callchain_subprog_start(struct callchain *callchain) +{ + return callchain->sp_starts[callchain->curframe]; +} + +/* + * Transfer @may_read and @must_write_acc marks from the first instruction of @instance, + * to the call instruction in function instance calling @instance. + */ +static int propagate_to_outer_instance(struct bpf_verifier_env *env, + struct func_instance *instance) +{ + struct callchain *callchain = &instance->callchain; + u32 this_subprog_start, callsite, frame; + struct func_instance *outer_instance; + struct per_frame_masks *insn; + int err; + + this_subprog_start = callchain_subprog_start(callchain); + outer_instance = get_outer_instance(env, instance); + callsite = callchain->callsites[callchain->curframe - 1]; + + reset_stack_write_marks(env, outer_instance, callsite); + for (frame = 0; frame < callchain->curframe; frame++) { + insn = get_frame_masks(instance, frame, this_subprog_start); + if (!insn) + continue; + bpf_mark_stack_write(env, frame, insn->must_write_acc); + err = mark_stack_read(env, outer_instance, frame, callsite, insn->live_before); + if (err) + return err; + } + bpf_commit_stack_write_marks(env); + return 0; +} + +static inline bool update_insn(struct bpf_verifier_env *env, + struct func_instance *instance, u32 frame, u32 insn_idx) +{ + struct bpf_insn_aux_data *aux = env->insn_aux_data; + u64 new_before, new_after, must_write_acc; + struct per_frame_masks *insn, *succ_insn; + u32 succ_num, s, succ[2]; + bool changed; + + succ_num = bpf_insn_successors(env->prog, insn_idx, succ); + if (unlikely(succ_num == 0)) + return false; + + changed = false; + insn = get_frame_masks(instance, frame, insn_idx); + new_before = 0; + new_after = 0; + /* + * New "must_write_acc" is an intersection of all "must_write_acc" + * of successors plus all "must_write" slots of instruction itself. + */ + must_write_acc = U64_MAX; + for (s = 0; s < succ_num; ++s) { + succ_insn = get_frame_masks(instance, frame, succ[s]); + new_after |= succ_insn->live_before; + must_write_acc &= succ_insn->must_write_acc; + } + must_write_acc |= insn->must_write; + /* + * New "live_before" is a union of all "live_before" of successors + * minus slots written by instruction plus slots read by instruction. + */ + new_before = (new_after & ~insn->must_write) | insn->may_read; + changed |= new_before != insn->live_before; + changed |= must_write_acc != insn->must_write_acc; + if (unlikely(env->log.level & BPF_LOG_LEVEL2) && + (insn->may_read || insn->must_write || + insn_idx == callchain_subprog_start(&instance->callchain) || + aux[insn_idx].prune_point)) { + log_mask_change(env, &instance->callchain, "live", + frame, insn_idx, insn->live_before, new_before); + log_mask_change(env, &instance->callchain, "written", + frame, insn_idx, insn->must_write_acc, must_write_acc); + } + insn->live_before = new_before; + insn->must_write_acc = must_write_acc; + return changed; +} + +/* Fixed-point computation of @live_before and @must_write_acc marks */ +static int update_instance(struct bpf_verifier_env *env, struct func_instance *instance) +{ + u32 i, frame, po_start, po_end, cnt, this_subprog_start; + struct callchain *callchain = &instance->callchain; + int *insn_postorder = env->cfg.insn_postorder; + struct bpf_subprog_info *subprog; + struct per_frame_masks *insn; + bool changed; + int err; + + this_subprog_start = callchain_subprog_start(callchain); + /* + * If must_write marks were updated must_write_acc needs to be reset + * (to account for the case when new must_write sets became smaller). + */ + if (instance->must_write_dropped) { + for (frame = 0; frame <= callchain->curframe; frame++) { + if (!instance->frames[frame]) + continue; + + for (i = 0; i < instance->insn_cnt; i++) { + insn = get_frame_masks(instance, frame, this_subprog_start + i); + insn->must_write_acc = 0; + } + } + } + + subprog = bpf_find_containing_subprog(env, this_subprog_start); + po_start = subprog->postorder_start; + po_end = (subprog + 1)->postorder_start; + cnt = 0; + /* repeat until fixed point is reached */ + do { + cnt++; + changed = false; + for (frame = 0; frame <= instance->callchain.curframe; frame++) { + if (!instance->frames[frame]) + continue; + + for (i = po_start; i < po_end; i++) + changed |= update_insn(env, instance, frame, insn_postorder[i]); + } + } while (changed); + + if (env->log.level & BPF_LOG_LEVEL2) + bpf_log(&env->log, "%s live stack update done in %d iterations\n", + fmt_callchain(env, callchain), cnt); + + /* transfer marks accumulated for outer frames to outer func instance (caller) */ + if (callchain->curframe > 0) { + err = propagate_to_outer_instance(env, instance); + if (err) + return err; + } + + return 0; +} + +/* + * Prepare all callchains within @env->cur_state for querying. + * This function should be called after each verifier.c:pop_stack() + * and whenever verifier.c:do_check_insn() processes subprogram exit. + * This would guarantee that visited verifier states with zero branches + * have their bpf_mark_stack_{read,write}() effects propagated in + * @env->liveness. + */ +int bpf_update_live_stack(struct bpf_verifier_env *env) +{ + struct func_instance *instance; + int err, frame; + + bpf_reset_live_stack_callchain(env); + for (frame = env->cur_state->curframe; frame >= 0; --frame) { + instance = lookup_instance(env, env->cur_state, frame); + if (IS_ERR(instance)) + return PTR_ERR(instance); + + if (instance->updated) { + err = update_instance(env, instance); + if (err) + return err; + instance->updated = false; + instance->must_write_dropped = false; + } + } + return 0; +} + +static bool is_live_before(struct func_instance *instance, u32 insn_idx, u32 frameno, u32 spi) +{ + struct per_frame_masks *masks; + + masks = get_frame_masks(instance, frameno, insn_idx); + return masks && (masks->live_before & BIT(spi)); +} + +int bpf_live_stack_query_init(struct bpf_verifier_env *env, struct bpf_verifier_state *st) +{ + struct live_stack_query *q = &env->liveness->live_stack_query; + struct func_instance *instance; + u32 frame; + + memset(q, 0, sizeof(*q)); + for (frame = 0; frame <= st->curframe; frame++) { + instance = lookup_instance(env, st, frame); + if (IS_ERR(instance)) + return PTR_ERR(instance); + q->instances[frame] = instance; + } + q->curframe = st->curframe; + q->insn_idx = st->insn_idx; + return 0; +} + +bool bpf_stack_slot_alive(struct bpf_verifier_env *env, u32 frameno, u32 spi) +{ + /* + * Slot is alive if it is read before q->st->insn_idx in current func instance, + * or if for some outer func instance: + * - alive before callsite if callsite calls callback, otherwise + * - alive after callsite + */ + struct live_stack_query *q = &env->liveness->live_stack_query; + struct func_instance *instance, *curframe_instance; + u32 i, callsite; + bool alive; + + curframe_instance = q->instances[q->curframe]; + if (is_live_before(curframe_instance, q->insn_idx, frameno, spi)) + return true; + + for (i = frameno; i < q->curframe; i++) { + callsite = curframe_instance->callchain.callsites[i]; + instance = q->instances[i]; + alive = bpf_calls_callback(env, callsite) + ? is_live_before(instance, callsite, frameno, spi) + : is_live_before(instance, callsite + 1, frameno, spi); + if (alive) + return true; + } + + return false; +} -- 2.47.3 ^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH bpf-next v1 07/10] bpf: enable callchain sensitive stack liveness tracking 2025-09-11 1:04 [PATCH bpf-next v1 00/10] bpf: replace path-sensitive with path-insensitive live stack analysis Eduard Zingerman ` (5 preceding siblings ...) 2025-09-11 1:04 ` [PATCH bpf-next v1 06/10] bpf: callchain sensitive stack liveness tracking using CFG Eduard Zingerman @ 2025-09-11 1:04 ` Eduard Zingerman 2025-09-11 1:04 ` [PATCH bpf-next v1 08/10] bpf: signal error if old liveness is more conservative than new Eduard Zingerman ` (3 subsequent siblings) 10 siblings, 0 replies; 22+ messages in thread From: Eduard Zingerman @ 2025-09-11 1:04 UTC (permalink / raw) To: bpf, ast, andrii; +Cc: daniel, martin.lau, kernel-team, yonghong.song, eddyz87 Allocate analysis instance: - Add bpf_stack_liveness_{init,free}() calls to bpf_check(). Notify the instance about any stack reads and writes: - Add bpf_mark_stack_write() call at every location where REG_LIVE_WRITTEN is recorded for a stack slot. - Add bpf_mark_stack_read() call at every location mark_reg_read() is called. - Both bpf_mark_stack_{read,write}() rely on env->liveness->cur_instance callchain being in sync with env->cur_state. It is possible to update env->liveness->cur_instance every time a mark read/write is called, but that costs a hash table lookup and is noticeable in the performance profile. Hence, manually reset env->liveness->cur_instance whenever the verifier changes env->cur_state call stack: - call bpf_reset_live_stack_callchain() when the verifier enters a subprogram; - call bpf_update_live_stack() when the verifier exits a subprogram (it implies the reset). Make sure bpf_update_live_stack() is called for a callchain before issuing liveness queries. And make sure that bpf_update_live_stack() is called for any callee callchain first: - Add bpf_update_live_stack() call at every location that processes BPF_EXIT: - exit from a subprogram; - before pop_stack() call. This makes sure that bpf_update_live_stack() is called for callee callchains before caller callchains. Make sure must_write marks are set to zero for instructions that do not always access the stack: - Wrap do_check_insn() with bpf_reset_stack_write_marks() / bpf_commit_stack_write_marks() calls. Any calls to bpf_mark_stack_write() are accumulated between this pair of calls. If no bpf_mark_stack_write() calls were made it means that the instruction does not access stack (at-least on the current verification path) and it is important to record this fact. Finally, use bpf_live_stack_query_init() / bpf_stack_slot_alive() to query stack liveness info. The manual tracking of the correct order for callee/caller bpf_update_live_stack() calls is a bit convoluted and may warrant some automation in future revisions. Signed-off-by: Eduard Zingerman <eddyz87@gmail.com> --- kernel/bpf/verifier.c | 61 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 53 insertions(+), 8 deletions(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index bdcc20d2fab6..33cb8beb8706 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -789,6 +789,7 @@ static int mark_stack_slots_dynptr(struct bpf_verifier_env *env, struct bpf_reg_ state->stack[spi].spilled_ptr.live |= REG_LIVE_WRITTEN; state->stack[spi - 1].spilled_ptr.live |= REG_LIVE_WRITTEN; + bpf_mark_stack_write(env, state->frameno, BIT(spi - 1) | BIT(spi)); return 0; } @@ -828,6 +829,7 @@ static void invalidate_dynptr(struct bpf_verifier_env *env, struct bpf_func_stat */ state->stack[spi].spilled_ptr.live |= REG_LIVE_WRITTEN; state->stack[spi - 1].spilled_ptr.live |= REG_LIVE_WRITTEN; + bpf_mark_stack_write(env, state->frameno, BIT(spi - 1) | BIT(spi)); } static int unmark_stack_slots_dynptr(struct bpf_verifier_env *env, struct bpf_reg_state *reg) @@ -939,6 +941,7 @@ static int destroy_if_dynptr_stack_slot(struct bpf_verifier_env *env, /* Same reason as unmark_stack_slots_dynptr above */ state->stack[spi].spilled_ptr.live |= REG_LIVE_WRITTEN; state->stack[spi - 1].spilled_ptr.live |= REG_LIVE_WRITTEN; + bpf_mark_stack_write(env, state->frameno, BIT(spi - 1) | BIT(spi)); return 0; } @@ -1066,6 +1069,7 @@ static int mark_stack_slots_iter(struct bpf_verifier_env *env, for (j = 0; j < BPF_REG_SIZE; j++) slot->slot_type[j] = STACK_ITER; + bpf_mark_stack_write(env, state->frameno, BIT(spi - i)); mark_stack_slot_scratched(env, spi - i); } @@ -1097,6 +1101,7 @@ static int unmark_stack_slots_iter(struct bpf_verifier_env *env, for (j = 0; j < BPF_REG_SIZE; j++) slot->slot_type[j] = STACK_INVALID; + bpf_mark_stack_write(env, state->frameno, BIT(spi - i)); mark_stack_slot_scratched(env, spi - i); } @@ -1186,6 +1191,7 @@ static int mark_stack_slot_irq_flag(struct bpf_verifier_env *env, slot = &state->stack[spi]; st = &slot->spilled_ptr; + bpf_mark_stack_write(env, reg->frameno, BIT(spi)); __mark_reg_known_zero(st); st->type = PTR_TO_STACK; /* we don't have dedicated reg type */ st->live |= REG_LIVE_WRITTEN; @@ -1244,6 +1250,7 @@ static int unmark_stack_slot_irq_flag(struct bpf_verifier_env *env, struct bpf_r /* see unmark_stack_slots_dynptr() for why we need to set REG_LIVE_WRITTEN */ st->live |= REG_LIVE_WRITTEN; + bpf_mark_stack_write(env, reg->frameno, BIT(spi)); for (i = 0; i < BPF_REG_SIZE; i++) slot->slot_type[i] = STACK_INVALID; @@ -3619,6 +3626,9 @@ static int mark_stack_slot_obj_read(struct bpf_verifier_env *env, struct bpf_reg if (err) return err; + err = bpf_mark_stack_read(env, reg->frameno, env->insn_idx, BIT(spi - i)); + if (err) + return err; mark_stack_slot_scratched(env, spi - i); } return 0; @@ -5151,6 +5161,18 @@ static int check_stack_write_fixed_off(struct bpf_verifier_env *env, if (err) return err; + if (!(off % BPF_REG_SIZE) && size == BPF_REG_SIZE) { + /* only mark the slot as written if all 8 bytes were written + * otherwise read propagation may incorrectly stop too soon + * when stack slots are partially written. + * This heuristic means that read propagation will be + * conservative, since it will add reg_live_read marks + * to stack slots all the way to first state when programs + * writes+reads less than 8 bytes + */ + bpf_mark_stack_write(env, state->frameno, BIT(spi)); + } + check_fastcall_stack_contract(env, state, insn_idx, off); mark_stack_slot_scratched(env, spi); if (reg && !(off % BPF_REG_SIZE) && reg->type == SCALAR_VALUE && env->bpf_capable) { @@ -5420,12 +5442,16 @@ static int check_stack_read_fixed_off(struct bpf_verifier_env *env, struct bpf_reg_state *reg; u8 *stype, type; int insn_flags = insn_stack_access_flags(reg_state->frameno, spi); + int err; stype = reg_state->stack[spi].slot_type; reg = ®_state->stack[spi].spilled_ptr; mark_stack_slot_scratched(env, spi); check_fastcall_stack_contract(env, state, env->insn_idx, off); + err = bpf_mark_stack_read(env, reg_state->frameno, env->insn_idx, BIT(spi)); + if (err) + return err; if (is_spilled_reg(®_state->stack[spi])) { u8 spill_size = 1; @@ -8159,6 +8185,9 @@ static int check_stack_range_initialized( mark_reg_read(env, &state->stack[spi].spilled_ptr, state->stack[spi].spilled_ptr.parent, REG_LIVE_READ64); + err = bpf_mark_stack_read(env, reg->frameno, env->insn_idx, BIT(spi)); + if (err) + return err; /* We do not set REG_LIVE_WRITTEN for stack slot, as we can not * be sure that whether stack slot is written to or not. Hence, * we must still conservatively propagate reads upwards even if @@ -10716,6 +10745,8 @@ static int check_func_call(struct bpf_verifier_env *env, struct bpf_insn *insn, /* and go analyze first insn of the callee */ *insn_idx = env->subprog_info[subprog].start - 1; + bpf_reset_live_stack_callchain(env); + if (env->log.level & BPF_LOG_LEVEL) { verbose(env, "caller:\n"); print_verifier_state(env, state, caller->frameno, true); @@ -18502,7 +18533,6 @@ static void clean_func_state(struct bpf_verifier_env *env, u32 ip) { u16 live_regs = env->insn_aux_data[ip].live_regs_before; - enum bpf_reg_liveness live; int i, j; for (i = 0; i < BPF_REG_FP; i++) { @@ -18515,9 +18545,7 @@ static void clean_func_state(struct bpf_verifier_env *env, } for (i = 0; i < st->allocated_stack / BPF_REG_SIZE; i++) { - live = st->stack[i].spilled_ptr.live; - /* liveness must not touch this stack slot anymore */ - if (!(live & REG_LIVE_READ)) { + if (!bpf_stack_slot_alive(env, st->frameno, i)) { __mark_reg_not_init(env, &st->stack[i].spilled_ptr); for (j = 0; j < BPF_REG_SIZE; j++) st->stack[i].slot_type[j] = STACK_INVALID; @@ -18530,6 +18558,7 @@ static void clean_verifier_state(struct bpf_verifier_env *env, { int i, ip; + bpf_live_stack_query_init(env, st); st->cleaned = true; for (i = 0; i <= st->curframe; i++) { ip = frame_insn_idx(st, i); @@ -18615,9 +18644,6 @@ static bool regsafe(struct bpf_verifier_env *env, struct bpf_reg_state *rold, if (exact == EXACT) return regs_exact(rold, rcur, idmap); - if (!(rold->live & REG_LIVE_READ) && exact == NOT_EXACT) - /* explored state didn't use this */ - return true; if (rold->type == NOT_INIT) { if (exact == NOT_EXACT || rcur->type == NOT_INIT) /* explored state can't have used this */ @@ -19856,6 +19882,9 @@ static int process_bpf_exit_full(struct bpf_verifier_env *env, return PROCESS_BPF_EXIT; if (env->cur_state->curframe) { + err = bpf_update_live_stack(env); + if (err) + return err; /* exit from nested function */ err = prepare_func_exit(env, &env->insn_idx); if (err) @@ -20041,7 +20070,7 @@ static int do_check(struct bpf_verifier_env *env) for (;;) { struct bpf_insn *insn; struct bpf_insn_aux_data *insn_aux; - int err; + int err, marks_err; /* reset current history entry on each new instruction */ env->cur_hist_ent = NULL; @@ -20134,7 +20163,15 @@ static int do_check(struct bpf_verifier_env *env) if (state->speculative && insn_aux->nospec) goto process_bpf_exit; + err = bpf_reset_stack_write_marks(env, env->insn_idx); + if (err) + return err; err = do_check_insn(env, &do_print_state); + if (err >= 0 || error_recoverable_with_nospec(err)) { + marks_err = bpf_commit_stack_write_marks(env); + if (marks_err) + return marks_err; + } if (error_recoverable_with_nospec(err) && state->speculative) { /* Prevent this speculative path from ever reaching the * insn that would have been unsafe to execute. @@ -20173,6 +20210,9 @@ static int do_check(struct bpf_verifier_env *env) process_bpf_exit: mark_verifier_state_scratched(env); err = update_branch_counts(env, env->cur_state); + if (err) + return err; + err = bpf_update_live_stack(env); if (err) return err; err = pop_stack(env, &prev_insn_idx, &env->insn_idx, @@ -24733,6 +24773,10 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr, __u3 if (ret < 0) goto skip_full_check; + ret = bpf_stack_liveness_init(env); + if (ret) + goto skip_full_check; + ret = check_attach_btf_id(env); if (ret) goto skip_full_check; @@ -24882,6 +24926,7 @@ int bpf_check(struct bpf_prog **prog, union bpf_attr *attr, bpfptr_t uattr, __u3 mutex_unlock(&bpf_verifier_lock); vfree(env->insn_aux_data); err_free_env: + bpf_stack_liveness_free(env); kvfree(env->cfg.insn_postorder); kvfree(env->scc_info); kvfree(env); -- 2.47.3 ^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH bpf-next v1 08/10] bpf: signal error if old liveness is more conservative than new 2025-09-11 1:04 [PATCH bpf-next v1 00/10] bpf: replace path-sensitive with path-insensitive live stack analysis Eduard Zingerman ` (6 preceding siblings ...) 2025-09-11 1:04 ` [PATCH bpf-next v1 07/10] bpf: enable callchain sensitive stack liveness tracking Eduard Zingerman @ 2025-09-11 1:04 ` Eduard Zingerman 2025-09-11 1:04 ` [PATCH bpf-next v1 09/10] bpf: disable and remove registers chain based liveness Eduard Zingerman ` (2 subsequent siblings) 10 siblings, 0 replies; 22+ messages in thread From: Eduard Zingerman @ 2025-09-11 1:04 UTC (permalink / raw) To: bpf, ast, andrii; +Cc: daniel, martin.lau, kernel-team, yonghong.song, eddyz87 Unlike the new algorithm, register chain based liveness tracking is fully path sensitive, and thus should be strictly more accurate. Validate the new algorithm by signaling an error whenever it considers a stack slot dead while the old algorithm considers it alive. Signed-off-by: Eduard Zingerman <eddyz87@gmail.com> --- include/linux/bpf_verifier.h | 1 + kernel/bpf/verifier.c | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h index 2e3bdd50e2ba..dec5da3a2e59 100644 --- a/include/linux/bpf_verifier.h +++ b/include/linux/bpf_verifier.h @@ -852,6 +852,7 @@ struct bpf_verifier_env { /* array of pointers to bpf_scc_info indexed by SCC id */ struct bpf_scc_info **scc_info; u32 scc_cnt; + bool internal_error; }; static inline struct bpf_func_info_aux *subprog_aux(struct bpf_verifier_env *env, int subprog) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 33cb8beb8706..07115f8b9e5f 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -18546,6 +18546,11 @@ static void clean_func_state(struct bpf_verifier_env *env, for (i = 0; i < st->allocated_stack / BPF_REG_SIZE; i++) { if (!bpf_stack_slot_alive(env, st->frameno, i)) { + if (st->stack[i].spilled_ptr.live & REG_LIVE_READ) { + verifier_bug(env, "incorrect live marks #1 for insn %d frameno %d spi %d\n", + env->insn_idx, st->frameno, i); + env->internal_error = true; + } __mark_reg_not_init(env, &st->stack[i].spilled_ptr); for (j = 0; j < BPF_REG_SIZE; j++) st->stack[i].slot_type[j] = STACK_INVALID; @@ -19516,6 +19521,8 @@ static int is_state_visited(struct bpf_verifier_env *env, int insn_idx) loop = incomplete_read_marks(env, &sl->state); if (states_equal(env, &sl->state, cur, loop ? RANGE_WITHIN : NOT_EXACT)) { hit: + if (env->internal_error) + return -EFAULT; sl->hit_cnt++; /* reached equivalent register/stack state, * prune the search. @@ -19630,6 +19637,8 @@ static int is_state_visited(struct bpf_verifier_env *env, int insn_idx) return 1; } miss: + if (env->internal_error) + return -EFAULT; /* when new state is not going to be added do not increase miss count. * Otherwise several loop iterations will remove the state * recorded earlier. The goal of these heuristics is to have -- 2.47.3 ^ permalink raw reply related [flat|nested] 22+ messages in thread
* [PATCH bpf-next v1 09/10] bpf: disable and remove registers chain based liveness 2025-09-11 1:04 [PATCH bpf-next v1 00/10] bpf: replace path-sensitive with path-insensitive live stack analysis Eduard Zingerman ` (7 preceding siblings ...) 2025-09-11 1:04 ` [PATCH bpf-next v1 08/10] bpf: signal error if old liveness is more conservative than new Eduard Zingerman @ 2025-09-11 1:04 ` Eduard Zingerman 2025-09-11 14:19 ` kernel test robot 2025-09-12 8:17 ` Dan Carpenter 2025-09-11 1:04 ` [PATCH bpf-next v1 10/10] bpf: table based bpf_insn_successors() Eduard Zingerman 2025-09-11 6:57 ` [syzbot ci] Re: bpf: replace path-sensitive with path-insensitive live stack analysis syzbot ci 10 siblings, 2 replies; 22+ messages in thread From: Eduard Zingerman @ 2025-09-11 1:04 UTC (permalink / raw) To: bpf, ast, andrii; +Cc: daniel, martin.lau, kernel-team, yonghong.song, eddyz87 Remove register chain based liveness tracking: - struct bpf_reg_state->{parent,live} fields are no longer needed; - REG_LIVE_WRITTEN marks are superseded by bpf_mark_stack_write() calls; - mark_reg_read() calls are superseded by bpf_mark_stack_read(); - log.c:print_liveness() is superseded by logging in liveness.c; - propagate_liveness() is superseded by bpf_update_live_stack(); - no need to establish register chains in is_state_visited() anymore; - fix a bunch of tests expecting "_w" suffixes in verifier log messages. Signed-off-by: Eduard Zingerman <eddyz87@gmail.com> --- Documentation/bpf/verifier.rst | 264 --------------- include/linux/bpf_verifier.h | 25 -- kernel/bpf/log.c | 26 +- kernel/bpf/verifier.c | 312 ++---------------- .../testing/selftests/bpf/prog_tests/align.c | 178 +++++----- .../selftests/bpf/prog_tests/spin_lock.c | 12 +- .../selftests/bpf/prog_tests/test_veristat.c | 44 +-- .../selftests/bpf/progs/exceptions_assert.c | 34 +- .../selftests/bpf/progs/iters_state_safety.c | 4 +- .../selftests/bpf/progs/iters_testmod_seq.c | 6 +- .../bpf/progs/mem_rdonly_untrusted.c | 4 +- .../selftests/bpf/progs/verifier_bounds.c | 38 +-- .../bpf/progs/verifier_global_ptr_args.c | 4 +- .../selftests/bpf/progs/verifier_ldsx.c | 2 +- .../selftests/bpf/progs/verifier_precision.c | 16 +- .../selftests/bpf/progs/verifier_scalar_ids.c | 10 +- .../selftests/bpf/progs/verifier_spill_fill.c | 40 +-- .../bpf/progs/verifier_subprog_precision.c | 6 +- .../selftests/bpf/verifier/bpf_st_mem.c | 4 +- 19 files changed, 224 insertions(+), 805 deletions(-) diff --git a/Documentation/bpf/verifier.rst b/Documentation/bpf/verifier.rst index 95e6f80a407e..510d15bc697b 100644 --- a/Documentation/bpf/verifier.rst +++ b/Documentation/bpf/verifier.rst @@ -347,270 +347,6 @@ However, only the value of register ``r1`` is important to successfully finish verification. The goal of the liveness tracking algorithm is to spot this fact and figure out that both states are actually equivalent. -Data structures -~~~~~~~~~~~~~~~ - -Liveness is tracked using the following data structures:: - - enum bpf_reg_liveness { - REG_LIVE_NONE = 0, - REG_LIVE_READ32 = 0x1, - REG_LIVE_READ64 = 0x2, - REG_LIVE_READ = REG_LIVE_READ32 | REG_LIVE_READ64, - REG_LIVE_WRITTEN = 0x4, - REG_LIVE_DONE = 0x8, - }; - - struct bpf_reg_state { - ... - struct bpf_reg_state *parent; - ... - enum bpf_reg_liveness live; - ... - }; - - struct bpf_stack_state { - struct bpf_reg_state spilled_ptr; - ... - }; - - struct bpf_func_state { - struct bpf_reg_state regs[MAX_BPF_REG]; - ... - struct bpf_stack_state *stack; - } - - struct bpf_verifier_state { - struct bpf_func_state *frame[MAX_CALL_FRAMES]; - struct bpf_verifier_state *parent; - ... - } - -* ``REG_LIVE_NONE`` is an initial value assigned to ``->live`` fields upon new - verifier state creation; - -* ``REG_LIVE_WRITTEN`` means that the value of the register (or stack slot) is - defined by some instruction verified between this verifier state's parent and - verifier state itself; - -* ``REG_LIVE_READ{32,64}`` means that the value of the register (or stack slot) - is read by a some child state of this verifier state; - -* ``REG_LIVE_DONE`` is a marker used by ``clean_verifier_state()`` to avoid - processing same verifier state multiple times and for some sanity checks; - -* ``->live`` field values are formed by combining ``enum bpf_reg_liveness`` - values using bitwise or. - -Register parentage chains -~~~~~~~~~~~~~~~~~~~~~~~~~ - -In order to propagate information between parent and child states, a *register -parentage chain* is established. Each register or stack slot is linked to a -corresponding register or stack slot in its parent state via a ``->parent`` -pointer. This link is established upon state creation in ``is_state_visited()`` -and might be modified by ``set_callee_state()`` called from -``__check_func_call()``. - -The rules for correspondence between registers / stack slots are as follows: - -* For the current stack frame, registers and stack slots of the new state are - linked to the registers and stack slots of the parent state with the same - indices. - -* For the outer stack frames, only callee saved registers (r6-r9) and stack - slots are linked to the registers and stack slots of the parent state with the - same indices. - -* When function call is processed a new ``struct bpf_func_state`` instance is - allocated, it encapsulates a new set of registers and stack slots. For this - new frame, parent links for r6-r9 and stack slots are set to nil, parent links - for r1-r5 are set to match caller r1-r5 parent links. - -This could be illustrated by the following diagram (arrows stand for -``->parent`` pointers):: - - ... ; Frame #0, some instructions - --- checkpoint #0 --- - 1 : r6 = 42 ; Frame #0 - --- checkpoint #1 --- - 2 : call foo() ; Frame #0 - ... ; Frame #1, instructions from foo() - --- checkpoint #2 --- - ... ; Frame #1, instructions from foo() - --- checkpoint #3 --- - exit ; Frame #1, return from foo() - 3 : r1 = r6 ; Frame #0 <- current state - - +-------------------------------+-------------------------------+ - | Frame #0 | Frame #1 | - Checkpoint +-------------------------------+-------------------------------+ - #0 | r0 | r1-r5 | r6-r9 | fp-8 ... | - +-------------------------------+ - ^ ^ ^ ^ - | | | | - Checkpoint +-------------------------------+ - #1 | r0 | r1-r5 | r6-r9 | fp-8 ... | - +-------------------------------+ - ^ ^ ^ - |_______|_______|_______________ - | | | - nil nil | | | nil nil - | | | | | | | - Checkpoint +-------------------------------+-------------------------------+ - #2 | r0 | r1-r5 | r6-r9 | fp-8 ... | r0 | r1-r5 | r6-r9 | fp-8 ... | - +-------------------------------+-------------------------------+ - ^ ^ ^ ^ ^ - nil nil | | | | | - | | | | | | | - Checkpoint +-------------------------------+-------------------------------+ - #3 | r0 | r1-r5 | r6-r9 | fp-8 ... | r0 | r1-r5 | r6-r9 | fp-8 ... | - +-------------------------------+-------------------------------+ - ^ ^ - nil nil | | - | | | | - Current +-------------------------------+ - state | r0 | r1-r5 | r6-r9 | fp-8 ... | - +-------------------------------+ - \ - r6 read mark is propagated via these links - all the way up to checkpoint #1. - The checkpoint #1 contains a write mark for r6 - because of instruction (1), thus read propagation - does not reach checkpoint #0 (see section below). - -Liveness marks tracking -~~~~~~~~~~~~~~~~~~~~~~~ - -For each processed instruction, the verifier tracks read and written registers -and stack slots. The main idea of the algorithm is that read marks propagate -back along the state parentage chain until they hit a write mark, which 'screens -off' earlier states from the read. The information about reads is propagated by -function ``mark_reg_read()`` which could be summarized as follows:: - - mark_reg_read(struct bpf_reg_state *state, ...): - parent = state->parent - while parent: - if state->live & REG_LIVE_WRITTEN: - break - if parent->live & REG_LIVE_READ64: - break - parent->live |= REG_LIVE_READ64 - state = parent - parent = state->parent - -Notes: - -* The read marks are applied to the **parent** state while write marks are - applied to the **current** state. The write mark on a register or stack slot - means that it is updated by some instruction in the straight-line code leading - from the parent state to the current state. - -* Details about REG_LIVE_READ32 are omitted. - -* Function ``propagate_liveness()`` (see section :ref:`read_marks_for_cache_hits`) - might override the first parent link. Please refer to the comments in the - ``propagate_liveness()`` and ``mark_reg_read()`` source code for further - details. - -Because stack writes could have different sizes ``REG_LIVE_WRITTEN`` marks are -applied conservatively: stack slots are marked as written only if write size -corresponds to the size of the register, e.g. see function ``save_register_state()``. - -Consider the following example:: - - 0: (*u64)(r10 - 8) = 0 ; define 8 bytes of fp-8 - --- checkpoint #0 --- - 1: (*u32)(r10 - 8) = 1 ; redefine lower 4 bytes - 2: r1 = (*u32)(r10 - 8) ; read lower 4 bytes defined at (1) - 3: r2 = (*u32)(r10 - 4) ; read upper 4 bytes defined at (0) - -As stated above, the write at (1) does not count as ``REG_LIVE_WRITTEN``. Should -it be otherwise, the algorithm above wouldn't be able to propagate the read mark -from (3) to checkpoint #0. - -Once the ``BPF_EXIT`` instruction is reached ``update_branch_counts()`` is -called to update the ``->branches`` counter for each verifier state in a chain -of parent verifier states. When the ``->branches`` counter reaches zero the -verifier state becomes a valid entry in a set of cached verifier states. - -Each entry of the verifier states cache is post-processed by a function -``clean_live_states()``. This function marks all registers and stack slots -without ``REG_LIVE_READ{32,64}`` marks as ``NOT_INIT`` or ``STACK_INVALID``. -Registers/stack slots marked in this way are ignored in function ``stacksafe()`` -called from ``states_equal()`` when a state cache entry is considered for -equivalence with a current state. - -Now it is possible to explain how the example from the beginning of the section -works:: - - 0: call bpf_get_prandom_u32() - 1: r1 = 0 - 2: if r0 == 0 goto +1 - 3: r0 = 1 - --- checkpoint[0] --- - 4: r0 = r1 - 5: exit - -* At instruction #2 branching point is reached and state ``{ r0 == 0, r1 == 0, pc == 4 }`` - is pushed to states processing queue (pc stands for program counter). - -* At instruction #4: - - * ``checkpoint[0]`` states cache entry is created: ``{ r0 == 1, r1 == 0, pc == 4 }``; - * ``checkpoint[0].r0`` is marked as written; - * ``checkpoint[0].r1`` is marked as read; - -* At instruction #5 exit is reached and ``checkpoint[0]`` can now be processed - by ``clean_live_states()``. After this processing ``checkpoint[0].r1`` has a - read mark and all other registers and stack slots are marked as ``NOT_INIT`` - or ``STACK_INVALID`` - -* The state ``{ r0 == 0, r1 == 0, pc == 4 }`` is popped from the states queue - and is compared against a cached state ``{ r1 == 0, pc == 4 }``, the states - are considered equivalent. - -.. _read_marks_for_cache_hits: - -Read marks propagation for cache hits -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -Another point is the handling of read marks when a previously verified state is -found in the states cache. Upon cache hit verifier must behave in the same way -as if the current state was verified to the program exit. This means that all -read marks, present on registers and stack slots of the cached state, must be -propagated over the parentage chain of the current state. Example below shows -why this is important. Function ``propagate_liveness()`` handles this case. - -Consider the following state parentage chain (S is a starting state, A-E are -derived states, -> arrows show which state is derived from which):: - - r1 read - <------------- A[r1] == 0 - C[r1] == 0 - S ---> A ---> B ---> exit E[r1] == 1 - | - ` ---> C ---> D - | - ` ---> E ^ - |___ suppose all these - ^ states are at insn #Y - | - suppose all these - states are at insn #X - -* Chain of states ``S -> A -> B -> exit`` is verified first. - -* While ``B -> exit`` is verified, register ``r1`` is read and this read mark is - propagated up to state ``A``. - -* When chain of states ``C -> D`` is verified the state ``D`` turns out to be - equivalent to state ``B``. - -* The read mark for ``r1`` has to be propagated to state ``C``, otherwise state - ``C`` might get mistakenly marked as equivalent to state ``E`` even though - values for register ``r1`` differ between ``C`` and ``E``. - Understanding eBPF verifier messages ==================================== diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h index dec5da3a2e59..c7515da8500c 100644 --- a/include/linux/bpf_verifier.h +++ b/include/linux/bpf_verifier.h @@ -26,27 +26,6 @@ /* Patch buffer size */ #define INSN_BUF_SIZE 32 -/* Liveness marks, used for registers and spilled-regs (in stack slots). - * Read marks propagate upwards until they find a write mark; they record that - * "one of this state's descendants read this reg" (and therefore the reg is - * relevant for states_equal() checks). - * Write marks collect downwards and do not propagate; they record that "the - * straight-line code that reached this state (from its parent) wrote this reg" - * (and therefore that reads propagated from this state or its descendants - * should not propagate to its parent). - * A state with a write mark can receive read marks; it just won't propagate - * them to its parent, since the write mark is a property, not of the state, - * but of the link between it and its parent. See mark_reg_read() and - * mark_stack_slot_read() in kernel/bpf/verifier.c. - */ -enum bpf_reg_liveness { - REG_LIVE_NONE = 0, /* reg hasn't been read or written this branch */ - REG_LIVE_READ32 = 0x1, /* reg was read, so we're sensitive to initial value */ - REG_LIVE_READ64 = 0x2, /* likewise, but full 64-bit content matters */ - REG_LIVE_READ = REG_LIVE_READ32 | REG_LIVE_READ64, - REG_LIVE_WRITTEN = 0x4, /* reg was written first, screening off later reads */ -}; - #define ITER_PREFIX "bpf_iter_" enum bpf_iter_state { @@ -211,8 +190,6 @@ struct bpf_reg_state { * allowed and has the same effect as bpf_sk_release(sk). */ u32 ref_obj_id; - /* parentage chain for liveness checking */ - struct bpf_reg_state *parent; /* Inside the callee two registers can be both PTR_TO_STACK like * R1=fp-8 and R2=fp-8, but one of them points to this function stack * while another to the caller's stack. To differentiate them 'frameno' @@ -225,7 +202,6 @@ struct bpf_reg_state { * patching which only happens after main verification finished. */ s32 subreg_def; - enum bpf_reg_liveness live; /* if (!precise && SCALAR_VALUE) min/max/tnum don't affect safety */ bool precise; }; @@ -852,7 +828,6 @@ struct bpf_verifier_env { /* array of pointers to bpf_scc_info indexed by SCC id */ struct bpf_scc_info **scc_info; u32 scc_cnt; - bool internal_error; }; static inline struct bpf_func_info_aux *subprog_aux(struct bpf_verifier_env *env, int subprog) diff --git a/kernel/bpf/log.c b/kernel/bpf/log.c index 0d6d7bfb2fd0..f50533169cc3 100644 --- a/kernel/bpf/log.c +++ b/kernel/bpf/log.c @@ -542,17 +542,6 @@ static char slot_type_char[] = { [STACK_IRQ_FLAG] = 'f' }; -static void print_liveness(struct bpf_verifier_env *env, - enum bpf_reg_liveness live) -{ - if (live & (REG_LIVE_READ | REG_LIVE_WRITTEN)) - verbose(env, "_"); - if (live & REG_LIVE_READ) - verbose(env, "r"); - if (live & REG_LIVE_WRITTEN) - verbose(env, "w"); -} - #define UNUM_MAX_DECIMAL U16_MAX #define SNUM_MAX_DECIMAL S16_MAX #define SNUM_MIN_DECIMAL S16_MIN @@ -770,7 +759,6 @@ void print_verifier_state(struct bpf_verifier_env *env, const struct bpf_verifie if (!print_all && !reg_scratched(env, i)) continue; verbose(env, " R%d", i); - print_liveness(env, reg->live); verbose(env, "="); print_reg_state(env, state, reg); } @@ -803,9 +791,7 @@ void print_verifier_state(struct bpf_verifier_env *env, const struct bpf_verifie break; types_buf[j] = '\0'; - verbose(env, " fp%d", (-i - 1) * BPF_REG_SIZE); - print_liveness(env, reg->live); - verbose(env, "=%s", types_buf); + verbose(env, " fp%d=%s", (-i - 1) * BPF_REG_SIZE, types_buf); print_reg_state(env, state, reg); break; case STACK_DYNPTR: @@ -814,7 +800,6 @@ void print_verifier_state(struct bpf_verifier_env *env, const struct bpf_verifie reg = &state->stack[i].spilled_ptr; verbose(env, " fp%d", (-i - 1) * BPF_REG_SIZE); - print_liveness(env, reg->live); verbose(env, "=dynptr_%s(", dynptr_type_str(reg->dynptr.type)); if (reg->id) verbose_a("id=%d", reg->id); @@ -829,9 +814,8 @@ void print_verifier_state(struct bpf_verifier_env *env, const struct bpf_verifie if (!reg->ref_obj_id) continue; - verbose(env, " fp%d", (-i - 1) * BPF_REG_SIZE); - print_liveness(env, reg->live); - verbose(env, "=iter_%s(ref_id=%d,state=%s,depth=%u)", + verbose(env, " fp%d=iter_%s(ref_id=%d,state=%s,depth=%u)", + (-i - 1) * BPF_REG_SIZE, iter_type_str(reg->iter.btf, reg->iter.btf_id), reg->ref_obj_id, iter_state_str(reg->iter.state), reg->iter.depth); @@ -839,9 +823,7 @@ void print_verifier_state(struct bpf_verifier_env *env, const struct bpf_verifie case STACK_MISC: case STACK_ZERO: default: - verbose(env, " fp%d", (-i - 1) * BPF_REG_SIZE); - print_liveness(env, reg->live); - verbose(env, "=%s", types_buf); + verbose(env, " fp%d=%s", (-i - 1) * BPF_REG_SIZE, types_buf); break; } } diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 07115f8b9e5f..6efb555a1e8a 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -787,8 +787,6 @@ static int mark_stack_slots_dynptr(struct bpf_verifier_env *env, struct bpf_reg_ state->stack[spi - 1].spilled_ptr.ref_obj_id = id; } - state->stack[spi].spilled_ptr.live |= REG_LIVE_WRITTEN; - state->stack[spi - 1].spilled_ptr.live |= REG_LIVE_WRITTEN; bpf_mark_stack_write(env, state->frameno, BIT(spi - 1) | BIT(spi)); return 0; @@ -806,29 +804,6 @@ static void invalidate_dynptr(struct bpf_verifier_env *env, struct bpf_func_stat __mark_reg_not_init(env, &state->stack[spi].spilled_ptr); __mark_reg_not_init(env, &state->stack[spi - 1].spilled_ptr); - /* Why do we need to set REG_LIVE_WRITTEN for STACK_INVALID slot? - * - * While we don't allow reading STACK_INVALID, it is still possible to - * do <8 byte writes marking some but not all slots as STACK_MISC. Then, - * helpers or insns can do partial read of that part without failing, - * but check_stack_range_initialized, check_stack_read_var_off, and - * check_stack_read_fixed_off will do mark_reg_read for all 8-bytes of - * the slot conservatively. Hence we need to prevent those liveness - * marking walks. - * - * This was not a problem before because STACK_INVALID is only set by - * default (where the default reg state has its reg->parent as NULL), or - * in clean_live_states after REG_LIVE_DONE (at which point - * mark_reg_read won't walk reg->parent chain), but not randomly during - * verifier state exploration (like we did above). Hence, for our case - * parentage chain will still be live (i.e. reg->parent may be - * non-NULL), while earlier reg->parent was NULL, so we need - * REG_LIVE_WRITTEN to screen off read marker propagation when it is - * done later on reads or by mark_dynptr_read as well to unnecessary - * mark registers in verifier state. - */ - state->stack[spi].spilled_ptr.live |= REG_LIVE_WRITTEN; - state->stack[spi - 1].spilled_ptr.live |= REG_LIVE_WRITTEN; bpf_mark_stack_write(env, state->frameno, BIT(spi - 1) | BIT(spi)); } @@ -938,9 +913,6 @@ static int destroy_if_dynptr_stack_slot(struct bpf_verifier_env *env, __mark_reg_not_init(env, &state->stack[spi].spilled_ptr); __mark_reg_not_init(env, &state->stack[spi - 1].spilled_ptr); - /* Same reason as unmark_stack_slots_dynptr above */ - state->stack[spi].spilled_ptr.live |= REG_LIVE_WRITTEN; - state->stack[spi - 1].spilled_ptr.live |= REG_LIVE_WRITTEN; bpf_mark_stack_write(env, state->frameno, BIT(spi - 1) | BIT(spi)); return 0; @@ -1059,7 +1031,6 @@ static int mark_stack_slots_iter(struct bpf_verifier_env *env, else st->type |= PTR_UNTRUSTED; } - st->live |= REG_LIVE_WRITTEN; st->ref_obj_id = i == 0 ? id : 0; st->iter.btf = btf; st->iter.btf_id = btf_id; @@ -1095,9 +1066,6 @@ static int unmark_stack_slots_iter(struct bpf_verifier_env *env, __mark_reg_not_init(env, st); - /* see unmark_stack_slots_dynptr() for why we need to set REG_LIVE_WRITTEN */ - st->live |= REG_LIVE_WRITTEN; - for (j = 0; j < BPF_REG_SIZE; j++) slot->slot_type[j] = STACK_INVALID; @@ -1194,7 +1162,6 @@ static int mark_stack_slot_irq_flag(struct bpf_verifier_env *env, bpf_mark_stack_write(env, reg->frameno, BIT(spi)); __mark_reg_known_zero(st); st->type = PTR_TO_STACK; /* we don't have dedicated reg type */ - st->live |= REG_LIVE_WRITTEN; st->ref_obj_id = id; st->irq.kfunc_class = kfunc_class; @@ -1248,8 +1215,6 @@ static int unmark_stack_slot_irq_flag(struct bpf_verifier_env *env, struct bpf_r __mark_reg_not_init(env, st); - /* see unmark_stack_slots_dynptr() for why we need to set REG_LIVE_WRITTEN */ - st->live |= REG_LIVE_WRITTEN; bpf_mark_stack_write(env, reg->frameno, BIT(spi)); for (i = 0; i < BPF_REG_SIZE; i++) @@ -2886,8 +2851,6 @@ static void init_reg_state(struct bpf_verifier_env *env, for (i = 0; i < MAX_BPF_REG; i++) { mark_reg_not_init(env, regs, i); - regs[i].live = REG_LIVE_NONE; - regs[i].parent = NULL; regs[i].subreg_def = DEF_NOT_SUBREG; } @@ -3568,64 +3531,12 @@ static int check_subprogs(struct bpf_verifier_env *env) return 0; } -/* Parentage chain of this register (or stack slot) should take care of all - * issues like callee-saved registers, stack slot allocation time, etc. - */ -static int mark_reg_read(struct bpf_verifier_env *env, - const struct bpf_reg_state *state, - struct bpf_reg_state *parent, u8 flag) -{ - bool writes = parent == state->parent; /* Observe write marks */ - int cnt = 0; - - while (parent) { - /* if read wasn't screened by an earlier write ... */ - if (writes && state->live & REG_LIVE_WRITTEN) - break; - /* The first condition is more likely to be true than the - * second, checked it first. - */ - if ((parent->live & REG_LIVE_READ) == flag || - parent->live & REG_LIVE_READ64) - /* The parentage chain never changes and - * this parent was already marked as LIVE_READ. - * There is no need to keep walking the chain again and - * keep re-marking all parents as LIVE_READ. - * This case happens when the same register is read - * multiple times without writes into it in-between. - * Also, if parent has the stronger REG_LIVE_READ64 set, - * then no need to set the weak REG_LIVE_READ32. - */ - break; - /* ... then we depend on parent's value */ - parent->live |= flag; - /* REG_LIVE_READ64 overrides REG_LIVE_READ32. */ - if (flag == REG_LIVE_READ64) - parent->live &= ~REG_LIVE_READ32; - state = parent; - parent = state->parent; - writes = true; - cnt++; - } - - if (env->longest_mark_read_walk < cnt) - env->longest_mark_read_walk = cnt; - return 0; -} - static int mark_stack_slot_obj_read(struct bpf_verifier_env *env, struct bpf_reg_state *reg, int spi, int nr_slots) { - struct bpf_func_state *state = func(env, reg); int err, i; for (i = 0; i < nr_slots; i++) { - struct bpf_reg_state *st = &state->stack[spi - i].spilled_ptr; - - err = mark_reg_read(env, st, st->parent, REG_LIVE_READ64); - if (err) - return err; - err = bpf_mark_stack_read(env, reg->frameno, env->insn_idx, BIT(spi - i)); if (err) return err; @@ -3837,15 +3748,13 @@ static int __check_reg_arg(struct bpf_verifier_env *env, struct bpf_reg_state *r if (rw64) mark_insn_zext(env, reg); - return mark_reg_read(env, reg, reg->parent, - rw64 ? REG_LIVE_READ64 : REG_LIVE_READ32); + return 0; } else { /* check whether register used as dest operand can be written to */ if (regno == BPF_REG_FP) { verbose(env, "frame pointer is read only\n"); return -EACCES; } - reg->live |= REG_LIVE_WRITTEN; reg->subreg_def = rw64 ? DEF_NOT_SUBREG : env->insn_idx + 1; if (t == DST_OP) mark_reg_unknown(env, regs, regno); @@ -5050,12 +4959,7 @@ static void assign_scalar_id_before_mov(struct bpf_verifier_env *env, /* Copy src state preserving dst->parent and dst->live fields */ static void copy_register_state(struct bpf_reg_state *dst, const struct bpf_reg_state *src) { - struct bpf_reg_state *parent = dst->parent; - enum bpf_reg_liveness live = dst->live; - *dst = *src; - dst->parent = parent; - dst->live = live; } static void save_register_state(struct bpf_verifier_env *env, @@ -5066,8 +4970,6 @@ static void save_register_state(struct bpf_verifier_env *env, int i; copy_register_state(&state->stack[spi].spilled_ptr, reg); - if (size == BPF_REG_SIZE) - state->stack[spi].spilled_ptr.live |= REG_LIVE_WRITTEN; for (i = BPF_REG_SIZE; i > BPF_REG_SIZE - size; i--) state->stack[spi].slot_type[i - 1] = STACK_SPILL; @@ -5216,17 +5118,6 @@ static int check_stack_write_fixed_off(struct bpf_verifier_env *env, for (i = 0; i < BPF_REG_SIZE; i++) scrub_spilled_slot(&state->stack[spi].slot_type[i]); - /* only mark the slot as written if all 8 bytes were written - * otherwise read propagation may incorrectly stop too soon - * when stack slots are partially written. - * This heuristic means that read propagation will be - * conservative, since it will add reg_live_read marks - * to stack slots all the way to first state when programs - * writes+reads less than 8 bytes - */ - if (size == BPF_REG_SIZE) - state->stack[spi].spilled_ptr.live |= REG_LIVE_WRITTEN; - /* when we zero initialize stack slots mark them as such */ if ((reg && register_is_null(reg)) || (!reg && is_bpf_st_mem(insn) && insn->imm == 0)) { @@ -5419,7 +5310,6 @@ static void mark_reg_stack_read(struct bpf_verifier_env *env, /* have read misc data from the stack */ mark_reg_unknown(env, state->regs, dst_regno); } - state->regs[dst_regno].live |= REG_LIVE_WRITTEN; } /* Read the stack at 'off' and put the results into the register indicated by @@ -5466,7 +5356,6 @@ static int check_stack_read_fixed_off(struct bpf_verifier_env *env, return -EACCES; } - mark_reg_read(env, reg, reg->parent, REG_LIVE_READ64); if (dst_regno < 0) return 0; @@ -5520,7 +5409,6 @@ static int check_stack_read_fixed_off(struct bpf_verifier_env *env, insn_flags = 0; /* not restoring original register state */ } } - state->regs[dst_regno].live |= REG_LIVE_WRITTEN; } else if (dst_regno >= 0) { /* restore register state from stack */ copy_register_state(&state->regs[dst_regno], reg); @@ -5528,7 +5416,6 @@ static int check_stack_read_fixed_off(struct bpf_verifier_env *env, * has its liveness marks cleared by is_state_visited() * which resets stack/reg liveness for state transitions */ - state->regs[dst_regno].live |= REG_LIVE_WRITTEN; } else if (__is_pointer_value(env->allow_ptr_leaks, reg)) { /* If dst_regno==-1, the caller is asking us whether * it is acceptable to use this value as a SCALAR_VALUE @@ -5540,7 +5427,6 @@ static int check_stack_read_fixed_off(struct bpf_verifier_env *env, off); return -EACCES; } - mark_reg_read(env, reg, reg->parent, REG_LIVE_READ64); } else { for (i = 0; i < size; i++) { type = stype[(slot - i) % BPF_REG_SIZE]; @@ -5554,7 +5440,6 @@ static int check_stack_read_fixed_off(struct bpf_verifier_env *env, off, i, size); return -EACCES; } - mark_reg_read(env, reg, reg->parent, REG_LIVE_READ64); if (dst_regno >= 0) mark_reg_stack_read(env, reg_state, off, off + size, dst_regno); insn_flags = 0; /* we are not restoring spilled register */ @@ -8182,13 +8067,10 @@ static int check_stack_range_initialized( /* reading any byte out of 8-byte 'spill_slot' will cause * the whole slot to be marked as 'read' */ - mark_reg_read(env, &state->stack[spi].spilled_ptr, - state->stack[spi].spilled_ptr.parent, - REG_LIVE_READ64); err = bpf_mark_stack_read(env, reg->frameno, env->insn_idx, BIT(spi)); if (err) return err; - /* We do not set REG_LIVE_WRITTEN for stack slot, as we can not + /* We do not call bpf_mark_stack_write(), as we can not * be sure that whether stack slot is written to or not. Hence, * we must still conservatively propagate reads upwards even if * helper may write to the entire memory range. @@ -11022,8 +10904,7 @@ static int prepare_func_exit(struct bpf_verifier_env *env, int *insn_idx) } /* we are going to rely on register's precise value */ - err = mark_reg_read(env, r0, r0->parent, REG_LIVE_READ64); - err = err ?: mark_chain_precision(env, BPF_REG_0); + err = mark_chain_precision(env, BPF_REG_0); if (err) return err; @@ -11927,17 +11808,11 @@ static void __mark_btf_func_reg_size(struct bpf_verifier_env *env, struct bpf_re if (regno == BPF_REG_0) { /* Function return value */ - reg->live |= REG_LIVE_WRITTEN; reg->subreg_def = reg_size == sizeof(u64) ? DEF_NOT_SUBREG : env->insn_idx + 1; - } else { + } else if (reg_size == sizeof(u64)) { /* Function argument */ - if (reg_size == sizeof(u64)) { - mark_insn_zext(env, reg); - mark_reg_read(env, reg, reg->parent, REG_LIVE_READ64); - } else { - mark_reg_read(env, reg, reg->parent, REG_LIVE_READ32); - } + mark_insn_zext(env, reg); } } @@ -15681,7 +15556,6 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn) */ assign_scalar_id_before_mov(env, src_reg); copy_register_state(dst_reg, src_reg); - dst_reg->live |= REG_LIVE_WRITTEN; dst_reg->subreg_def = DEF_NOT_SUBREG; } else { /* case: R1 = (s8, s16 s32)R2 */ @@ -15700,7 +15574,6 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn) if (!no_sext) dst_reg->id = 0; coerce_reg_to_size_sx(dst_reg, insn->off >> 3); - dst_reg->live |= REG_LIVE_WRITTEN; dst_reg->subreg_def = DEF_NOT_SUBREG; } else { mark_reg_unknown(env, regs, insn->dst_reg); @@ -15726,7 +15599,6 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn) */ if (!is_src_reg_u32) dst_reg->id = 0; - dst_reg->live |= REG_LIVE_WRITTEN; dst_reg->subreg_def = env->insn_idx + 1; } else { /* case: W1 = (s8, s16)W2 */ @@ -15737,7 +15609,6 @@ static int check_alu_op(struct bpf_verifier_env *env, struct bpf_insn *insn) copy_register_state(dst_reg, src_reg); if (!no_sext) dst_reg->id = 0; - dst_reg->live |= REG_LIVE_WRITTEN; dst_reg->subreg_def = env->insn_idx + 1; coerce_subreg_to_size_sx(dst_reg, insn->off >> 3); } @@ -18546,11 +18417,6 @@ static void clean_func_state(struct bpf_verifier_env *env, for (i = 0; i < st->allocated_stack / BPF_REG_SIZE; i++) { if (!bpf_stack_slot_alive(env, st->frameno, i)) { - if (st->stack[i].spilled_ptr.live & REG_LIVE_READ) { - verifier_bug(env, "incorrect live marks #1 for insn %d frameno %d spi %d\n", - env->insn_idx, st->frameno, i); - env->internal_error = true; - } __mark_reg_not_init(env, &st->stack[i].spilled_ptr); for (j = 0; j < BPF_REG_SIZE; j++) st->stack[i].slot_type[j] = STACK_INVALID; @@ -18579,25 +18445,23 @@ static void clean_verifier_state(struct bpf_verifier_env *env, * but a lot of states will get revised from liveness point of view when * the verifier explores other branches. * Example: - * 1: r0 = 1 + * 1: *(u64)(r10 - 8) = 1 * 2: if r1 == 100 goto pc+1 - * 3: r0 = 2 - * 4: exit - * when the verifier reaches exit insn the register r0 in the state list of - * insn 2 will be seen as !REG_LIVE_READ. Then the verifier pops the other_branch - * of insn 2 and goes exploring further. At the insn 4 it will walk the - * parentage chain from insn 4 into insn 2 and will mark r0 as REG_LIVE_READ. + * 3: *(u64)(r10 - 8) = 2 + * 4: r0 = *(u64)(r10 - 8) + * 5: exit + * when the verifier reaches exit insn the stack slot -8 in the state list of + * insn 2 is not yet marked alive. Then the verifier pops the other_branch + * of insn 2 and goes exploring further. After the insn 4 read, liveness + * analysis would propagate read mark for -8 at insn 2. * * Since the verifier pushes the branch states as it sees them while exploring * the program the condition of walking the branch instruction for the second * time means that all states below this branch were already explored and * their final liveness marks are already propagated. * Hence when the verifier completes the search of state list in is_state_visited() - * we can call this clean_live_states() function to mark all liveness states - * as st->cleaned to indicate that 'parent' pointers of 'struct bpf_reg_state' - * will not be used. - * This function also clears the registers and stack for states that !READ - * to simplify state merging. + * we can call this clean_live_states() function to clear dead the registers and stack + * slots to simplify state merging. * * Important note here that walking the same branch instruction in the callee * doesn't meant that the states are DONE. The verifier has to compare @@ -18772,7 +18636,6 @@ static struct bpf_reg_state unbound_reg; static __init int unbound_reg_init(void) { __mark_reg_unknown_imprecise(&unbound_reg); - unbound_reg.live |= REG_LIVE_READ; return 0; } late_initcall(unbound_reg_init); @@ -19067,91 +18930,6 @@ static bool states_equal(struct bpf_verifier_env *env, return true; } -/* Return 0 if no propagation happened. Return negative error code if error - * happened. Otherwise, return the propagated bit. - */ -static int propagate_liveness_reg(struct bpf_verifier_env *env, - struct bpf_reg_state *reg, - struct bpf_reg_state *parent_reg) -{ - u8 parent_flag = parent_reg->live & REG_LIVE_READ; - u8 flag = reg->live & REG_LIVE_READ; - int err; - - /* When comes here, read flags of PARENT_REG or REG could be any of - * REG_LIVE_READ64, REG_LIVE_READ32, REG_LIVE_NONE. There is no need - * of propagation if PARENT_REG has strongest REG_LIVE_READ64. - */ - if (parent_flag == REG_LIVE_READ64 || - /* Or if there is no read flag from REG. */ - !flag || - /* Or if the read flag from REG is the same as PARENT_REG. */ - parent_flag == flag) - return 0; - - err = mark_reg_read(env, reg, parent_reg, flag); - if (err) - return err; - - return flag; -} - -/* A write screens off any subsequent reads; but write marks come from the - * straight-line code between a state and its parent. When we arrive at an - * equivalent state (jump target or such) we didn't arrive by the straight-line - * code, so read marks in the state must propagate to the parent regardless - * of the state's write marks. That's what 'parent == state->parent' comparison - * in mark_reg_read() is for. - */ -static int propagate_liveness(struct bpf_verifier_env *env, - const struct bpf_verifier_state *vstate, - struct bpf_verifier_state *vparent, - bool *changed) -{ - struct bpf_reg_state *state_reg, *parent_reg; - struct bpf_func_state *state, *parent; - int i, frame, err = 0; - bool tmp = false; - - changed = changed ?: &tmp; - if (vparent->curframe != vstate->curframe) { - WARN(1, "propagate_live: parent frame %d current frame %d\n", - vparent->curframe, vstate->curframe); - return -EFAULT; - } - /* Propagate read liveness of registers... */ - BUILD_BUG_ON(BPF_REG_FP + 1 != MAX_BPF_REG); - for (frame = 0; frame <= vstate->curframe; frame++) { - parent = vparent->frame[frame]; - state = vstate->frame[frame]; - parent_reg = parent->regs; - state_reg = state->regs; - /* We don't need to worry about FP liveness, it's read-only */ - for (i = frame < vstate->curframe ? BPF_REG_6 : 0; i < BPF_REG_FP; i++) { - err = propagate_liveness_reg(env, &state_reg[i], - &parent_reg[i]); - if (err < 0) - return err; - *changed |= err > 0; - if (err == REG_LIVE_READ64) - mark_insn_zext(env, &parent_reg[i]); - } - - /* Propagate stack slots. */ - for (i = 0; i < state->allocated_stack / BPF_REG_SIZE && - i < parent->allocated_stack / BPF_REG_SIZE; i++) { - parent_reg = &parent->stack[i].spilled_ptr; - state_reg = &state->stack[i].spilled_ptr; - err = propagate_liveness_reg(env, state_reg, - parent_reg); - *changed |= err > 0; - if (err < 0) - return err; - } - } - return 0; -} - /* find precise scalars in the previous equivalent state and * propagate them into the current state */ @@ -19171,8 +18949,7 @@ static int propagate_precision(struct bpf_verifier_env *env, first = true; for (i = 0; i < BPF_REG_FP; i++, state_reg++) { if (state_reg->type != SCALAR_VALUE || - !state_reg->precise || - !(state_reg->live & REG_LIVE_READ)) + !state_reg->precise) continue; if (env->log.level & BPF_LOG_LEVEL2) { if (first) @@ -19189,8 +18966,7 @@ static int propagate_precision(struct bpf_verifier_env *env, continue; state_reg = &state->stack[i].spilled_ptr; if (state_reg->type != SCALAR_VALUE || - !state_reg->precise || - !(state_reg->live & REG_LIVE_READ)) + !state_reg->precise) continue; if (env->log.level & BPF_LOG_LEVEL2) { if (first) @@ -19240,9 +19016,6 @@ static int propagate_backedges(struct bpf_verifier_env *env, struct bpf_scc_visi changed = false; for (backedge = visit->backedges; backedge; backedge = backedge->next) { st = &backedge->state; - err = propagate_liveness(env, st->equal_state, st, &changed); - if (err) - return err; err = propagate_precision(env, st->equal_state, st, &changed); if (err) return err; @@ -19266,7 +19039,7 @@ static bool states_maybe_looping(struct bpf_verifier_state *old, fcur = cur->frame[fr]; for (i = 0; i < MAX_BPF_REG; i++) if (memcmp(&fold->regs[i], &fcur->regs[i], - offsetof(struct bpf_reg_state, parent))) + offsetof(struct bpf_reg_state, frameno))) return false; return true; } @@ -19364,7 +19137,7 @@ static int is_state_visited(struct bpf_verifier_env *env, int insn_idx) struct bpf_verifier_state_list *sl; struct bpf_verifier_state *cur = env->cur_state, *new; bool force_new_state, add_new_state, loop; - int i, j, n, err, states_cnt = 0; + int n, err, states_cnt = 0; struct list_head *pos, *tmp, *head; force_new_state = env->test_state_freq || is_force_checkpoint(env, insn_idx) || @@ -19521,20 +19294,7 @@ static int is_state_visited(struct bpf_verifier_env *env, int insn_idx) loop = incomplete_read_marks(env, &sl->state); if (states_equal(env, &sl->state, cur, loop ? RANGE_WITHIN : NOT_EXACT)) { hit: - if (env->internal_error) - return -EFAULT; sl->hit_cnt++; - /* reached equivalent register/stack state, - * prune the search. - * Registers read by the continuation are read by us. - * If we have any write marks in env->cur_state, they - * will prevent corresponding reads in the continuation - * from reaching our parent (an explored_state). Our - * own state will get the read marks recorded, but - * they'll be immediately forgotten as we're pruning - * this state and will pop a new one. - */ - err = propagate_liveness(env, &sl->state, cur, NULL); /* if previous state reached the exit with precision and * current state is equivalent to it (except precision marks) @@ -19637,8 +19397,6 @@ static int is_state_visited(struct bpf_verifier_env *env, int insn_idx) return 1; } miss: - if (env->internal_error) - return -EFAULT; /* when new state is not going to be added do not increase miss count. * Otherwise several loop iterations will remove the state * recorded earlier. The goal of these heuristics is to have @@ -19724,38 +19482,6 @@ static int is_state_visited(struct bpf_verifier_env *env, int insn_idx) cur->dfs_depth = new->dfs_depth + 1; clear_jmp_history(cur); list_add(&new_sl->node, head); - - /* connect new state to parentage chain. Current frame needs all - * registers connected. Only r6 - r9 of the callers are alive (pushed - * to the stack implicitly by JITs) so in callers' frames connect just - * r6 - r9 as an optimization. Callers will have r1 - r5 connected to - * the state of the call instruction (with WRITTEN set), and r0 comes - * from callee with its full parentage chain, anyway. - */ - /* clear write marks in current state: the writes we did are not writes - * our child did, so they don't screen off its reads from us. - * (There are no read marks in current state, because reads always mark - * their parent and current state never has children yet. Only - * explored_states can get read marks.) - */ - for (j = 0; j <= cur->curframe; j++) { - for (i = j < cur->curframe ? BPF_REG_6 : 0; i < BPF_REG_FP; i++) - cur->frame[j]->regs[i].parent = &new->frame[j]->regs[i]; - for (i = 0; i < BPF_REG_FP; i++) - cur->frame[j]->regs[i].live = REG_LIVE_NONE; - } - - /* all stack frames are accessible from callee, clear them all */ - for (j = 0; j <= cur->curframe; j++) { - struct bpf_func_state *frame = cur->frame[j]; - struct bpf_func_state *newframe = new->frame[j]; - - for (i = 0; i < frame->allocated_stack / BPF_REG_SIZE; i++) { - frame->stack[i].spilled_ptr.live = REG_LIVE_NONE; - frame->stack[i].spilled_ptr.parent = - &newframe->stack[i].spilled_ptr; - } - } return 0; } diff --git a/tools/testing/selftests/bpf/prog_tests/align.c b/tools/testing/selftests/bpf/prog_tests/align.c index 1d53a8561ee2..24c509ce4e5b 100644 --- a/tools/testing/selftests/bpf/prog_tests/align.c +++ b/tools/testing/selftests/bpf/prog_tests/align.c @@ -42,11 +42,11 @@ static struct bpf_align_test tests[] = { .matches = { {0, "R1", "ctx()"}, {0, "R10", "fp0"}, - {0, "R3_w", "2"}, - {1, "R3_w", "4"}, - {2, "R3_w", "8"}, - {3, "R3_w", "16"}, - {4, "R3_w", "32"}, + {0, "R3", "2"}, + {1, "R3", "4"}, + {2, "R3", "8"}, + {3, "R3", "16"}, + {4, "R3", "32"}, }, }, { @@ -70,17 +70,17 @@ static struct bpf_align_test tests[] = { .matches = { {0, "R1", "ctx()"}, {0, "R10", "fp0"}, - {0, "R3_w", "1"}, - {1, "R3_w", "2"}, - {2, "R3_w", "4"}, - {3, "R3_w", "8"}, - {4, "R3_w", "16"}, - {5, "R3_w", "1"}, - {6, "R4_w", "32"}, - {7, "R4_w", "16"}, - {8, "R4_w", "8"}, - {9, "R4_w", "4"}, - {10, "R4_w", "2"}, + {0, "R3", "1"}, + {1, "R3", "2"}, + {2, "R3", "4"}, + {3, "R3", "8"}, + {4, "R3", "16"}, + {5, "R3", "1"}, + {6, "R4", "32"}, + {7, "R4", "16"}, + {8, "R4", "8"}, + {9, "R4", "4"}, + {10, "R4", "2"}, }, }, { @@ -99,12 +99,12 @@ static struct bpf_align_test tests[] = { .matches = { {0, "R1", "ctx()"}, {0, "R10", "fp0"}, - {0, "R3_w", "4"}, - {1, "R3_w", "8"}, - {2, "R3_w", "10"}, - {3, "R4_w", "8"}, - {4, "R4_w", "12"}, - {5, "R4_w", "14"}, + {0, "R3", "4"}, + {1, "R3", "8"}, + {2, "R3", "10"}, + {3, "R4", "8"}, + {4, "R4", "12"}, + {5, "R4", "14"}, }, }, { @@ -121,10 +121,10 @@ static struct bpf_align_test tests[] = { .matches = { {0, "R1", "ctx()"}, {0, "R10", "fp0"}, - {0, "R3_w", "7"}, - {1, "R3_w", "7"}, - {2, "R3_w", "14"}, - {3, "R3_w", "56"}, + {0, "R3", "7"}, + {1, "R3", "7"}, + {2, "R3", "14"}, + {3, "R3", "56"}, }, }, @@ -162,19 +162,19 @@ static struct bpf_align_test tests[] = { }, .prog_type = BPF_PROG_TYPE_SCHED_CLS, .matches = { - {6, "R0_w", "pkt(off=8,r=8)"}, - {6, "R3_w", "var_off=(0x0; 0xff)"}, - {7, "R3_w", "var_off=(0x0; 0x1fe)"}, - {8, "R3_w", "var_off=(0x0; 0x3fc)"}, - {9, "R3_w", "var_off=(0x0; 0x7f8)"}, - {10, "R3_w", "var_off=(0x0; 0xff0)"}, - {12, "R3_w", "pkt_end()"}, - {17, "R4_w", "var_off=(0x0; 0xff)"}, - {18, "R4_w", "var_off=(0x0; 0x1fe0)"}, - {19, "R4_w", "var_off=(0x0; 0xff0)"}, - {20, "R4_w", "var_off=(0x0; 0x7f8)"}, - {21, "R4_w", "var_off=(0x0; 0x3fc)"}, - {22, "R4_w", "var_off=(0x0; 0x1fe)"}, + {6, "R0", "pkt(off=8,r=8)"}, + {6, "R3", "var_off=(0x0; 0xff)"}, + {7, "R3", "var_off=(0x0; 0x1fe)"}, + {8, "R3", "var_off=(0x0; 0x3fc)"}, + {9, "R3", "var_off=(0x0; 0x7f8)"}, + {10, "R3", "var_off=(0x0; 0xff0)"}, + {12, "R3", "pkt_end()"}, + {17, "R4", "var_off=(0x0; 0xff)"}, + {18, "R4", "var_off=(0x0; 0x1fe0)"}, + {19, "R4", "var_off=(0x0; 0xff0)"}, + {20, "R4", "var_off=(0x0; 0x7f8)"}, + {21, "R4", "var_off=(0x0; 0x3fc)"}, + {22, "R4", "var_off=(0x0; 0x1fe)"}, }, }, { @@ -195,16 +195,16 @@ static struct bpf_align_test tests[] = { }, .prog_type = BPF_PROG_TYPE_SCHED_CLS, .matches = { - {6, "R3_w", "var_off=(0x0; 0xff)"}, - {7, "R4_w", "var_off=(0x0; 0xff)"}, - {8, "R4_w", "var_off=(0x0; 0xff)"}, - {9, "R4_w", "var_off=(0x0; 0xff)"}, - {10, "R4_w", "var_off=(0x0; 0x1fe)"}, - {11, "R4_w", "var_off=(0x0; 0xff)"}, - {12, "R4_w", "var_off=(0x0; 0x3fc)"}, - {13, "R4_w", "var_off=(0x0; 0xff)"}, - {14, "R4_w", "var_off=(0x0; 0x7f8)"}, - {15, "R4_w", "var_off=(0x0; 0xff0)"}, + {6, "R3", "var_off=(0x0; 0xff)"}, + {7, "R4", "var_off=(0x0; 0xff)"}, + {8, "R4", "var_off=(0x0; 0xff)"}, + {9, "R4", "var_off=(0x0; 0xff)"}, + {10, "R4", "var_off=(0x0; 0x1fe)"}, + {11, "R4", "var_off=(0x0; 0xff)"}, + {12, "R4", "var_off=(0x0; 0x3fc)"}, + {13, "R4", "var_off=(0x0; 0xff)"}, + {14, "R4", "var_off=(0x0; 0x7f8)"}, + {15, "R4", "var_off=(0x0; 0xff0)"}, }, }, { @@ -235,14 +235,14 @@ static struct bpf_align_test tests[] = { }, .prog_type = BPF_PROG_TYPE_SCHED_CLS, .matches = { - {2, "R5_w", "pkt(r=0)"}, - {4, "R5_w", "pkt(off=14,r=0)"}, - {5, "R4_w", "pkt(off=14,r=0)"}, + {2, "R5", "pkt(r=0)"}, + {4, "R5", "pkt(off=14,r=0)"}, + {5, "R4", "pkt(off=14,r=0)"}, {9, "R2", "pkt(r=18)"}, {10, "R5", "pkt(off=14,r=18)"}, - {10, "R4_w", "var_off=(0x0; 0xff)"}, - {13, "R4_w", "var_off=(0x0; 0xffff)"}, - {14, "R4_w", "var_off=(0x0; 0xffff)"}, + {10, "R4", "var_off=(0x0; 0xff)"}, + {13, "R4", "var_off=(0x0; 0xffff)"}, + {14, "R4", "var_off=(0x0; 0xffff)"}, }, }, { @@ -299,12 +299,12 @@ static struct bpf_align_test tests[] = { /* Calculated offset in R6 has unknown value, but known * alignment of 4. */ - {6, "R2_w", "pkt(r=8)"}, - {7, "R6_w", "var_off=(0x0; 0x3fc)"}, + {6, "R2", "pkt(r=8)"}, + {7, "R6", "var_off=(0x0; 0x3fc)"}, /* Offset is added to packet pointer R5, resulting in * known fixed offset, and variable offset from R6. */ - {11, "R5_w", "pkt(id=1,off=14,"}, + {11, "R5", "pkt(id=1,off=14,"}, /* At the time the word size load is performed from R5, * it's total offset is NET_IP_ALIGN + reg->off (0) + * reg->aux_off (14) which is 16. Then the variable @@ -320,12 +320,12 @@ static struct bpf_align_test tests[] = { * instruction to validate R5 state. We also check * that R4 is what it should be in such case. */ - {18, "R4_w", "var_off=(0x0; 0x3fc)"}, - {18, "R5_w", "var_off=(0x0; 0x3fc)"}, + {18, "R4", "var_off=(0x0; 0x3fc)"}, + {18, "R5", "var_off=(0x0; 0x3fc)"}, /* Constant offset is added to R5, resulting in * reg->off of 14. */ - {19, "R5_w", "pkt(id=2,off=14,"}, + {19, "R5", "pkt(id=2,off=14,"}, /* At the time the word size load is performed from R5, * its total fixed offset is NET_IP_ALIGN + reg->off * (14) which is 16. Then the variable offset is 4-byte @@ -337,21 +337,21 @@ static struct bpf_align_test tests[] = { /* Constant offset is added to R5 packet pointer, * resulting in reg->off value of 14. */ - {26, "R5_w", "pkt(off=14,r=8)"}, + {26, "R5", "pkt(off=14,r=8)"}, /* Variable offset is added to R5, resulting in a * variable offset of (4n). See comment for insn #18 * for R4 = R5 trick. */ - {28, "R4_w", "var_off=(0x0; 0x3fc)"}, - {28, "R5_w", "var_off=(0x0; 0x3fc)"}, + {28, "R4", "var_off=(0x0; 0x3fc)"}, + {28, "R5", "var_off=(0x0; 0x3fc)"}, /* Constant is added to R5 again, setting reg->off to 18. */ - {29, "R5_w", "pkt(id=3,off=18,"}, + {29, "R5", "pkt(id=3,off=18,"}, /* And once more we add a variable; resulting var_off * is still (4n), fixed offset is not changed. * Also, we create a new reg->id. */ - {31, "R4_w", "var_off=(0x0; 0x7fc)"}, - {31, "R5_w", "var_off=(0x0; 0x7fc)"}, + {31, "R4", "var_off=(0x0; 0x7fc)"}, + {31, "R5", "var_off=(0x0; 0x7fc)"}, /* At the time the word size load is performed from R5, * its total fixed offset is NET_IP_ALIGN + reg->off (18) * which is 20. Then the variable offset is (4n), so @@ -397,12 +397,12 @@ static struct bpf_align_test tests[] = { /* Calculated offset in R6 has unknown value, but known * alignment of 4. */ - {6, "R2_w", "pkt(r=8)"}, - {7, "R6_w", "var_off=(0x0; 0x3fc)"}, + {6, "R2", "pkt(r=8)"}, + {7, "R6", "var_off=(0x0; 0x3fc)"}, /* Adding 14 makes R6 be (4n+2) */ - {8, "R6_w", "var_off=(0x2; 0x7fc)"}, + {8, "R6", "var_off=(0x2; 0x7fc)"}, /* Packet pointer has (4n+2) offset */ - {11, "R5_w", "var_off=(0x2; 0x7fc)"}, + {11, "R5", "var_off=(0x2; 0x7fc)"}, {12, "R4", "var_off=(0x2; 0x7fc)"}, /* At the time the word size load is performed from R5, * its total fixed offset is NET_IP_ALIGN + reg->off (0) @@ -414,11 +414,11 @@ static struct bpf_align_test tests[] = { /* Newly read value in R6 was shifted left by 2, so has * known alignment of 4. */ - {17, "R6_w", "var_off=(0x0; 0x3fc)"}, + {17, "R6", "var_off=(0x0; 0x3fc)"}, /* Added (4n) to packet pointer's (4n+2) var_off, giving * another (4n+2). */ - {19, "R5_w", "var_off=(0x2; 0xffc)"}, + {19, "R5", "var_off=(0x2; 0xffc)"}, {20, "R4", "var_off=(0x2; 0xffc)"}, /* At the time the word size load is performed from R5, * its total fixed offset is NET_IP_ALIGN + reg->off (0) @@ -459,18 +459,18 @@ static struct bpf_align_test tests[] = { .prog_type = BPF_PROG_TYPE_SCHED_CLS, .result = REJECT, .matches = { - {3, "R5_w", "pkt_end()"}, + {3, "R5", "pkt_end()"}, /* (ptr - ptr) << 2 == unknown, (4n) */ - {5, "R5_w", "var_off=(0x0; 0xfffffffffffffffc)"}, + {5, "R5", "var_off=(0x0; 0xfffffffffffffffc)"}, /* (4n) + 14 == (4n+2). We blow our bounds, because * the add could overflow. */ - {6, "R5_w", "var_off=(0x2; 0xfffffffffffffffc)"}, + {6, "R5", "var_off=(0x2; 0xfffffffffffffffc)"}, /* Checked s>=0 */ {9, "R5", "var_off=(0x2; 0x7ffffffffffffffc)"}, /* packet pointer + nonnegative (4n+2) */ - {11, "R6_w", "var_off=(0x2; 0x7ffffffffffffffc)"}, - {12, "R4_w", "var_off=(0x2; 0x7ffffffffffffffc)"}, + {11, "R6", "var_off=(0x2; 0x7ffffffffffffffc)"}, + {12, "R4", "var_off=(0x2; 0x7ffffffffffffffc)"}, /* NET_IP_ALIGN + (4n+2) == (4n), alignment is fine. * We checked the bounds, but it might have been able * to overflow if the packet pointer started in the @@ -478,7 +478,7 @@ static struct bpf_align_test tests[] = { * So we did not get a 'range' on R6, and the access * attempt will fail. */ - {15, "R6_w", "var_off=(0x2; 0x7ffffffffffffffc)"}, + {15, "R6", "var_off=(0x2; 0x7ffffffffffffffc)"}, } }, { @@ -513,12 +513,12 @@ static struct bpf_align_test tests[] = { /* Calculated offset in R6 has unknown value, but known * alignment of 4. */ - {6, "R2_w", "pkt(r=8)"}, - {8, "R6_w", "var_off=(0x0; 0x3fc)"}, + {6, "R2", "pkt(r=8)"}, + {8, "R6", "var_off=(0x0; 0x3fc)"}, /* Adding 14 makes R6 be (4n+2) */ - {9, "R6_w", "var_off=(0x2; 0x7fc)"}, + {9, "R6", "var_off=(0x2; 0x7fc)"}, /* New unknown value in R7 is (4n) */ - {10, "R7_w", "var_off=(0x0; 0x3fc)"}, + {10, "R7", "var_off=(0x0; 0x3fc)"}, /* Subtracting it from R6 blows our unsigned bounds */ {11, "R6", "var_off=(0x2; 0xfffffffffffffffc)"}, /* Checked s>= 0 */ @@ -566,16 +566,16 @@ static struct bpf_align_test tests[] = { /* Calculated offset in R6 has unknown value, but known * alignment of 4. */ - {6, "R2_w", "pkt(r=8)"}, - {9, "R6_w", "var_off=(0x0; 0x3c)"}, + {6, "R2", "pkt(r=8)"}, + {9, "R6", "var_off=(0x0; 0x3c)"}, /* Adding 14 makes R6 be (4n+2) */ - {10, "R6_w", "var_off=(0x2; 0x7c)"}, + {10, "R6", "var_off=(0x2; 0x7c)"}, /* Subtracting from packet pointer overflows ubounds */ - {13, "R5_w", "var_off=(0xffffffffffffff82; 0x7c)"}, + {13, "R5", "var_off=(0xffffffffffffff82; 0x7c)"}, /* New unknown value in R7 is (4n), >= 76 */ - {14, "R7_w", "var_off=(0x0; 0x7fc)"}, + {14, "R7", "var_off=(0x0; 0x7fc)"}, /* Adding it to packet pointer gives nice bounds again */ - {16, "R5_w", "var_off=(0x2; 0x7fc)"}, + {16, "R5", "var_off=(0x2; 0x7fc)"}, /* At the time the word size load is performed from R5, * its total fixed offset is NET_IP_ALIGN + reg->off (0) * which is 2. Then the variable offset is (4n+2), so diff --git a/tools/testing/selftests/bpf/prog_tests/spin_lock.c b/tools/testing/selftests/bpf/prog_tests/spin_lock.c index e3ea5dc2f697..254fbfeab06a 100644 --- a/tools/testing/selftests/bpf/prog_tests/spin_lock.c +++ b/tools/testing/selftests/bpf/prog_tests/spin_lock.c @@ -13,22 +13,22 @@ static struct { const char *err_msg; } spin_lock_fail_tests[] = { { "lock_id_kptr_preserve", - "5: (bf) r1 = r0 ; R0_w=ptr_foo(id=2,ref_obj_id=2) " - "R1_w=ptr_foo(id=2,ref_obj_id=2) refs=2\n6: (85) call bpf_this_cpu_ptr#154\n" + "5: (bf) r1 = r0 ; R0=ptr_foo(id=2,ref_obj_id=2) " + "R1=ptr_foo(id=2,ref_obj_id=2) refs=2\n6: (85) call bpf_this_cpu_ptr#154\n" "R1 type=ptr_ expected=percpu_ptr_" }, { "lock_id_global_zero", - "; R1_w=map_value(map=.data.A,ks=4,vs=4)\n2: (85) call bpf_this_cpu_ptr#154\n" + "; R1=map_value(map=.data.A,ks=4,vs=4)\n2: (85) call bpf_this_cpu_ptr#154\n" "R1 type=map_value expected=percpu_ptr_" }, { "lock_id_mapval_preserve", "[0-9]\\+: (bf) r1 = r0 ;" - " R0_w=map_value(id=1,map=array_map,ks=4,vs=8)" - " R1_w=map_value(id=1,map=array_map,ks=4,vs=8)\n" + " R0=map_value(id=1,map=array_map,ks=4,vs=8)" + " R1=map_value(id=1,map=array_map,ks=4,vs=8)\n" "[0-9]\\+: (85) call bpf_this_cpu_ptr#154\n" "R1 type=map_value expected=percpu_ptr_" }, { "lock_id_innermapval_preserve", "[0-9]\\+: (bf) r1 = r0 ;" " R0=map_value(id=2,ks=4,vs=8)" - " R1_w=map_value(id=2,ks=4,vs=8)\n" + " R1=map_value(id=2,ks=4,vs=8)\n" "[0-9]\\+: (85) call bpf_this_cpu_ptr#154\n" "R1 type=map_value expected=percpu_ptr_" }, { "lock_id_mismatch_kptr_kptr", "bpf_spin_unlock of different lock" }, diff --git a/tools/testing/selftests/bpf/prog_tests/test_veristat.c b/tools/testing/selftests/bpf/prog_tests/test_veristat.c index 367f47e4a936..b38c16b4247f 100644 --- a/tools/testing/selftests/bpf/prog_tests/test_veristat.c +++ b/tools/testing/selftests/bpf/prog_tests/test_veristat.c @@ -75,26 +75,26 @@ static void test_set_global_vars_succeeds(void) " -vl2 > %s", fix->veristat, fix->tmpfile); read(fix->fd, fix->output, fix->sz); - __CHECK_STR("_w=0xf000000000000001 ", "var_s64 = 0xf000000000000001"); - __CHECK_STR("_w=0xfedcba9876543210 ", "var_u64 = 0xfedcba9876543210"); - __CHECK_STR("_w=0x80000000 ", "var_s32 = -0x80000000"); - __CHECK_STR("_w=0x76543210 ", "var_u32 = 0x76543210"); - __CHECK_STR("_w=0x8000 ", "var_s16 = -32768"); - __CHECK_STR("_w=0xecec ", "var_u16 = 60652"); - __CHECK_STR("_w=128 ", "var_s8 = -128"); - __CHECK_STR("_w=255 ", "var_u8 = 255"); - __CHECK_STR("_w=11 ", "var_ea = EA2"); - __CHECK_STR("_w=12 ", "var_eb = EB2"); - __CHECK_STR("_w=13 ", "var_ec = EC2"); - __CHECK_STR("_w=1 ", "var_b = 1"); - __CHECK_STR("_w=170 ", "struct1[2].struct2[1][2].u.var_u8[2]=170"); - __CHECK_STR("_w=0xaaaa ", "union1.var_u16 = 0xaaaa"); - __CHECK_STR("_w=171 ", "arr[3]= 171"); - __CHECK_STR("_w=172 ", "arr[EA2] =172"); - __CHECK_STR("_w=10 ", "enum_arr[EC2]=EA3"); - __CHECK_STR("_w=173 ", "matrix[31][7][11]=173"); - __CHECK_STR("_w=174 ", "struct1[2].struct2[1][2].u.mat[5][3]=174"); - __CHECK_STR("_w=175 ", "struct11[7][5].struct2[0][1].u.mat[3][0]=175"); + __CHECK_STR("=0xf000000000000001 ", "var_s64 = 0xf000000000000001"); + __CHECK_STR("=0xfedcba9876543210 ", "var_u64 = 0xfedcba9876543210"); + __CHECK_STR("=0x80000000 ", "var_s32 = -0x80000000"); + __CHECK_STR("=0x76543210 ", "var_u32 = 0x76543210"); + __CHECK_STR("=0x8000 ", "var_s16 = -32768"); + __CHECK_STR("=0xecec ", "var_u16 = 60652"); + __CHECK_STR("=128 ", "var_s8 = -128"); + __CHECK_STR("=255 ", "var_u8 = 255"); + __CHECK_STR("=11 ", "var_ea = EA2"); + __CHECK_STR("=12 ", "var_eb = EB2"); + __CHECK_STR("=13 ", "var_ec = EC2"); + __CHECK_STR("=1 ", "var_b = 1"); + __CHECK_STR("=170 ", "struct1[2].struct2[1][2].u.var_u8[2]=170"); + __CHECK_STR("=0xaaaa ", "union1.var_u16 = 0xaaaa"); + __CHECK_STR("=171 ", "arr[3]= 171"); + __CHECK_STR("=172 ", "arr[EA2] =172"); + __CHECK_STR("=10 ", "enum_arr[EC2]=EA3"); + __CHECK_STR("=173 ", "matrix[31][7][11]=173"); + __CHECK_STR("=174 ", "struct1[2].struct2[1][2].u.mat[5][3]=174"); + __CHECK_STR("=175 ", "struct11[7][5].struct2[0][1].u.mat[3][0]=175"); out: teardown_fixture(fix); @@ -117,8 +117,8 @@ static void test_set_global_vars_from_file_succeeds(void) SYS(out, "%s set_global_vars.bpf.o -G \"@%s\" -vl2 > %s", fix->veristat, input_file, fix->tmpfile); read(fix->fd, fix->output, fix->sz); - __CHECK_STR("_w=0x8000 ", "var_s16 = -32768"); - __CHECK_STR("_w=0xecec ", "var_u16 = 60652"); + __CHECK_STR("=0x8000 ", "var_s16 = -32768"); + __CHECK_STR("=0xecec ", "var_u16 = 60652"); out: close(fd); diff --git a/tools/testing/selftests/bpf/progs/exceptions_assert.c b/tools/testing/selftests/bpf/progs/exceptions_assert.c index 5e0a1ca96d4e..a01c2736890f 100644 --- a/tools/testing/selftests/bpf/progs/exceptions_assert.c +++ b/tools/testing/selftests/bpf/progs/exceptions_assert.c @@ -18,43 +18,43 @@ return *(u64 *)num; \ } -__msg(": R0_w=0xffffffff80000000") +__msg(": R0=0xffffffff80000000") check_assert(s64, ==, eq_int_min, INT_MIN); -__msg(": R0_w=0x7fffffff") +__msg(": R0=0x7fffffff") check_assert(s64, ==, eq_int_max, INT_MAX); -__msg(": R0_w=0") +__msg(": R0=0") check_assert(s64, ==, eq_zero, 0); -__msg(": R0_w=0x8000000000000000 R1_w=0x8000000000000000") +__msg(": R0=0x8000000000000000 R1=0x8000000000000000") check_assert(s64, ==, eq_llong_min, LLONG_MIN); -__msg(": R0_w=0x7fffffffffffffff R1_w=0x7fffffffffffffff") +__msg(": R0=0x7fffffffffffffff R1=0x7fffffffffffffff") check_assert(s64, ==, eq_llong_max, LLONG_MAX); -__msg(": R0_w=scalar(id=1,smax=0x7ffffffe)") +__msg(": R0=scalar(id=1,smax=0x7ffffffe)") check_assert(s64, <, lt_pos, INT_MAX); -__msg(": R0_w=scalar(id=1,smax=-1,umin=0x8000000000000000,var_off=(0x8000000000000000; 0x7fffffffffffffff))") +__msg(": R0=scalar(id=1,smax=-1,umin=0x8000000000000000,var_off=(0x8000000000000000; 0x7fffffffffffffff))") check_assert(s64, <, lt_zero, 0); -__msg(": R0_w=scalar(id=1,smax=0xffffffff7fffffff") +__msg(": R0=scalar(id=1,smax=0xffffffff7fffffff") check_assert(s64, <, lt_neg, INT_MIN); -__msg(": R0_w=scalar(id=1,smax=0x7fffffff)") +__msg(": R0=scalar(id=1,smax=0x7fffffff)") check_assert(s64, <=, le_pos, INT_MAX); -__msg(": R0_w=scalar(id=1,smax=0)") +__msg(": R0=scalar(id=1,smax=0)") check_assert(s64, <=, le_zero, 0); -__msg(": R0_w=scalar(id=1,smax=0xffffffff80000000") +__msg(": R0=scalar(id=1,smax=0xffffffff80000000") check_assert(s64, <=, le_neg, INT_MIN); -__msg(": R0_w=scalar(id=1,smin=umin=0x80000000,umax=0x7fffffffffffffff,var_off=(0x0; 0x7fffffffffffffff))") +__msg(": R0=scalar(id=1,smin=umin=0x80000000,umax=0x7fffffffffffffff,var_off=(0x0; 0x7fffffffffffffff))") check_assert(s64, >, gt_pos, INT_MAX); -__msg(": R0_w=scalar(id=1,smin=umin=1,umax=0x7fffffffffffffff,var_off=(0x0; 0x7fffffffffffffff))") +__msg(": R0=scalar(id=1,smin=umin=1,umax=0x7fffffffffffffff,var_off=(0x0; 0x7fffffffffffffff))") check_assert(s64, >, gt_zero, 0); -__msg(": R0_w=scalar(id=1,smin=0xffffffff80000001") +__msg(": R0=scalar(id=1,smin=0xffffffff80000001") check_assert(s64, >, gt_neg, INT_MIN); -__msg(": R0_w=scalar(id=1,smin=umin=0x7fffffff,umax=0x7fffffffffffffff,var_off=(0x0; 0x7fffffffffffffff))") +__msg(": R0=scalar(id=1,smin=umin=0x7fffffff,umax=0x7fffffffffffffff,var_off=(0x0; 0x7fffffffffffffff))") check_assert(s64, >=, ge_pos, INT_MAX); -__msg(": R0_w=scalar(id=1,smin=0,umax=0x7fffffffffffffff,var_off=(0x0; 0x7fffffffffffffff))") +__msg(": R0=scalar(id=1,smin=0,umax=0x7fffffffffffffff,var_off=(0x0; 0x7fffffffffffffff))") check_assert(s64, >=, ge_zero, 0); -__msg(": R0_w=scalar(id=1,smin=0xffffffff80000000") +__msg(": R0=scalar(id=1,smin=0xffffffff80000000") check_assert(s64, >=, ge_neg, INT_MIN); SEC("?tc") diff --git a/tools/testing/selftests/bpf/progs/iters_state_safety.c b/tools/testing/selftests/bpf/progs/iters_state_safety.c index b381ac0c736c..d273b46dfc7c 100644 --- a/tools/testing/selftests/bpf/progs/iters_state_safety.c +++ b/tools/testing/selftests/bpf/progs/iters_state_safety.c @@ -30,7 +30,7 @@ int force_clang_to_emit_btf_for_externs(void *ctx) SEC("?raw_tp") __success __log_level(2) -__msg("fp-8_w=iter_num(ref_id=1,state=active,depth=0)") +__msg("fp-8=iter_num(ref_id=1,state=active,depth=0)") int create_and_destroy(void *ctx) { struct bpf_iter_num iter; @@ -196,7 +196,7 @@ int leak_iter_from_subprog_fail(void *ctx) SEC("?raw_tp") __success __log_level(2) -__msg("fp-8_w=iter_num(ref_id=1,state=active,depth=0)") +__msg("fp-8=iter_num(ref_id=1,state=active,depth=0)") int valid_stack_reuse(void *ctx) { struct bpf_iter_num iter; diff --git a/tools/testing/selftests/bpf/progs/iters_testmod_seq.c b/tools/testing/selftests/bpf/progs/iters_testmod_seq.c index 6543d5b6e0a9..83791348bed5 100644 --- a/tools/testing/selftests/bpf/progs/iters_testmod_seq.c +++ b/tools/testing/selftests/bpf/progs/iters_testmod_seq.c @@ -20,7 +20,7 @@ __s64 res_empty; SEC("raw_tp/sys_enter") __success __log_level(2) -__msg("fp-16_w=iter_testmod_seq(ref_id=1,state=active,depth=0)") +__msg("fp-16=iter_testmod_seq(ref_id=1,state=active,depth=0)") __msg("fp-16=iter_testmod_seq(ref_id=1,state=drained,depth=0)") __msg("call bpf_iter_testmod_seq_destroy") int testmod_seq_empty(const void *ctx) @@ -38,7 +38,7 @@ __s64 res_full; SEC("raw_tp/sys_enter") __success __log_level(2) -__msg("fp-16_w=iter_testmod_seq(ref_id=1,state=active,depth=0)") +__msg("fp-16=iter_testmod_seq(ref_id=1,state=active,depth=0)") __msg("fp-16=iter_testmod_seq(ref_id=1,state=drained,depth=0)") __msg("call bpf_iter_testmod_seq_destroy") int testmod_seq_full(const void *ctx) @@ -58,7 +58,7 @@ static volatile int zero = 0; SEC("raw_tp/sys_enter") __success __log_level(2) -__msg("fp-16_w=iter_testmod_seq(ref_id=1,state=active,depth=0)") +__msg("fp-16=iter_testmod_seq(ref_id=1,state=active,depth=0)") __msg("fp-16=iter_testmod_seq(ref_id=1,state=drained,depth=0)") __msg("call bpf_iter_testmod_seq_destroy") int testmod_seq_truncated(const void *ctx) diff --git a/tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c b/tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c index 4f94c971ae86..3b984b6ae7c0 100644 --- a/tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c +++ b/tools/testing/selftests/bpf/progs/mem_rdonly_untrusted.c @@ -8,8 +8,8 @@ SEC("tp_btf/sys_enter") __success __log_level(2) -__msg("r8 = *(u64 *)(r7 +0) ; R7_w=ptr_nameidata(off={{[0-9]+}}) R8_w=rdonly_untrusted_mem(sz=0)") -__msg("r9 = *(u8 *)(r8 +0) ; R8_w=rdonly_untrusted_mem(sz=0) R9_w=scalar") +__msg("r8 = *(u64 *)(r7 +0) ; R7=ptr_nameidata(off={{[0-9]+}}) R8=rdonly_untrusted_mem(sz=0)") +__msg("r9 = *(u8 *)(r8 +0) ; R8=rdonly_untrusted_mem(sz=0) R9=scalar") int btf_id_to_ptr_mem(void *ctx) { struct task_struct *task; diff --git a/tools/testing/selftests/bpf/progs/verifier_bounds.c b/tools/testing/selftests/bpf/progs/verifier_bounds.c index fbccc20555f4..0a72e0228ea9 100644 --- a/tools/testing/selftests/bpf/progs/verifier_bounds.c +++ b/tools/testing/selftests/bpf/progs/verifier_bounds.c @@ -926,7 +926,7 @@ l1_%=: r0 = 0; \ SEC("socket") __description("bounds check for non const xor src dst") __success __log_level(2) -__msg("5: (af) r0 ^= r6 ; R0_w=scalar(smin=smin32=0,smax=umax=smax32=umax32=431,var_off=(0x0; 0x1af))") +__msg("5: (af) r0 ^= r6 ; R0=scalar(smin=smin32=0,smax=umax=smax32=umax32=431,var_off=(0x0; 0x1af))") __naked void non_const_xor_src_dst(void) { asm volatile (" \ @@ -947,7 +947,7 @@ __naked void non_const_xor_src_dst(void) SEC("socket") __description("bounds check for non const or src dst") __success __log_level(2) -__msg("5: (4f) r0 |= r6 ; R0_w=scalar(smin=smin32=0,smax=umax=smax32=umax32=431,var_off=(0x0; 0x1af))") +__msg("5: (4f) r0 |= r6 ; R0=scalar(smin=smin32=0,smax=umax=smax32=umax32=431,var_off=(0x0; 0x1af))") __naked void non_const_or_src_dst(void) { asm volatile (" \ @@ -968,7 +968,7 @@ __naked void non_const_or_src_dst(void) SEC("socket") __description("bounds check for non const mul regs") __success __log_level(2) -__msg("5: (2f) r0 *= r6 ; R0_w=scalar(smin=smin32=0,smax=umax=smax32=umax32=3825,var_off=(0x0; 0xfff))") +__msg("5: (2f) r0 *= r6 ; R0=scalar(smin=smin32=0,smax=umax=smax32=umax32=3825,var_off=(0x0; 0xfff))") __naked void non_const_mul_regs(void) { asm volatile (" \ @@ -1241,7 +1241,7 @@ l0_%=: r0 = 0; \ SEC("tc") __description("multiply mixed sign bounds. test 1") __success __log_level(2) -__msg("r6 *= r7 {{.*}}; R6_w=scalar(smin=umin=0x1bc16d5cd4927ee1,smax=umax=0x1bc16d674ec80000,smax32=0x7ffffeff,umax32=0xfffffeff,var_off=(0x1bc16d4000000000; 0x3ffffffeff))") +__msg("r6 *= r7 {{.*}}; R6=scalar(smin=umin=0x1bc16d5cd4927ee1,smax=umax=0x1bc16d674ec80000,smax32=0x7ffffeff,umax32=0xfffffeff,var_off=(0x1bc16d4000000000; 0x3ffffffeff))") __naked void mult_mixed0_sign(void) { asm volatile ( @@ -1264,7 +1264,7 @@ __naked void mult_mixed0_sign(void) SEC("tc") __description("multiply mixed sign bounds. test 2") __success __log_level(2) -__msg("r6 *= r7 {{.*}}; R6_w=scalar(smin=smin32=-100,smax=smax32=200)") +__msg("r6 *= r7 {{.*}}; R6=scalar(smin=smin32=-100,smax=smax32=200)") __naked void mult_mixed1_sign(void) { asm volatile ( @@ -1287,7 +1287,7 @@ __naked void mult_mixed1_sign(void) SEC("tc") __description("multiply negative bounds") __success __log_level(2) -__msg("r6 *= r7 {{.*}}; R6_w=scalar(smin=umin=smin32=umin32=0x3ff280b0,smax=umax=smax32=umax32=0x3fff0001,var_off=(0x3ff00000; 0xf81ff))") +__msg("r6 *= r7 {{.*}}; R6=scalar(smin=umin=smin32=umin32=0x3ff280b0,smax=umax=smax32=umax32=0x3fff0001,var_off=(0x3ff00000; 0xf81ff))") __naked void mult_sign_bounds(void) { asm volatile ( @@ -1311,7 +1311,7 @@ __naked void mult_sign_bounds(void) SEC("tc") __description("multiply bounds that don't cross signed boundary") __success __log_level(2) -__msg("r8 *= r6 {{.*}}; R6_w=scalar(smin=smin32=0,smax=umax=smax32=umax32=11,var_off=(0x0; 0xb)) R8_w=scalar(smin=0,smax=umax=0x7b96bb0a94a3a7cd,var_off=(0x0; 0x7fffffffffffffff))") +__msg("r8 *= r6 {{.*}}; R6=scalar(smin=smin32=0,smax=umax=smax32=umax32=11,var_off=(0x0; 0xb)) R8=scalar(smin=0,smax=umax=0x7b96bb0a94a3a7cd,var_off=(0x0; 0x7fffffffffffffff))") __naked void mult_no_sign_crossing(void) { asm volatile ( @@ -1331,7 +1331,7 @@ __naked void mult_no_sign_crossing(void) SEC("tc") __description("multiplication overflow, result in unbounded reg. test 1") __success __log_level(2) -__msg("r6 *= r7 {{.*}}; R6_w=scalar()") +__msg("r6 *= r7 {{.*}}; R6=scalar()") __naked void mult_unsign_ovf(void) { asm volatile ( @@ -1353,7 +1353,7 @@ __naked void mult_unsign_ovf(void) SEC("tc") __description("multiplication overflow, result in unbounded reg. test 2") __success __log_level(2) -__msg("r6 *= r7 {{.*}}; R6_w=scalar()") +__msg("r6 *= r7 {{.*}}; R6=scalar()") __naked void mult_sign_ovf(void) { asm volatile ( @@ -1376,7 +1376,7 @@ __naked void mult_sign_ovf(void) SEC("socket") __description("64-bit addition, all outcomes overflow") __success __log_level(2) -__msg("5: (0f) r3 += r3 {{.*}} R3_w=scalar(umin=0x4000000000000000,umax=0xfffffffffffffffe)") +__msg("5: (0f) r3 += r3 {{.*}} R3=scalar(umin=0x4000000000000000,umax=0xfffffffffffffffe)") __retval(0) __naked void add64_full_overflow(void) { @@ -1396,7 +1396,7 @@ __naked void add64_full_overflow(void) SEC("socket") __description("64-bit addition, partial overflow, result in unbounded reg") __success __log_level(2) -__msg("4: (0f) r3 += r3 {{.*}} R3_w=scalar()") +__msg("4: (0f) r3 += r3 {{.*}} R3=scalar()") __retval(0) __naked void add64_partial_overflow(void) { @@ -1416,7 +1416,7 @@ __naked void add64_partial_overflow(void) SEC("socket") __description("32-bit addition overflow, all outcomes overflow") __success __log_level(2) -__msg("4: (0c) w3 += w3 {{.*}} R3_w=scalar(smin=umin=umin32=0x40000000,smax=umax=umax32=0xfffffffe,var_off=(0x0; 0xffffffff))") +__msg("4: (0c) w3 += w3 {{.*}} R3=scalar(smin=umin=umin32=0x40000000,smax=umax=umax32=0xfffffffe,var_off=(0x0; 0xffffffff))") __retval(0) __naked void add32_full_overflow(void) { @@ -1436,7 +1436,7 @@ __naked void add32_full_overflow(void) SEC("socket") __description("32-bit addition, partial overflow, result in unbounded u32 bounds") __success __log_level(2) -__msg("4: (0c) w3 += w3 {{.*}} R3_w=scalar(smin=0,smax=umax=0xffffffff,var_off=(0x0; 0xffffffff))") +__msg("4: (0c) w3 += w3 {{.*}} R3=scalar(smin=0,smax=umax=0xffffffff,var_off=(0x0; 0xffffffff))") __retval(0) __naked void add32_partial_overflow(void) { @@ -1456,7 +1456,7 @@ __naked void add32_partial_overflow(void) SEC("socket") __description("64-bit subtraction, all outcomes underflow") __success __log_level(2) -__msg("6: (1f) r3 -= r1 {{.*}} R3_w=scalar(umin=1,umax=0x8000000000000000)") +__msg("6: (1f) r3 -= r1 {{.*}} R3=scalar(umin=1,umax=0x8000000000000000)") __retval(0) __naked void sub64_full_overflow(void) { @@ -1477,7 +1477,7 @@ __naked void sub64_full_overflow(void) SEC("socket") __description("64-bit subtraction, partial overflow, result in unbounded reg") __success __log_level(2) -__msg("3: (1f) r3 -= r2 {{.*}} R3_w=scalar()") +__msg("3: (1f) r3 -= r2 {{.*}} R3=scalar()") __retval(0) __naked void sub64_partial_overflow(void) { @@ -1496,7 +1496,7 @@ __naked void sub64_partial_overflow(void) SEC("socket") __description("32-bit subtraction overflow, all outcomes underflow") __success __log_level(2) -__msg("5: (1c) w3 -= w1 {{.*}} R3_w=scalar(smin=umin=umin32=1,smax=umax=umax32=0x80000000,var_off=(0x0; 0xffffffff))") +__msg("5: (1c) w3 -= w1 {{.*}} R3=scalar(smin=umin=umin32=1,smax=umax=umax32=0x80000000,var_off=(0x0; 0xffffffff))") __retval(0) __naked void sub32_full_overflow(void) { @@ -1517,7 +1517,7 @@ __naked void sub32_full_overflow(void) SEC("socket") __description("32-bit subtraction, partial overflow, result in unbounded u32 bounds") __success __log_level(2) -__msg("3: (1c) w3 -= w2 {{.*}} R3_w=scalar(smin=0,smax=umax=0xffffffff,var_off=(0x0; 0xffffffff))") +__msg("3: (1c) w3 -= w2 {{.*}} R3=scalar(smin=0,smax=umax=0xffffffff,var_off=(0x0; 0xffffffff))") __retval(0) __naked void sub32_partial_overflow(void) { @@ -1617,7 +1617,7 @@ l0_%=: r0 = 0; \ SEC("socket") __description("bounds deduction cross sign boundary, positive overlap") __success __log_level(2) __flag(BPF_F_TEST_REG_INVARIANTS) -__msg("3: (2d) if r0 > r1 {{.*}} R0_w=scalar(smin=smin32=0,smax=umax=smax32=umax32=127,var_off=(0x0; 0x7f))") +__msg("3: (2d) if r0 > r1 {{.*}} R0=scalar(smin=smin32=0,smax=umax=smax32=umax32=127,var_off=(0x0; 0x7f))") __retval(0) __naked void bounds_deduct_positive_overlap(void) { @@ -1650,7 +1650,7 @@ l0_%=: r0 = 0; \ SEC("socket") __description("bounds deduction cross sign boundary, two overlaps") __failure __flag(BPF_F_TEST_REG_INVARIANTS) -__msg("3: (2d) if r0 > r1 {{.*}} R0_w=scalar(smin=smin32=-128,smax=smax32=127,umax=0xffffffffffffff80)") +__msg("3: (2d) if r0 > r1 {{.*}} R0=scalar(smin=smin32=-128,smax=smax32=127,umax=0xffffffffffffff80)") __msg("frame pointer is read only") __naked void bounds_deduct_two_overlaps(void) { diff --git a/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c b/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c index 181da86ba5f0..6630a92b1b47 100644 --- a/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c +++ b/tools/testing/selftests/bpf/progs/verifier_global_ptr_args.c @@ -215,7 +215,7 @@ __weak int subprog_untrusted(const volatile struct task_struct *restrict task __ SEC("tp_btf/sys_enter") __success __log_level(2) -__msg("r1 = {{.*}}; {{.*}}R1_w=trusted_ptr_task_struct()") +__msg("r1 = {{.*}}; {{.*}}R1=trusted_ptr_task_struct()") __msg("Func#1 ('subprog_untrusted') is global and assumed valid.") __msg("Validating subprog_untrusted() func#1...") __msg(": R1=untrusted_ptr_task_struct") @@ -278,7 +278,7 @@ __weak int subprog_enum_untrusted(enum bpf_attach_type *p __arg_untrusted) SEC("tp_btf/sys_enter") __success __log_level(2) -__msg("r1 = {{.*}}; {{.*}}R1_w=trusted_ptr_task_struct()") +__msg("r1 = {{.*}}; {{.*}}R1=trusted_ptr_task_struct()") __msg("Func#1 ('subprog_void_untrusted') is global and assumed valid.") __msg("Validating subprog_void_untrusted() func#1...") __msg(": R1=rdonly_untrusted_mem(sz=0)") diff --git a/tools/testing/selftests/bpf/progs/verifier_ldsx.c b/tools/testing/selftests/bpf/progs/verifier_ldsx.c index 52edee41caf6..f087ffb79f20 100644 --- a/tools/testing/selftests/bpf/progs/verifier_ldsx.c +++ b/tools/testing/selftests/bpf/progs/verifier_ldsx.c @@ -65,7 +65,7 @@ __naked void ldsx_s32(void) SEC("socket") __description("LDSX, S8 range checking, privileged") __log_level(2) __success __retval(1) -__msg("R1_w=scalar(smin=smin32=-128,smax=smax32=127)") +__msg("R1=scalar(smin=smin32=-128,smax=smax32=127)") __naked void ldsx_s8_range_priv(void) { asm volatile ( diff --git a/tools/testing/selftests/bpf/progs/verifier_precision.c b/tools/testing/selftests/bpf/progs/verifier_precision.c index 73fee2aec698..1fe090cd6744 100644 --- a/tools/testing/selftests/bpf/progs/verifier_precision.c +++ b/tools/testing/selftests/bpf/progs/verifier_precision.c @@ -144,21 +144,21 @@ SEC("?raw_tp") __success __log_level(2) /* * Without the bug fix there will be no history between "last_idx 3 first_idx 3" - * and "parent state regs=" lines. "R0_w=6" parts are here to help anchor + * and "parent state regs=" lines. "R0=6" parts are here to help anchor * expected log messages to the one specific mark_chain_precision operation. * * This is quite fragile: if verifier checkpointing heuristic changes, this * might need adjusting. */ -__msg("2: (07) r0 += 1 ; R0_w=6") +__msg("2: (07) r0 += 1 ; R0=6") __msg("3: (35) if r0 >= 0xa goto pc+1") __msg("mark_precise: frame0: last_idx 3 first_idx 3 subseq_idx -1") __msg("mark_precise: frame0: regs=r0 stack= before 2: (07) r0 += 1") __msg("mark_precise: frame0: regs=r0 stack= before 1: (07) r0 += 1") __msg("mark_precise: frame0: regs=r0 stack= before 4: (05) goto pc-4") __msg("mark_precise: frame0: regs=r0 stack= before 3: (35) if r0 >= 0xa goto pc+1") -__msg("mark_precise: frame0: parent state regs= stack=: R0_rw=P4") -__msg("3: R0_w=6") +__msg("mark_precise: frame0: parent state regs= stack=: R0=P4") +__msg("3: R0=6") __naked int state_loop_first_last_equal(void) { asm volatile ( @@ -233,8 +233,8 @@ __naked void bpf_cond_op_not_r10(void) SEC("lsm.s/socket_connect") __success __log_level(2) -__msg("0: (b7) r0 = 1 ; R0_w=1") -__msg("1: (84) w0 = -w0 ; R0_w=0xffffffff") +__msg("0: (b7) r0 = 1 ; R0=1") +__msg("1: (84) w0 = -w0 ; R0=0xffffffff") __msg("mark_precise: frame0: last_idx 2 first_idx 0 subseq_idx -1") __msg("mark_precise: frame0: regs=r0 stack= before 1: (84) w0 = -w0") __msg("mark_precise: frame0: regs=r0 stack= before 0: (b7) r0 = 1") @@ -268,8 +268,8 @@ __naked int bpf_neg_3(void) SEC("lsm.s/socket_connect") __success __log_level(2) -__msg("0: (b7) r0 = 1 ; R0_w=1") -__msg("1: (87) r0 = -r0 ; R0_w=-1") +__msg("0: (b7) r0 = 1 ; R0=1") +__msg("1: (87) r0 = -r0 ; R0=-1") __msg("mark_precise: frame0: last_idx 2 first_idx 0 subseq_idx -1") __msg("mark_precise: frame0: regs=r0 stack= before 1: (87) r0 = -r0") __msg("mark_precise: frame0: regs=r0 stack= before 0: (b7) r0 = 1") diff --git a/tools/testing/selftests/bpf/progs/verifier_scalar_ids.c b/tools/testing/selftests/bpf/progs/verifier_scalar_ids.c index dba3ca728f6e..c0ce690ddb68 100644 --- a/tools/testing/selftests/bpf/progs/verifier_scalar_ids.c +++ b/tools/testing/selftests/bpf/progs/verifier_scalar_ids.c @@ -353,7 +353,7 @@ __flag(BPF_F_TEST_STATE_FREQ) * collect_linked_regs() can't tie more than 6 registers for a single insn. */ __msg("8: (25) if r0 > 0x7 goto pc+0 ; R0=scalar(id=1") -__msg("9: (bf) r6 = r6 ; R6_w=scalar(id=2") +__msg("9: (bf) r6 = r6 ; R6=scalar(id=2") /* check that r{0-5} are marked precise after 'if' */ __msg("frame0: regs=r0 stack= before 8: (25) if r0 > 0x7 goto pc+0") __msg("frame0: parent state regs=r0,r1,r2,r3,r4,r5 stack=:") @@ -779,12 +779,12 @@ __success __retval(0) /* Check that verifier believes r1/r0 are zero at exit */ __log_level(2) -__msg("4: (77) r1 >>= 32 ; R1_w=0") -__msg("5: (bf) r0 = r1 ; R0_w=0 R1_w=0") +__msg("4: (77) r1 >>= 32 ; R1=0") +__msg("5: (bf) r0 = r1 ; R0=0 R1=0") __msg("6: (95) exit") __msg("from 3 to 4") -__msg("4: (77) r1 >>= 32 ; R1_w=0") -__msg("5: (bf) r0 = r1 ; R0_w=0 R1_w=0") +__msg("4: (77) r1 >>= 32 ; R1=0") +__msg("5: (bf) r0 = r1 ; R0=0 R1=0") __msg("6: (95) exit") /* Verify that statements to randomize upper half of r1 had not been * generated. diff --git a/tools/testing/selftests/bpf/progs/verifier_spill_fill.c b/tools/testing/selftests/bpf/progs/verifier_spill_fill.c index 1e5a511e8494..7a13dbd794b2 100644 --- a/tools/testing/selftests/bpf/progs/verifier_spill_fill.c +++ b/tools/testing/selftests/bpf/progs/verifier_spill_fill.c @@ -506,17 +506,17 @@ SEC("raw_tp") __log_level(2) __success /* fp-8 is spilled IMPRECISE value zero (represented by a zero value fake reg) */ -__msg("2: (7a) *(u64 *)(r10 -8) = 0 ; R10=fp0 fp-8_w=0") +__msg("2: (7a) *(u64 *)(r10 -8) = 0 ; R10=fp0 fp-8=0") /* but fp-16 is spilled IMPRECISE zero const reg */ -__msg("4: (7b) *(u64 *)(r10 -16) = r0 ; R0_w=0 R10=fp0 fp-16_w=0") +__msg("4: (7b) *(u64 *)(r10 -16) = r0 ; R0=0 R10=fp0 fp-16=0") /* validate that assigning R2 from STACK_SPILL with zero value doesn't mark register * precise immediately; if necessary, it will be marked precise later */ -__msg("6: (71) r2 = *(u8 *)(r10 -1) ; R2_w=0 R10=fp0 fp-8_w=0") +__msg("6: (71) r2 = *(u8 *)(r10 -1) ; R2=0 R10=fp0 fp-8=0") /* similarly, when R2 is assigned from spilled register, it is initially * imprecise, but will be marked precise later once it is used in precise context */ -__msg("10: (71) r2 = *(u8 *)(r10 -9) ; R2_w=0 R10=fp0 fp-16_w=0") +__msg("10: (71) r2 = *(u8 *)(r10 -9) ; R2=0 R10=fp0 fp-16=0") __msg("11: (0f) r1 += r2") __msg("mark_precise: frame0: last_idx 11 first_idx 0 subseq_idx -1") __msg("mark_precise: frame0: regs=r2 stack= before 10: (71) r2 = *(u8 *)(r10 -9)") @@ -598,7 +598,7 @@ __log_level(2) __success /* fp-4 is STACK_ZERO */ __msg("2: (62) *(u32 *)(r10 -4) = 0 ; R10=fp0 fp-8=0000????") -__msg("4: (71) r2 = *(u8 *)(r10 -1) ; R2_w=0 R10=fp0 fp-8=0000????") +__msg("4: (71) r2 = *(u8 *)(r10 -1) ; R2=0 R10=fp0 fp-8=0000????") __msg("5: (0f) r1 += r2") __msg("mark_precise: frame0: last_idx 5 first_idx 0 subseq_idx -1") __msg("mark_precise: frame0: regs=r2 stack= before 4: (71) r2 = *(u8 *)(r10 -1)") @@ -640,25 +640,25 @@ SEC("raw_tp") __log_level(2) __flag(BPF_F_TEST_STATE_FREQ) __success /* make sure fp-8 is IMPRECISE fake register spill */ -__msg("3: (7a) *(u64 *)(r10 -8) = 1 ; R10=fp0 fp-8_w=1") +__msg("3: (7a) *(u64 *)(r10 -8) = 1 ; R10=fp0 fp-8=1") /* and fp-16 is spilled IMPRECISE const reg */ -__msg("5: (7b) *(u64 *)(r10 -16) = r0 ; R0_w=1 R10=fp0 fp-16_w=1") +__msg("5: (7b) *(u64 *)(r10 -16) = r0 ; R0=1 R10=fp0 fp-16=1") /* validate load from fp-8, which was initialized using BPF_ST_MEM */ -__msg("8: (79) r2 = *(u64 *)(r10 -8) ; R2_w=1 R10=fp0 fp-8=1") +__msg("8: (79) r2 = *(u64 *)(r10 -8) ; R2=1 R10=fp0 fp-8=1") __msg("9: (0f) r1 += r2") __msg("mark_precise: frame0: last_idx 9 first_idx 7 subseq_idx -1") __msg("mark_precise: frame0: regs=r2 stack= before 8: (79) r2 = *(u64 *)(r10 -8)") __msg("mark_precise: frame0: regs= stack=-8 before 7: (bf) r1 = r6") /* note, fp-8 is precise, fp-16 is not yet precise, we'll get there */ -__msg("mark_precise: frame0: parent state regs= stack=-8: R0_w=1 R1=ctx() R6_r=map_value(map=.data.two_byte_,ks=4,vs=2) R10=fp0 fp-8_rw=P1 fp-16_w=1") +__msg("mark_precise: frame0: parent state regs= stack=-8: R0=1 R1=ctx() R6=map_value(map=.data.two_byte_,ks=4,vs=2) R10=fp0 fp-8=P1 fp-16=1") __msg("mark_precise: frame0: last_idx 6 first_idx 3 subseq_idx 7") __msg("mark_precise: frame0: regs= stack=-8 before 6: (05) goto pc+0") __msg("mark_precise: frame0: regs= stack=-8 before 5: (7b) *(u64 *)(r10 -16) = r0") __msg("mark_precise: frame0: regs= stack=-8 before 4: (b7) r0 = 1") __msg("mark_precise: frame0: regs= stack=-8 before 3: (7a) *(u64 *)(r10 -8) = 1") -__msg("10: R1_w=map_value(map=.data.two_byte_,ks=4,vs=2,off=1) R2_w=1") +__msg("10: R1=map_value(map=.data.two_byte_,ks=4,vs=2,off=1) R2=1") /* validate load from fp-16, which was initialized using BPF_STX_MEM */ -__msg("12: (79) r2 = *(u64 *)(r10 -16) ; R2_w=1 R10=fp0 fp-16=1") +__msg("12: (79) r2 = *(u64 *)(r10 -16) ; R2=1 R10=fp0 fp-16=1") __msg("13: (0f) r1 += r2") __msg("mark_precise: frame0: last_idx 13 first_idx 7 subseq_idx -1") __msg("mark_precise: frame0: regs=r2 stack= before 12: (79) r2 = *(u64 *)(r10 -16)") @@ -668,12 +668,12 @@ __msg("mark_precise: frame0: regs= stack=-16 before 9: (0f) r1 += r2") __msg("mark_precise: frame0: regs= stack=-16 before 8: (79) r2 = *(u64 *)(r10 -8)") __msg("mark_precise: frame0: regs= stack=-16 before 7: (bf) r1 = r6") /* now both fp-8 and fp-16 are precise, very good */ -__msg("mark_precise: frame0: parent state regs= stack=-16: R0_w=1 R1=ctx() R6_r=map_value(map=.data.two_byte_,ks=4,vs=2) R10=fp0 fp-8_rw=P1 fp-16_rw=P1") +__msg("mark_precise: frame0: parent state regs= stack=-16: R0=1 R1=ctx() R6=map_value(map=.data.two_byte_,ks=4,vs=2) R10=fp0 fp-8=P1 fp-16=P1") __msg("mark_precise: frame0: last_idx 6 first_idx 3 subseq_idx 7") __msg("mark_precise: frame0: regs= stack=-16 before 6: (05) goto pc+0") __msg("mark_precise: frame0: regs= stack=-16 before 5: (7b) *(u64 *)(r10 -16) = r0") __msg("mark_precise: frame0: regs=r0 stack= before 4: (b7) r0 = 1") -__msg("14: R1_w=map_value(map=.data.two_byte_,ks=4,vs=2,off=1) R2_w=1") +__msg("14: R1=map_value(map=.data.two_byte_,ks=4,vs=2,off=1) R2=1") __naked void stack_load_preserves_const_precision(void) { asm volatile ( @@ -719,22 +719,22 @@ __success /* make sure fp-8 is 32-bit FAKE subregister spill */ __msg("3: (62) *(u32 *)(r10 -8) = 1 ; R10=fp0 fp-8=????1") /* but fp-16 is spilled IMPRECISE zero const reg */ -__msg("5: (63) *(u32 *)(r10 -16) = r0 ; R0_w=1 R10=fp0 fp-16=????1") +__msg("5: (63) *(u32 *)(r10 -16) = r0 ; R0=1 R10=fp0 fp-16=????1") /* validate load from fp-8, which was initialized using BPF_ST_MEM */ -__msg("8: (61) r2 = *(u32 *)(r10 -8) ; R2_w=1 R10=fp0 fp-8=????1") +__msg("8: (61) r2 = *(u32 *)(r10 -8) ; R2=1 R10=fp0 fp-8=????1") __msg("9: (0f) r1 += r2") __msg("mark_precise: frame0: last_idx 9 first_idx 7 subseq_idx -1") __msg("mark_precise: frame0: regs=r2 stack= before 8: (61) r2 = *(u32 *)(r10 -8)") __msg("mark_precise: frame0: regs= stack=-8 before 7: (bf) r1 = r6") -__msg("mark_precise: frame0: parent state regs= stack=-8: R0_w=1 R1=ctx() R6_r=map_value(map=.data.two_byte_,ks=4,vs=2) R10=fp0 fp-8_r=????P1 fp-16=????1") +__msg("mark_precise: frame0: parent state regs= stack=-8: R0=1 R1=ctx() R6=map_value(map=.data.two_byte_,ks=4,vs=2) R10=fp0 fp-8=????P1 fp-16=????1") __msg("mark_precise: frame0: last_idx 6 first_idx 3 subseq_idx 7") __msg("mark_precise: frame0: regs= stack=-8 before 6: (05) goto pc+0") __msg("mark_precise: frame0: regs= stack=-8 before 5: (63) *(u32 *)(r10 -16) = r0") __msg("mark_precise: frame0: regs= stack=-8 before 4: (b7) r0 = 1") __msg("mark_precise: frame0: regs= stack=-8 before 3: (62) *(u32 *)(r10 -8) = 1") -__msg("10: R1_w=map_value(map=.data.two_byte_,ks=4,vs=2,off=1) R2_w=1") +__msg("10: R1=map_value(map=.data.two_byte_,ks=4,vs=2,off=1) R2=1") /* validate load from fp-16, which was initialized using BPF_STX_MEM */ -__msg("12: (61) r2 = *(u32 *)(r10 -16) ; R2_w=1 R10=fp0 fp-16=????1") +__msg("12: (61) r2 = *(u32 *)(r10 -16) ; R2=1 R10=fp0 fp-16=????1") __msg("13: (0f) r1 += r2") __msg("mark_precise: frame0: last_idx 13 first_idx 7 subseq_idx -1") __msg("mark_precise: frame0: regs=r2 stack= before 12: (61) r2 = *(u32 *)(r10 -16)") @@ -743,12 +743,12 @@ __msg("mark_precise: frame0: regs= stack=-16 before 10: (73) *(u8 *)(r1 +0) = r2 __msg("mark_precise: frame0: regs= stack=-16 before 9: (0f) r1 += r2") __msg("mark_precise: frame0: regs= stack=-16 before 8: (61) r2 = *(u32 *)(r10 -8)") __msg("mark_precise: frame0: regs= stack=-16 before 7: (bf) r1 = r6") -__msg("mark_precise: frame0: parent state regs= stack=-16: R0_w=1 R1=ctx() R6_r=map_value(map=.data.two_byte_,ks=4,vs=2) R10=fp0 fp-8_r=????P1 fp-16_r=????P1") +__msg("mark_precise: frame0: parent state regs= stack=-16: R0=1 R1=ctx() R6=map_value(map=.data.two_byte_,ks=4,vs=2) R10=fp0 fp-8=????P1 fp-16=????P1") __msg("mark_precise: frame0: last_idx 6 first_idx 3 subseq_idx 7") __msg("mark_precise: frame0: regs= stack=-16 before 6: (05) goto pc+0") __msg("mark_precise: frame0: regs= stack=-16 before 5: (63) *(u32 *)(r10 -16) = r0") __msg("mark_precise: frame0: regs=r0 stack= before 4: (b7) r0 = 1") -__msg("14: R1_w=map_value(map=.data.two_byte_,ks=4,vs=2,off=1) R2_w=1") +__msg("14: R1=map_value(map=.data.two_byte_,ks=4,vs=2,off=1) R2=1") __naked void stack_load_preserves_const_precision_subreg(void) { asm volatile ( diff --git a/tools/testing/selftests/bpf/progs/verifier_subprog_precision.c b/tools/testing/selftests/bpf/progs/verifier_subprog_precision.c index 9d415f7ce599..ac3e418c2a96 100644 --- a/tools/testing/selftests/bpf/progs/verifier_subprog_precision.c +++ b/tools/testing/selftests/bpf/progs/verifier_subprog_precision.c @@ -105,7 +105,7 @@ __msg("mark_precise: frame0: regs=r0 stack= before 4: (27) r0 *= 4") __msg("mark_precise: frame0: regs=r0 stack= before 3: (57) r0 &= 3") __msg("mark_precise: frame0: regs=r0 stack= before 10: (95) exit") __msg("mark_precise: frame1: regs=r0 stack= before 9: (bf) r0 = (s8)r10") -__msg("7: R0_w=scalar") +__msg("7: R0=scalar") __naked int fp_precise_subprog_result(void) { asm volatile ( @@ -141,7 +141,7 @@ __msg("mark_precise: frame1: regs=r0 stack= before 10: (bf) r0 = (s8)r1") * anyways, at which point we'll break precision chain */ __msg("mark_precise: frame1: regs=r1 stack= before 9: (bf) r1 = r10") -__msg("7: R0_w=scalar") +__msg("7: R0=scalar") __naked int sneaky_fp_precise_subprog_result(void) { asm volatile ( @@ -681,7 +681,7 @@ __msg("mark_precise: frame0: last_idx 10 first_idx 7 subseq_idx -1") __msg("mark_precise: frame0: regs=r7 stack= before 9: (bf) r1 = r8") __msg("mark_precise: frame0: regs=r7 stack= before 8: (27) r7 *= 4") __msg("mark_precise: frame0: regs=r7 stack= before 7: (79) r7 = *(u64 *)(r10 -8)") -__msg("mark_precise: frame0: parent state regs= stack=-8: R0_w=2 R6_w=1 R8_rw=map_value(map=.data.vals,ks=4,vs=16) R10=fp0 fp-8_rw=P1") +__msg("mark_precise: frame0: parent state regs= stack=-8: R0=2 R6=1 R8=map_value(map=.data.vals,ks=4,vs=16) R10=fp0 fp-8=P1") __msg("mark_precise: frame0: last_idx 18 first_idx 0 subseq_idx 7") __msg("mark_precise: frame0: regs= stack=-8 before 18: (95) exit") __msg("mark_precise: frame1: regs= stack= before 17: (0f) r0 += r2") diff --git a/tools/testing/selftests/bpf/verifier/bpf_st_mem.c b/tools/testing/selftests/bpf/verifier/bpf_st_mem.c index b616575c3b00..ce13002c7a19 100644 --- a/tools/testing/selftests/bpf/verifier/bpf_st_mem.c +++ b/tools/testing/selftests/bpf/verifier/bpf_st_mem.c @@ -93,7 +93,7 @@ .expected_attach_type = BPF_SK_LOOKUP, .result = VERBOSE_ACCEPT, .runs = -1, - .errstr = "0: (7a) *(u64 *)(r10 -8) = -44 ; R10=fp0 fp-8_w=-44\ + .errstr = "0: (7a) *(u64 *)(r10 -8) = -44 ; R10=fp0 fp-8=-44\ 2: (c5) if r0 s< 0x0 goto pc+2\ - R0_w=-44", + R0=-44", }, -- 2.47.3 ^ permalink raw reply related [flat|nested] 22+ messages in thread
* Re: [PATCH bpf-next v1 09/10] bpf: disable and remove registers chain based liveness 2025-09-11 1:04 ` [PATCH bpf-next v1 09/10] bpf: disable and remove registers chain based liveness Eduard Zingerman @ 2025-09-11 14:19 ` kernel test robot 2025-09-11 21:26 ` Eduard Zingerman 2025-09-12 8:17 ` Dan Carpenter 1 sibling, 1 reply; 22+ messages in thread From: kernel test robot @ 2025-09-11 14:19 UTC (permalink / raw) To: Eduard Zingerman, bpf, ast, andrii Cc: llvm, oe-kbuild-all, daniel, martin.lau, kernel-team, yonghong.song, eddyz87 Hi Eduard, kernel test robot noticed the following build warnings: [auto build test WARNING on bpf-next/master] url: https://github.com/intel-lab-lkp/linux/commits/Eduard-Zingerman/bpf-bpf_verifier_state-cleaned-flag-instead-of-REG_LIVE_DONE/20250911-090604 base: https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git master patch link: https://lore.kernel.org/r/20250911010437.2779173-10-eddyz87%40gmail.com patch subject: [PATCH bpf-next v1 09/10] bpf: disable and remove registers chain based liveness config: x86_64-buildonly-randconfig-003-20250911 (https://download.01.org/0day-ci/archive/20250911/202509112112.wkWw6wJW-lkp@intel.com/config) compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261) reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250911/202509112112.wkWw6wJW-lkp@intel.com/reproduce) If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot <lkp@intel.com> | Closes: https://lore.kernel.org/oe-kbuild-all/202509112112.wkWw6wJW-lkp@intel.com/ All warnings (new ones prefixed by >>): >> kernel/bpf/verifier.c:19305:11: warning: variable 'err' is uninitialized when used here [-Wuninitialized] 19305 | err = err ? : push_jmp_history(env, cur, 0, 0); | ^~~ kernel/bpf/verifier.c:19140:12: note: initialize the variable 'err' to silence this warning 19140 | int n, err, states_cnt = 0; | ^ | = 0 1 warning generated. vim +/err +19305 kernel/bpf/verifier.c 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19133 58e2af8b3a6b58 Jakub Kicinski 2016-09-21 19134 static int is_state_visited(struct bpf_verifier_env *env, int insn_idx) f1bca824dabba4 Alexei Starovoitov 2014-09-29 19135 { 58e2af8b3a6b58 Jakub Kicinski 2016-09-21 19136 struct bpf_verifier_state_list *new_sl; 5564ee3abb2ebe Eduard Zingerman 2025-02-15 19137 struct bpf_verifier_state_list *sl; c9e31900b54cad Eduard Zingerman 2025-06-11 19138 struct bpf_verifier_state *cur = env->cur_state, *new; c9e31900b54cad Eduard Zingerman 2025-06-11 19139 bool force_new_state, add_new_state, loop; d5c95ed86213e4 Eduard Zingerman 2025-09-10 19140 int n, err, states_cnt = 0; 5564ee3abb2ebe Eduard Zingerman 2025-02-15 19141 struct list_head *pos, *tmp, *head; aa30eb3260b2de Eduard Zingerman 2024-10-29 19142 aa30eb3260b2de Eduard Zingerman 2024-10-29 19143 force_new_state = env->test_state_freq || is_force_checkpoint(env, insn_idx) || aa30eb3260b2de Eduard Zingerman 2024-10-29 19144 /* Avoid accumulating infinitely long jmp history */ baaebe0928bf32 Eduard Zingerman 2025-06-11 19145 cur->jmp_history_cnt > 40; f1bca824dabba4 Alexei Starovoitov 2014-09-29 19146 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19147 /* bpf progs typically have pruning point every 4 instructions 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19148 * http://vger.kernel.org/bpfconf2019.html#session-1 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19149 * Do not add new state for future pruning if the verifier hasn't seen 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19150 * at least 2 jumps and at least 8 instructions. 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19151 * This heuristics helps decrease 'total_states' and 'peak_states' metric. 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19152 * In tests that amounts to up to 50% reduction into total verifier 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19153 * memory consumption and 20% verifier time speedup. 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19154 */ aa30eb3260b2de Eduard Zingerman 2024-10-29 19155 add_new_state = force_new_state; 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19156 if (env->jmps_processed - env->prev_jmps_processed >= 2 && 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19157 env->insn_processed - env->prev_insn_processed >= 8) 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19158 add_new_state = true; 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19159 9242b5f5615c82 Alexei Starovoitov 2018-12-13 19160 clean_live_states(env, insn_idx, cur); 9242b5f5615c82 Alexei Starovoitov 2018-12-13 19161 c9e31900b54cad Eduard Zingerman 2025-06-11 19162 loop = false; 5564ee3abb2ebe Eduard Zingerman 2025-02-15 19163 head = explored_state(env, insn_idx); 5564ee3abb2ebe Eduard Zingerman 2025-02-15 19164 list_for_each_safe(pos, tmp, head) { 5564ee3abb2ebe Eduard Zingerman 2025-02-15 19165 sl = container_of(pos, struct bpf_verifier_state_list, node); dc2a4ebc0b44a2 Alexei Starovoitov 2019-05-21 19166 states_cnt++; dc2a4ebc0b44a2 Alexei Starovoitov 2019-05-21 19167 if (sl->state.insn_idx != insn_idx) 5564ee3abb2ebe Eduard Zingerman 2025-02-15 19168 continue; bfc6bb74e4f16a Alexei Starovoitov 2021-07-14 19169 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19170 if (sl->state.branches) { bfc6bb74e4f16a Alexei Starovoitov 2021-07-14 19171 struct bpf_func_state *frame = sl->state.frame[sl->state.curframe]; bfc6bb74e4f16a Alexei Starovoitov 2021-07-14 19172 bfc6bb74e4f16a Alexei Starovoitov 2021-07-14 19173 if (frame->in_async_callback_fn && bfc6bb74e4f16a Alexei Starovoitov 2021-07-14 19174 frame->async_entry_cnt != cur->frame[cur->curframe]->async_entry_cnt) { bfc6bb74e4f16a Alexei Starovoitov 2021-07-14 19175 /* Different async_entry_cnt means that the verifier is bfc6bb74e4f16a Alexei Starovoitov 2021-07-14 19176 * processing another entry into async callback. bfc6bb74e4f16a Alexei Starovoitov 2021-07-14 19177 * Seeing the same state is not an indication of infinite bfc6bb74e4f16a Alexei Starovoitov 2021-07-14 19178 * loop or infinite recursion. bfc6bb74e4f16a Alexei Starovoitov 2021-07-14 19179 * But finding the same state doesn't mean that it's safe bfc6bb74e4f16a Alexei Starovoitov 2021-07-14 19180 * to stop processing the current state. The previous state bfc6bb74e4f16a Alexei Starovoitov 2021-07-14 19181 * hasn't yet reached bpf_exit, since state.branches > 0. bfc6bb74e4f16a Alexei Starovoitov 2021-07-14 19182 * Checking in_async_callback_fn alone is not enough either. bfc6bb74e4f16a Alexei Starovoitov 2021-07-14 19183 * Since the verifier still needs to catch infinite loops bfc6bb74e4f16a Alexei Starovoitov 2021-07-14 19184 * inside async callbacks. bfc6bb74e4f16a Alexei Starovoitov 2021-07-14 19185 */ 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19186 goto skip_inf_loop_check; 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19187 } 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19188 /* BPF open-coded iterators loop detection is special. 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19189 * states_maybe_looping() logic is too simplistic in detecting 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19190 * states that *might* be equivalent, because it doesn't know 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19191 * about ID remapping, so don't even perform it. 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19192 * See process_iter_next_call() and iter_active_depths_differ() 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19193 * for overview of the logic. When current and one of parent 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19194 * states are detected as equivalent, it's a good thing: we prove 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19195 * convergence and can stop simulating further iterations. 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19196 * It's safe to assume that iterator loop will finish, taking into 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19197 * account iter_next() contract of eventually returning 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19198 * sticky NULL result. 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19199 * 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19200 * Note, that states have to be compared exactly in this case because 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19201 * read and precision marks might not be finalized inside the loop. 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19202 * E.g. as in the program below: 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19203 * 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19204 * 1. r7 = -16 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19205 * 2. r6 = bpf_get_prandom_u32() 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19206 * 3. while (bpf_iter_num_next(&fp[-8])) { 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19207 * 4. if (r6 != 42) { 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19208 * 5. r7 = -32 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19209 * 6. r6 = bpf_get_prandom_u32() 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19210 * 7. continue 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19211 * 8. } 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19212 * 9. r0 = r10 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19213 * 10. r0 += r7 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19214 * 11. r8 = *(u64 *)(r0 + 0) 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19215 * 12. r6 = bpf_get_prandom_u32() 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19216 * 13. } 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19217 * 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19218 * Here verifier would first visit path 1-3, create a checkpoint at 3 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19219 * with r7=-16, continue to 4-7,3. Existing checkpoint at 3 does 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19220 * not have read or precision mark for r7 yet, thus inexact states 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19221 * comparison would discard current state with r7=-32 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19222 * => unsafe memory access at 11 would not be caught. 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19223 */ 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19224 if (is_iter_next_insn(env, insn_idx)) { 4f81c16f50baf6 Alexei Starovoitov 2024-03-05 19225 if (states_equal(env, &sl->state, cur, RANGE_WITHIN)) { 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19226 struct bpf_func_state *cur_frame; 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19227 struct bpf_reg_state *iter_state, *iter_reg; 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19228 int spi; 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19229 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19230 cur_frame = cur->frame[cur->curframe]; 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19231 /* btf_check_iter_kfuncs() enforces that 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19232 * iter state pointer is always the first arg 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19233 */ 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19234 iter_reg = &cur_frame->regs[BPF_REG_1]; 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19235 /* current state is valid due to states_equal(), 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19236 * so we can assume valid iter and reg state, 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19237 * no need for extra (re-)validations 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19238 */ 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19239 spi = __get_spi(iter_reg->off + iter_reg->var_off.value); 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19240 iter_state = &func(env, iter_reg)->stack[spi].spilled_ptr; 2a0992829ea386 Eduard Zingerman 2023-10-24 19241 if (iter_state->iter.state == BPF_ITER_STATE_ACTIVE) { c9e31900b54cad Eduard Zingerman 2025-06-11 19242 loop = true; 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19243 goto hit; 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19244 } 2a0992829ea386 Eduard Zingerman 2023-10-24 19245 } 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19246 goto skip_inf_loop_check; 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19247 } 011832b97b311b Alexei Starovoitov 2024-03-05 19248 if (is_may_goto_insn_at(env, insn_idx)) { 2b2efe1937ca9f Alexei Starovoitov 2024-06-19 19249 if (sl->state.may_goto_depth != cur->may_goto_depth && 2b2efe1937ca9f Alexei Starovoitov 2024-06-19 19250 states_equal(env, &sl->state, cur, RANGE_WITHIN)) { c9e31900b54cad Eduard Zingerman 2025-06-11 19251 loop = true; 011832b97b311b Alexei Starovoitov 2024-03-05 19252 goto hit; 011832b97b311b Alexei Starovoitov 2024-03-05 19253 } 011832b97b311b Alexei Starovoitov 2024-03-05 19254 } 588af0c506ec8e Eduard Zingerman 2025-09-10 19255 if (bpf_calls_callback(env, insn_idx)) { 4f81c16f50baf6 Alexei Starovoitov 2024-03-05 19256 if (states_equal(env, &sl->state, cur, RANGE_WITHIN)) ab5cfac139ab85 Eduard Zingerman 2023-11-21 19257 goto hit; ab5cfac139ab85 Eduard Zingerman 2023-11-21 19258 goto skip_inf_loop_check; ab5cfac139ab85 Eduard Zingerman 2023-11-21 19259 } 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19260 /* attempt to detect infinite loop to avoid unnecessary doomed work */ 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19261 if (states_maybe_looping(&sl->state, cur) && 4f81c16f50baf6 Alexei Starovoitov 2024-03-05 19262 states_equal(env, &sl->state, cur, EXACT) && ab5cfac139ab85 Eduard Zingerman 2023-11-21 19263 !iter_active_depths_differ(&sl->state, cur) && 011832b97b311b Alexei Starovoitov 2024-03-05 19264 sl->state.may_goto_depth == cur->may_goto_depth && ab5cfac139ab85 Eduard Zingerman 2023-11-21 19265 sl->state.callback_unroll_depth == cur->callback_unroll_depth) { 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19266 verbose_linfo(env, insn_idx, "; "); 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19267 verbose(env, "infinite loop detected at insn %d\n", insn_idx); b4d8239534fddc Eduard Zingerman 2023-10-24 19268 verbose(env, "cur state:"); 1995edc5f9089e Kumar Kartikeya Dwivedi 2024-12-03 19269 print_verifier_state(env, cur, cur->curframe, true); b4d8239534fddc Eduard Zingerman 2023-10-24 19270 verbose(env, "old state:"); 1995edc5f9089e Kumar Kartikeya Dwivedi 2024-12-03 19271 print_verifier_state(env, &sl->state, cur->curframe, true); 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19272 return -EINVAL; 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19273 } 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19274 /* if the verifier is processing a loop, avoid adding new state 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19275 * too often, since different loop iterations have distinct 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19276 * states and may not help future pruning. 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19277 * This threshold shouldn't be too low to make sure that 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19278 * a loop with large bound will be rejected quickly. 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19279 * The most abusive loop will be: 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19280 * r1 += 1 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19281 * if r1 < 1000000 goto pc-2 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19282 * 1M insn_procssed limit / 100 == 10k peak states. 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19283 * This threshold shouldn't be too high either, since states 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19284 * at the end of the loop are likely to be useful in pruning. 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19285 */ 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19286 skip_inf_loop_check: 4b5ce570dbef57 Andrii Nakryiko 2023-03-09 19287 if (!force_new_state && 98ddcf389d1bb7 Andrii Nakryiko 2023-03-02 19288 env->jmps_processed - env->prev_jmps_processed < 20 && 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19289 env->insn_processed - env->prev_insn_processed < 100) 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19290 add_new_state = false; 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19291 goto miss; 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19292 } c9e31900b54cad Eduard Zingerman 2025-06-11 19293 /* See comments for mark_all_regs_read_and_precise() */ c9e31900b54cad Eduard Zingerman 2025-06-11 19294 loop = incomplete_read_marks(env, &sl->state); c9e31900b54cad Eduard Zingerman 2025-06-11 19295 if (states_equal(env, &sl->state, cur, loop ? RANGE_WITHIN : NOT_EXACT)) { 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19296 hit: 9f4686c41bdff0 Alexei Starovoitov 2019-04-01 19297 sl->hit_cnt++; a3ce685dd01a78 Alexei Starovoitov 2019-06-28 19298 a3ce685dd01a78 Alexei Starovoitov 2019-06-28 19299 /* if previous state reached the exit with precision and a7de265cb2d849 Rafael Passos 2024-04-17 19300 * current state is equivalent to it (except precision marks) a3ce685dd01a78 Alexei Starovoitov 2019-06-28 19301 * the precision needs to be propagated back in a3ce685dd01a78 Alexei Starovoitov 2019-06-28 19302 * the current state. a3ce685dd01a78 Alexei Starovoitov 2019-06-28 19303 */ 41f6f64e6999a8 Andrii Nakryiko 2023-12-05 19304 if (is_jmp_point(env, env->insn_idx)) baaebe0928bf32 Eduard Zingerman 2025-06-11 @19305 err = err ? : push_jmp_history(env, cur, 0, 0); 23b37d616565c8 Eduard Zingerman 2025-06-11 19306 err = err ? : propagate_precision(env, &sl->state, cur, NULL); f4d7e40a5b7157 Alexei Starovoitov 2017-12-14 19307 if (err) f4d7e40a5b7157 Alexei Starovoitov 2017-12-14 19308 return err; c9e31900b54cad Eduard Zingerman 2025-06-11 19309 /* When processing iterator based loops above propagate_liveness and c9e31900b54cad Eduard Zingerman 2025-06-11 19310 * propagate_precision calls are not sufficient to transfer all relevant c9e31900b54cad Eduard Zingerman 2025-06-11 19311 * read and precision marks. E.g. consider the following case: c9e31900b54cad Eduard Zingerman 2025-06-11 19312 * c9e31900b54cad Eduard Zingerman 2025-06-11 19313 * .-> A --. Assume the states are visited in the order A, B, C. c9e31900b54cad Eduard Zingerman 2025-06-11 19314 * | | | Assume that state B reaches a state equivalent to state A. c9e31900b54cad Eduard Zingerman 2025-06-11 19315 * | v v At this point, state C is not processed yet, so state A c9e31900b54cad Eduard Zingerman 2025-06-11 19316 * '-- B C has not received any read or precision marks from C. c9e31900b54cad Eduard Zingerman 2025-06-11 19317 * Thus, marks propagated from A to B are incomplete. c9e31900b54cad Eduard Zingerman 2025-06-11 19318 * c9e31900b54cad Eduard Zingerman 2025-06-11 19319 * The verifier mitigates this by performing the following steps: c9e31900b54cad Eduard Zingerman 2025-06-11 19320 * c9e31900b54cad Eduard Zingerman 2025-06-11 19321 * - Prior to the main verification pass, strongly connected components c9e31900b54cad Eduard Zingerman 2025-06-11 19322 * (SCCs) are computed over the program's control flow graph, c9e31900b54cad Eduard Zingerman 2025-06-11 19323 * intraprocedurally. c9e31900b54cad Eduard Zingerman 2025-06-11 19324 * c9e31900b54cad Eduard Zingerman 2025-06-11 19325 * - During the main verification pass, `maybe_enter_scc()` checks c9e31900b54cad Eduard Zingerman 2025-06-11 19326 * whether the current verifier state is entering an SCC. If so, an c9e31900b54cad Eduard Zingerman 2025-06-11 19327 * instance of a `bpf_scc_visit` object is created, and the state c9e31900b54cad Eduard Zingerman 2025-06-11 19328 * entering the SCC is recorded as the entry state. c9e31900b54cad Eduard Zingerman 2025-06-11 19329 * c9e31900b54cad Eduard Zingerman 2025-06-11 19330 * - This instance is associated not with the SCC itself, but with a c9e31900b54cad Eduard Zingerman 2025-06-11 19331 * `bpf_scc_callchain`: a tuple consisting of the call sites leading to c9e31900b54cad Eduard Zingerman 2025-06-11 19332 * the SCC and the SCC id. See `compute_scc_callchain()`. c9e31900b54cad Eduard Zingerman 2025-06-11 19333 * c9e31900b54cad Eduard Zingerman 2025-06-11 19334 * - When a verification path encounters a `states_equal(..., c9e31900b54cad Eduard Zingerman 2025-06-11 19335 * RANGE_WITHIN)` condition, there exists a call chain describing the c9e31900b54cad Eduard Zingerman 2025-06-11 19336 * current state and a corresponding `bpf_scc_visit` instance. A copy c9e31900b54cad Eduard Zingerman 2025-06-11 19337 * of the current state is created and added to c9e31900b54cad Eduard Zingerman 2025-06-11 19338 * `bpf_scc_visit->backedges`. c9e31900b54cad Eduard Zingerman 2025-06-11 19339 * c9e31900b54cad Eduard Zingerman 2025-06-11 19340 * - When a verification path terminates, `maybe_exit_scc()` is called c9e31900b54cad Eduard Zingerman 2025-06-11 19341 * from `update_branch_counts()`. For states with `branches == 0`, it c9e31900b54cad Eduard Zingerman 2025-06-11 19342 * checks whether the state is the entry state of any `bpf_scc_visit` c9e31900b54cad Eduard Zingerman 2025-06-11 19343 * instance. If it is, this indicates that all paths originating from c9e31900b54cad Eduard Zingerman 2025-06-11 19344 * this SCC visit have been explored. `propagate_backedges()` is then c9e31900b54cad Eduard Zingerman 2025-06-11 19345 * called, which propagates read and precision marks through the c9e31900b54cad Eduard Zingerman 2025-06-11 19346 * backedges until a fixed point is reached. c9e31900b54cad Eduard Zingerman 2025-06-11 19347 * (In the earlier example, this would propagate marks from A to B, c9e31900b54cad Eduard Zingerman 2025-06-11 19348 * from C to A, and then again from A to B.) c9e31900b54cad Eduard Zingerman 2025-06-11 19349 * c9e31900b54cad Eduard Zingerman 2025-06-11 19350 * A note on callchains c9e31900b54cad Eduard Zingerman 2025-06-11 19351 * -------------------- c9e31900b54cad Eduard Zingerman 2025-06-11 19352 * c9e31900b54cad Eduard Zingerman 2025-06-11 19353 * Consider the following example: c9e31900b54cad Eduard Zingerman 2025-06-11 19354 * c9e31900b54cad Eduard Zingerman 2025-06-11 19355 * void foo() { loop { ... SCC#1 ... } } c9e31900b54cad Eduard Zingerman 2025-06-11 19356 * void main() { c9e31900b54cad Eduard Zingerman 2025-06-11 19357 * A: foo(); c9e31900b54cad Eduard Zingerman 2025-06-11 19358 * B: ... c9e31900b54cad Eduard Zingerman 2025-06-11 19359 * C: foo(); c9e31900b54cad Eduard Zingerman 2025-06-11 19360 * } c9e31900b54cad Eduard Zingerman 2025-06-11 19361 * c9e31900b54cad Eduard Zingerman 2025-06-11 19362 * Here, there are two distinct callchains leading to SCC#1: c9e31900b54cad Eduard Zingerman 2025-06-11 19363 * - (A, SCC#1) c9e31900b54cad Eduard Zingerman 2025-06-11 19364 * - (C, SCC#1) c9e31900b54cad Eduard Zingerman 2025-06-11 19365 * c9e31900b54cad Eduard Zingerman 2025-06-11 19366 * Each callchain identifies a separate `bpf_scc_visit` instance that c9e31900b54cad Eduard Zingerman 2025-06-11 19367 * accumulates backedge states. The `propagate_{liveness,precision}()` c9e31900b54cad Eduard Zingerman 2025-06-11 19368 * functions traverse the parent state of each backedge state, which c9e31900b54cad Eduard Zingerman 2025-06-11 19369 * means these parent states must remain valid (i.e., not freed) while c9e31900b54cad Eduard Zingerman 2025-06-11 19370 * the corresponding `bpf_scc_visit` instance exists. c9e31900b54cad Eduard Zingerman 2025-06-11 19371 * c9e31900b54cad Eduard Zingerman 2025-06-11 19372 * Associating `bpf_scc_visit` instances directly with SCCs instead of c9e31900b54cad Eduard Zingerman 2025-06-11 19373 * callchains would break this invariant: c9e31900b54cad Eduard Zingerman 2025-06-11 19374 * - States explored during `C: foo()` would contribute backedges to c9e31900b54cad Eduard Zingerman 2025-06-11 19375 * SCC#1, but SCC#1 would only be exited once the exploration of c9e31900b54cad Eduard Zingerman 2025-06-11 19376 * `A: foo()` completes. c9e31900b54cad Eduard Zingerman 2025-06-11 19377 * - By that time, the states explored between `A: foo()` and `C: foo()` c9e31900b54cad Eduard Zingerman 2025-06-11 19378 * (i.e., `B: ...`) may have already been freed, causing the parent c9e31900b54cad Eduard Zingerman 2025-06-11 19379 * links for states from `C: foo()` to become invalid. c9e31900b54cad Eduard Zingerman 2025-06-11 19380 */ c9e31900b54cad Eduard Zingerman 2025-06-11 19381 if (loop) { c9e31900b54cad Eduard Zingerman 2025-06-11 19382 struct bpf_scc_backedge *backedge; c9e31900b54cad Eduard Zingerman 2025-06-11 19383 43736ec3e02789 Eduard Zingerman 2025-06-13 19384 backedge = kzalloc(sizeof(*backedge), GFP_KERNEL_ACCOUNT); c9e31900b54cad Eduard Zingerman 2025-06-11 19385 if (!backedge) c9e31900b54cad Eduard Zingerman 2025-06-11 19386 return -ENOMEM; c9e31900b54cad Eduard Zingerman 2025-06-11 19387 err = copy_verifier_state(&backedge->state, cur); c9e31900b54cad Eduard Zingerman 2025-06-11 19388 backedge->state.equal_state = &sl->state; c9e31900b54cad Eduard Zingerman 2025-06-11 19389 backedge->state.insn_idx = insn_idx; c9e31900b54cad Eduard Zingerman 2025-06-11 19390 err = err ?: add_scc_backedge(env, &sl->state, backedge); c9e31900b54cad Eduard Zingerman 2025-06-11 19391 if (err) { c9e31900b54cad Eduard Zingerman 2025-06-11 19392 free_verifier_state(&backedge->state, false); bf0c2a84df9fb0 Qianfeng Rong 2025-08-11 19393 kfree(backedge); c9e31900b54cad Eduard Zingerman 2025-06-11 19394 return err; c9e31900b54cad Eduard Zingerman 2025-06-11 19395 } c9e31900b54cad Eduard Zingerman 2025-06-11 19396 } f1bca824dabba4 Alexei Starovoitov 2014-09-29 19397 return 1; dc503a8ad98474 Edward Cree 2017-08-15 19398 } 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19399 miss: 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19400 /* when new state is not going to be added do not increase miss count. 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19401 * Otherwise several loop iterations will remove the state 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19402 * recorded earlier. The goal of these heuristics is to have 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19403 * states from some iterations of the loop (some in the beginning 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19404 * and some at the end) to help pruning. 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19405 */ 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19406 if (add_new_state) 9f4686c41bdff0 Alexei Starovoitov 2019-04-01 19407 sl->miss_cnt++; 9f4686c41bdff0 Alexei Starovoitov 2019-04-01 19408 /* heuristic to determine whether this state is beneficial 9f4686c41bdff0 Alexei Starovoitov 2019-04-01 19409 * to keep checking from state equivalence point of view. 9f4686c41bdff0 Alexei Starovoitov 2019-04-01 19410 * Higher numbers increase max_states_per_insn and verification time, 9f4686c41bdff0 Alexei Starovoitov 2019-04-01 19411 * but do not meaningfully decrease insn_processed. 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19412 * 'n' controls how many times state could miss before eviction. 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19413 * Use bigger 'n' for checkpoints because evicting checkpoint states 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19414 * too early would hinder iterator convergence. 9f4686c41bdff0 Alexei Starovoitov 2019-04-01 19415 */ 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19416 n = is_force_checkpoint(env, insn_idx) && sl->state.branches > 0 ? 64 : 3; 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19417 if (sl->miss_cnt > sl->hit_cnt * n + n) { 9f4686c41bdff0 Alexei Starovoitov 2019-04-01 19418 /* the state is unlikely to be useful. Remove it to 9f4686c41bdff0 Alexei Starovoitov 2019-04-01 19419 * speed up verification 9f4686c41bdff0 Alexei Starovoitov 2019-04-01 19420 */ 408fcf946b2bad Eduard Zingerman 2025-02-15 19421 sl->in_free_list = true; 5564ee3abb2ebe Eduard Zingerman 2025-02-15 19422 list_del(&sl->node); 5564ee3abb2ebe Eduard Zingerman 2025-02-15 19423 list_add(&sl->node, &env->free_list); 574078b001cdf6 Eduard Zingerman 2025-02-15 19424 env->free_list_size++; 574078b001cdf6 Eduard Zingerman 2025-02-15 19425 env->explored_states_size--; 408fcf946b2bad Eduard Zingerman 2025-02-15 19426 maybe_free_verifier_state(env, sl); 9f4686c41bdff0 Alexei Starovoitov 2019-04-01 19427 } f1bca824dabba4 Alexei Starovoitov 2014-09-29 19428 } f1bca824dabba4 Alexei Starovoitov 2014-09-29 19429 06ee7115b0d174 Alexei Starovoitov 2019-04-01 19430 if (env->max_states_per_insn < states_cnt) 06ee7115b0d174 Alexei Starovoitov 2019-04-01 19431 env->max_states_per_insn = states_cnt; f1bca824dabba4 Alexei Starovoitov 2014-09-29 19432 2c78ee898d8f10 Alexei Starovoitov 2020-05-13 19433 if (!env->bpf_capable && states_cnt > BPF_COMPLEXITY_LIMIT_STATES) a095f421057e22 Andrii Nakryiko 2022-12-06 19434 return 0; ceefbc96fa5c5b Alexei Starovoitov 2018-12-03 19435 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19436 if (!add_new_state) a095f421057e22 Andrii Nakryiko 2022-12-06 19437 return 0; ceefbc96fa5c5b Alexei Starovoitov 2018-12-03 19438 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19439 /* There were no equivalent states, remember the current one. 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19440 * Technically the current state is not proven to be safe yet, f4d7e40a5b7157 Alexei Starovoitov 2017-12-14 19441 * but it will either reach outer most bpf_exit (which means it's safe) 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19442 * or it will be rejected. When there are no loops the verifier won't be f4d7e40a5b7157 Alexei Starovoitov 2017-12-14 19443 * seeing this tuple (frame[0].callsite, frame[1].callsite, .. insn_idx) 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19444 * again on the way to bpf_exit. 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19445 * When looping the sl->state.branches will be > 0 and this state 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19446 * will not be considered for equivalence until branches == 0. f1bca824dabba4 Alexei Starovoitov 2014-09-29 19447 */ 43736ec3e02789 Eduard Zingerman 2025-06-13 19448 new_sl = kzalloc(sizeof(struct bpf_verifier_state_list), GFP_KERNEL_ACCOUNT); f1bca824dabba4 Alexei Starovoitov 2014-09-29 19449 if (!new_sl) f1bca824dabba4 Alexei Starovoitov 2014-09-29 19450 return -ENOMEM; 06ee7115b0d174 Alexei Starovoitov 2019-04-01 19451 env->total_states++; 574078b001cdf6 Eduard Zingerman 2025-02-15 19452 env->explored_states_size++; 574078b001cdf6 Eduard Zingerman 2025-02-15 19453 update_peak_states(env); 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19454 env->prev_jmps_processed = env->jmps_processed; 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19455 env->prev_insn_processed = env->insn_processed; f1bca824dabba4 Alexei Starovoitov 2014-09-29 19456 7a830b53c17bba Andrii Nakryiko 2022-11-04 19457 /* forget precise markings we inherited, see __mark_chain_precision */ 7a830b53c17bba Andrii Nakryiko 2022-11-04 19458 if (env->bpf_capable) 7a830b53c17bba Andrii Nakryiko 2022-11-04 19459 mark_all_scalars_imprecise(env, cur); 7a830b53c17bba Andrii Nakryiko 2022-11-04 19460 f1bca824dabba4 Alexei Starovoitov 2014-09-29 19461 /* add new state to the head of linked list */ 679c782de14bd4 Edward Cree 2018-08-22 19462 new = &new_sl->state; 679c782de14bd4 Edward Cree 2018-08-22 19463 err = copy_verifier_state(new, cur); 1969db47f8d0e8 Alexei Starovoitov 2017-11-01 19464 if (err) { 679c782de14bd4 Edward Cree 2018-08-22 19465 free_verifier_state(new, false); 1969db47f8d0e8 Alexei Starovoitov 2017-11-01 19466 kfree(new_sl); 1969db47f8d0e8 Alexei Starovoitov 2017-11-01 19467 return err; 1969db47f8d0e8 Alexei Starovoitov 2017-11-01 19468 } dc2a4ebc0b44a2 Alexei Starovoitov 2019-05-21 19469 new->insn_idx = insn_idx; 0df1a55afa832f Paul Chaignon 2025-07-01 19470 verifier_bug_if(new->branches != 1, env, 0df1a55afa832f Paul Chaignon 2025-07-01 19471 "%s:branches_to_explore=%d insn %d", 0df1a55afa832f Paul Chaignon 2025-07-01 19472 __func__, new->branches, insn_idx); c9e31900b54cad Eduard Zingerman 2025-06-11 19473 err = maybe_enter_scc(env, new); c9e31900b54cad Eduard Zingerman 2025-06-11 19474 if (err) { c9e31900b54cad Eduard Zingerman 2025-06-11 19475 free_verifier_state(new, false); e4980fa6463624 Feng Yang 2025-08-27 19476 kfree(new_sl); c9e31900b54cad Eduard Zingerman 2025-06-11 19477 return err; c9e31900b54cad Eduard Zingerman 2025-06-11 19478 } b5dc0163d8fd78 Alexei Starovoitov 2019-06-15 19479 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19480 cur->parent = new; b5dc0163d8fd78 Alexei Starovoitov 2019-06-15 19481 cur->first_insn_idx = insn_idx; 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19482 cur->dfs_depth = new->dfs_depth + 1; baaebe0928bf32 Eduard Zingerman 2025-06-11 19483 clear_jmp_history(cur); 5564ee3abb2ebe Eduard Zingerman 2025-02-15 19484 list_add(&new_sl->node, head); f1bca824dabba4 Alexei Starovoitov 2014-09-29 19485 return 0; f1bca824dabba4 Alexei Starovoitov 2014-09-29 19486 } f1bca824dabba4 Alexei Starovoitov 2014-09-29 19487 -- 0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki ^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH bpf-next v1 09/10] bpf: disable and remove registers chain based liveness 2025-09-11 14:19 ` kernel test robot @ 2025-09-11 21:26 ` Eduard Zingerman 2025-09-11 22:00 ` Alexei Starovoitov 0 siblings, 1 reply; 22+ messages in thread From: Eduard Zingerman @ 2025-09-11 21:26 UTC (permalink / raw) To: kernel test robot, bpf, ast, andrii Cc: llvm, oe-kbuild-all, daniel, martin.lau, kernel-team, yonghong.song On Thu, 2025-09-11 at 22:19 +0800, kernel test robot wrote: > Hi Eduard, > > kernel test robot noticed the following build warnings: > > [auto build test WARNING on bpf-next/master] > > url: https://github.com/intel-lab-lkp/linux/commits/Eduard-Zingerman/bpf-bpf_verifier_state-cleaned-flag-instead-of-REG_LIVE_DONE/20250911-090604 > base: https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git master > patch link: https://lore.kernel.org/r/20250911010437.2779173-10-eddyz87%40gmail.com > patch subject: [PATCH bpf-next v1 09/10] bpf: disable and remove registers chain based liveness > config: x86_64-buildonly-randconfig-003-20250911 (https://download.01.org/0day-ci/archive/20250911/202509112112.wkWw6wJW-lkp@intel.com/config) > compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261) > reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250911/202509112112.wkWw6wJW-lkp@intel.com/reproduce) > > If you fix the issue in a separate patch/commit (i.e. not just a new version of > the same patch/commit), kindly add following tags > > Reported-by: kernel test robot <lkp@intel.com> > > Closes: https://lore.kernel.org/oe-kbuild-all/202509112112.wkWw6wJW-lkp@intel.com/ > > All warnings (new ones prefixed by >>): > > > > kernel/bpf/verifier.c:19305:11: warning: variable 'err' is uninitialized when used here [-Wuninitialized] > 19305 | err = err ? : push_jmp_history(env, cur, 0, 0); > | ^~~ > kernel/bpf/verifier.c:19140:12: note: initialize the variable 'err' to silence this warning > 19140 | int n, err, states_cnt = 0; > | ^ > | = 0 > 1 warning generated. > > > vim +/err +19305 kernel/bpf/verifier.c This was sloppy on my side, should look as follows: --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -19297,9 +19297,12 @@ static int is_state_visited(struct bpf_verifier_env *env, int insn_idx) * the precision needs to be propagated back in * the current state. */ - if (is_jmp_point(env, env->insn_idx)) - err = err ? : push_jmp_history(env, cur, 0, 0); - err = err ? : propagate_precision(env, &sl->state, cur, NULL); + if (is_jmp_point(env, env->insn_idx)) { + err = push_jmp_history(env, cur, 0, 0); + if (err) + return err; + } + err = propagate_precision(env, &sl->state, cur, NULL); if (err) return err; /* When processing iterator based loops above propagate_liveness and [...] ^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH bpf-next v1 09/10] bpf: disable and remove registers chain based liveness 2025-09-11 21:26 ` Eduard Zingerman @ 2025-09-11 22:00 ` Alexei Starovoitov 2025-09-11 22:07 ` Eduard Zingerman 0 siblings, 1 reply; 22+ messages in thread From: Alexei Starovoitov @ 2025-09-11 22:00 UTC (permalink / raw) To: Eduard Zingerman Cc: kernel test robot, bpf, Alexei Starovoitov, Andrii Nakryiko, clang-built-linux, oe-kbuild-all, Daniel Borkmann, Martin KaFai Lau, Kernel Team, Yonghong Song On Thu, Sep 11, 2025 at 2:26 PM Eduard Zingerman <eddyz87@gmail.com> wrote: > > On Thu, 2025-09-11 at 22:19 +0800, kernel test robot wrote: > > Hi Eduard, > > > > kernel test robot noticed the following build warnings: > > > > [auto build test WARNING on bpf-next/master] > > > > url: https://github.com/intel-lab-lkp/linux/commits/Eduard-Zingerman/bpf-bpf_verifier_state-cleaned-flag-instead-of-REG_LIVE_DONE/20250911-090604 > > base: https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git master > > patch link: https://lore.kernel.org/r/20250911010437.2779173-10-eddyz87%40gmail.com > > patch subject: [PATCH bpf-next v1 09/10] bpf: disable and remove registers chain based liveness > > config: x86_64-buildonly-randconfig-003-20250911 (https://download.01.org/0day-ci/archive/20250911/202509112112.wkWw6wJW-lkp@intel.com/config) > > compiler: clang version 20.1.8 (https://github.com/llvm/llvm-project 87f0227cb60147a26a1eeb4fb06e3b505e9c7261) > > reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20250911/202509112112.wkWw6wJW-lkp@intel.com/reproduce) > > > > If you fix the issue in a separate patch/commit (i.e. not just a new version of > > the same patch/commit), kindly add following tags > > > Reported-by: kernel test robot <lkp@intel.com> > > > Closes: https://lore.kernel.org/oe-kbuild-all/202509112112.wkWw6wJW-lkp@intel.com/ > > > > All warnings (new ones prefixed by >>): > > > > > > kernel/bpf/verifier.c:19305:11: warning: variable 'err' is uninitialized when used here [-Wuninitialized] > > 19305 | err = err ? : push_jmp_history(env, cur, 0, 0); > > | ^~~ > > kernel/bpf/verifier.c:19140:12: note: initialize the variable 'err' to silence this warning > > 19140 | int n, err, states_cnt = 0; > > | ^ > > | = 0 > > 1 warning generated. > > > > > > vim +/err +19305 kernel/bpf/verifier.c > > This was sloppy on my side, should look as follows: > > --- a/kernel/bpf/verifier.c > +++ b/kernel/bpf/verifier.c > @@ -19297,9 +19297,12 @@ static int is_state_visited(struct bpf_verifier_env *env, int insn_idx) > * the precision needs to be propagated back in > * the current state. > */ > - if (is_jmp_point(env, env->insn_idx)) > - err = err ? : push_jmp_history(env, cur, 0, 0); > - err = err ? : propagate_precision(env, &sl->state, cur, NULL); > + if (is_jmp_point(env, env->insn_idx)) { > + err = push_jmp_history(env, cur, 0, 0); > + if (err) > + return err; > + } > + err = propagate_precision(env, &sl->state, cur, NULL); hmm. init err=0 instead and avoid explicit if (err)return err ? ^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH bpf-next v1 09/10] bpf: disable and remove registers chain based liveness 2025-09-11 22:00 ` Alexei Starovoitov @ 2025-09-11 22:07 ` Eduard Zingerman 0 siblings, 0 replies; 22+ messages in thread From: Eduard Zingerman @ 2025-09-11 22:07 UTC (permalink / raw) To: Alexei Starovoitov Cc: kernel test robot, bpf, Alexei Starovoitov, Andrii Nakryiko, clang-built-linux, oe-kbuild-all, Daniel Borkmann, Martin KaFai Lau, Kernel Team, Yonghong Song On Thu, 2025-09-11 at 15:00 -0700, Alexei Starovoitov wrote: [...] > > --- a/kernel/bpf/verifier.c > > +++ b/kernel/bpf/verifier.c > > @@ -19297,9 +19297,12 @@ static int is_state_visited(struct bpf_verifier_env *env, int insn_idx) > > * the precision needs to be propagated back in > > * the current state. > > */ > > - if (is_jmp_point(env, env->insn_idx)) > > - err = err ? : push_jmp_history(env, cur, 0, 0); > > - err = err ? : propagate_precision(env, &sl->state, cur, NULL); > > + if (is_jmp_point(env, env->insn_idx)) { > > + err = push_jmp_history(env, cur, 0, 0); > > + if (err) > > + return err; > > + } > > + err = propagate_precision(env, &sl->state, cur, NULL); > > hmm. init err=0 instead and avoid explicit if (err)return err ? Or like that, yes. ^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH bpf-next v1 09/10] bpf: disable and remove registers chain based liveness 2025-09-11 1:04 ` [PATCH bpf-next v1 09/10] bpf: disable and remove registers chain based liveness Eduard Zingerman 2025-09-11 14:19 ` kernel test robot @ 2025-09-12 8:17 ` Dan Carpenter 2025-09-12 16:48 ` Eduard Zingerman 1 sibling, 1 reply; 22+ messages in thread From: Dan Carpenter @ 2025-09-12 8:17 UTC (permalink / raw) To: oe-kbuild, Eduard Zingerman, bpf, ast, andrii Cc: lkp, oe-kbuild-all, daniel, martin.lau, kernel-team, yonghong.song, eddyz87 Hi Eduard, kernel test robot noticed the following build warnings: url: https://github.com/intel-lab-lkp/linux/commits/Eduard-Zingerman/bpf-bpf_verifier_state-cleaned-flag-instead-of-REG_LIVE_DONE/20250911-090604 base: https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git master patch link: https://lore.kernel.org/r/20250911010437.2779173-10-eddyz87%40gmail.com patch subject: [PATCH bpf-next v1 09/10] bpf: disable and remove registers chain based liveness config: arm-randconfig-r071-20250911 (https://download.01.org/0day-ci/archive/20250912/202509120205.YfzyI2gp-lkp@intel.com/config) compiler: clang version 22.0.0git (https://github.com/llvm/llvm-project 21857ae337e0892a5522b6e7337899caa61de2a6) If you fix the issue in a separate patch/commit (i.e. not just a new version of the same patch/commit), kindly add following tags | Reported-by: kernel test robot <lkp@intel.com> | Reported-by: Dan Carpenter <dan.carpenter@linaro.org> | Closes: https://lore.kernel.org/r/202509120205.YfzyI2gp-lkp@intel.com/ smatch warnings: kernel/bpf/verifier.c:19305 is_state_visited() error: uninitialized symbol 'err'. vim +/err +19305 kernel/bpf/verifier.c 58e2af8b3a6b58 Jakub Kicinski 2016-09-21 19134 static int is_state_visited(struct bpf_verifier_env *env, int insn_idx) f1bca824dabba4 Alexei Starovoitov 2014-09-29 19135 { 58e2af8b3a6b58 Jakub Kicinski 2016-09-21 19136 struct bpf_verifier_state_list *new_sl; 5564ee3abb2ebe Eduard Zingerman 2025-02-15 19137 struct bpf_verifier_state_list *sl; c9e31900b54cad Eduard Zingerman 2025-06-11 19138 struct bpf_verifier_state *cur = env->cur_state, *new; c9e31900b54cad Eduard Zingerman 2025-06-11 19139 bool force_new_state, add_new_state, loop; d5c95ed86213e4 Eduard Zingerman 2025-09-10 19140 int n, err, states_cnt = 0; 5564ee3abb2ebe Eduard Zingerman 2025-02-15 19141 struct list_head *pos, *tmp, *head; aa30eb3260b2de Eduard Zingerman 2024-10-29 19142 aa30eb3260b2de Eduard Zingerman 2024-10-29 19143 force_new_state = env->test_state_freq || is_force_checkpoint(env, insn_idx) || aa30eb3260b2de Eduard Zingerman 2024-10-29 19144 /* Avoid accumulating infinitely long jmp history */ baaebe0928bf32 Eduard Zingerman 2025-06-11 19145 cur->jmp_history_cnt > 40; f1bca824dabba4 Alexei Starovoitov 2014-09-29 19146 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19147 /* bpf progs typically have pruning point every 4 instructions 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19148 * http://vger.kernel.org/bpfconf2019.html#session-1 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19149 * Do not add new state for future pruning if the verifier hasn't seen 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19150 * at least 2 jumps and at least 8 instructions. 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19151 * This heuristics helps decrease 'total_states' and 'peak_states' metric. 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19152 * In tests that amounts to up to 50% reduction into total verifier 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19153 * memory consumption and 20% verifier time speedup. 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19154 */ aa30eb3260b2de Eduard Zingerman 2024-10-29 19155 add_new_state = force_new_state; 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19156 if (env->jmps_processed - env->prev_jmps_processed >= 2 && 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19157 env->insn_processed - env->prev_insn_processed >= 8) 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19158 add_new_state = true; 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19159 9242b5f5615c82 Alexei Starovoitov 2018-12-13 19160 clean_live_states(env, insn_idx, cur); 9242b5f5615c82 Alexei Starovoitov 2018-12-13 19161 c9e31900b54cad Eduard Zingerman 2025-06-11 19162 loop = false; 5564ee3abb2ebe Eduard Zingerman 2025-02-15 19163 head = explored_state(env, insn_idx); 5564ee3abb2ebe Eduard Zingerman 2025-02-15 19164 list_for_each_safe(pos, tmp, head) { 5564ee3abb2ebe Eduard Zingerman 2025-02-15 19165 sl = container_of(pos, struct bpf_verifier_state_list, node); dc2a4ebc0b44a2 Alexei Starovoitov 2019-05-21 19166 states_cnt++; dc2a4ebc0b44a2 Alexei Starovoitov 2019-05-21 19167 if (sl->state.insn_idx != insn_idx) 5564ee3abb2ebe Eduard Zingerman 2025-02-15 19168 continue; bfc6bb74e4f16a Alexei Starovoitov 2021-07-14 19169 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19170 if (sl->state.branches) { bfc6bb74e4f16a Alexei Starovoitov 2021-07-14 19171 struct bpf_func_state *frame = sl->state.frame[sl->state.curframe]; bfc6bb74e4f16a Alexei Starovoitov 2021-07-14 19172 bfc6bb74e4f16a Alexei Starovoitov 2021-07-14 19173 if (frame->in_async_callback_fn && bfc6bb74e4f16a Alexei Starovoitov 2021-07-14 19174 frame->async_entry_cnt != cur->frame[cur->curframe]->async_entry_cnt) { bfc6bb74e4f16a Alexei Starovoitov 2021-07-14 19175 /* Different async_entry_cnt means that the verifier is bfc6bb74e4f16a Alexei Starovoitov 2021-07-14 19176 * processing another entry into async callback. bfc6bb74e4f16a Alexei Starovoitov 2021-07-14 19177 * Seeing the same state is not an indication of infinite bfc6bb74e4f16a Alexei Starovoitov 2021-07-14 19178 * loop or infinite recursion. bfc6bb74e4f16a Alexei Starovoitov 2021-07-14 19179 * But finding the same state doesn't mean that it's safe bfc6bb74e4f16a Alexei Starovoitov 2021-07-14 19180 * to stop processing the current state. The previous state bfc6bb74e4f16a Alexei Starovoitov 2021-07-14 19181 * hasn't yet reached bpf_exit, since state.branches > 0. bfc6bb74e4f16a Alexei Starovoitov 2021-07-14 19182 * Checking in_async_callback_fn alone is not enough either. bfc6bb74e4f16a Alexei Starovoitov 2021-07-14 19183 * Since the verifier still needs to catch infinite loops bfc6bb74e4f16a Alexei Starovoitov 2021-07-14 19184 * inside async callbacks. bfc6bb74e4f16a Alexei Starovoitov 2021-07-14 19185 */ 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19186 goto skip_inf_loop_check; 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19187 } 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19188 /* BPF open-coded iterators loop detection is special. 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19189 * states_maybe_looping() logic is too simplistic in detecting 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19190 * states that *might* be equivalent, because it doesn't know 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19191 * about ID remapping, so don't even perform it. 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19192 * See process_iter_next_call() and iter_active_depths_differ() 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19193 * for overview of the logic. When current and one of parent 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19194 * states are detected as equivalent, it's a good thing: we prove 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19195 * convergence and can stop simulating further iterations. 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19196 * It's safe to assume that iterator loop will finish, taking into 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19197 * account iter_next() contract of eventually returning 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19198 * sticky NULL result. 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19199 * 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19200 * Note, that states have to be compared exactly in this case because 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19201 * read and precision marks might not be finalized inside the loop. 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19202 * E.g. as in the program below: 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19203 * 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19204 * 1. r7 = -16 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19205 * 2. r6 = bpf_get_prandom_u32() 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19206 * 3. while (bpf_iter_num_next(&fp[-8])) { 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19207 * 4. if (r6 != 42) { 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19208 * 5. r7 = -32 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19209 * 6. r6 = bpf_get_prandom_u32() 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19210 * 7. continue 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19211 * 8. } 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19212 * 9. r0 = r10 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19213 * 10. r0 += r7 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19214 * 11. r8 = *(u64 *)(r0 + 0) 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19215 * 12. r6 = bpf_get_prandom_u32() 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19216 * 13. } 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19217 * 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19218 * Here verifier would first visit path 1-3, create a checkpoint at 3 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19219 * with r7=-16, continue to 4-7,3. Existing checkpoint at 3 does 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19220 * not have read or precision mark for r7 yet, thus inexact states 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19221 * comparison would discard current state with r7=-32 2793a8b015f7f1 Eduard Zingerman 2023-10-24 19222 * => unsafe memory access at 11 would not be caught. 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19223 */ 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19224 if (is_iter_next_insn(env, insn_idx)) { 4f81c16f50baf6 Alexei Starovoitov 2024-03-05 19225 if (states_equal(env, &sl->state, cur, RANGE_WITHIN)) { 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19226 struct bpf_func_state *cur_frame; 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19227 struct bpf_reg_state *iter_state, *iter_reg; 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19228 int spi; 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19229 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19230 cur_frame = cur->frame[cur->curframe]; 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19231 /* btf_check_iter_kfuncs() enforces that 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19232 * iter state pointer is always the first arg 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19233 */ 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19234 iter_reg = &cur_frame->regs[BPF_REG_1]; 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19235 /* current state is valid due to states_equal(), 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19236 * so we can assume valid iter and reg state, 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19237 * no need for extra (re-)validations 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19238 */ 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19239 spi = __get_spi(iter_reg->off + iter_reg->var_off.value); 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19240 iter_state = &func(env, iter_reg)->stack[spi].spilled_ptr; 2a0992829ea386 Eduard Zingerman 2023-10-24 19241 if (iter_state->iter.state == BPF_ITER_STATE_ACTIVE) { c9e31900b54cad Eduard Zingerman 2025-06-11 19242 loop = true; 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19243 goto hit; 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19244 } 2a0992829ea386 Eduard Zingerman 2023-10-24 19245 } 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19246 goto skip_inf_loop_check; 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19247 } 011832b97b311b Alexei Starovoitov 2024-03-05 19248 if (is_may_goto_insn_at(env, insn_idx)) { 2b2efe1937ca9f Alexei Starovoitov 2024-06-19 19249 if (sl->state.may_goto_depth != cur->may_goto_depth && 2b2efe1937ca9f Alexei Starovoitov 2024-06-19 19250 states_equal(env, &sl->state, cur, RANGE_WITHIN)) { c9e31900b54cad Eduard Zingerman 2025-06-11 19251 loop = true; 011832b97b311b Alexei Starovoitov 2024-03-05 19252 goto hit; 011832b97b311b Alexei Starovoitov 2024-03-05 19253 } 011832b97b311b Alexei Starovoitov 2024-03-05 19254 } 588af0c506ec8e Eduard Zingerman 2025-09-10 19255 if (bpf_calls_callback(env, insn_idx)) { 4f81c16f50baf6 Alexei Starovoitov 2024-03-05 19256 if (states_equal(env, &sl->state, cur, RANGE_WITHIN)) ab5cfac139ab85 Eduard Zingerman 2023-11-21 19257 goto hit; ab5cfac139ab85 Eduard Zingerman 2023-11-21 19258 goto skip_inf_loop_check; ab5cfac139ab85 Eduard Zingerman 2023-11-21 19259 } 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19260 /* attempt to detect infinite loop to avoid unnecessary doomed work */ 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19261 if (states_maybe_looping(&sl->state, cur) && 4f81c16f50baf6 Alexei Starovoitov 2024-03-05 19262 states_equal(env, &sl->state, cur, EXACT) && ab5cfac139ab85 Eduard Zingerman 2023-11-21 19263 !iter_active_depths_differ(&sl->state, cur) && 011832b97b311b Alexei Starovoitov 2024-03-05 19264 sl->state.may_goto_depth == cur->may_goto_depth && ab5cfac139ab85 Eduard Zingerman 2023-11-21 19265 sl->state.callback_unroll_depth == cur->callback_unroll_depth) { 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19266 verbose_linfo(env, insn_idx, "; "); 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19267 verbose(env, "infinite loop detected at insn %d\n", insn_idx); b4d8239534fddc Eduard Zingerman 2023-10-24 19268 verbose(env, "cur state:"); 1995edc5f9089e Kumar Kartikeya Dwivedi 2024-12-03 19269 print_verifier_state(env, cur, cur->curframe, true); b4d8239534fddc Eduard Zingerman 2023-10-24 19270 verbose(env, "old state:"); 1995edc5f9089e Kumar Kartikeya Dwivedi 2024-12-03 19271 print_verifier_state(env, &sl->state, cur->curframe, true); 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19272 return -EINVAL; 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19273 } 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19274 /* if the verifier is processing a loop, avoid adding new state 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19275 * too often, since different loop iterations have distinct 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19276 * states and may not help future pruning. 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19277 * This threshold shouldn't be too low to make sure that 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19278 * a loop with large bound will be rejected quickly. 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19279 * The most abusive loop will be: 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19280 * r1 += 1 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19281 * if r1 < 1000000 goto pc-2 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19282 * 1M insn_procssed limit / 100 == 10k peak states. 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19283 * This threshold shouldn't be too high either, since states 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19284 * at the end of the loop are likely to be useful in pruning. 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19285 */ 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19286 skip_inf_loop_check: 4b5ce570dbef57 Andrii Nakryiko 2023-03-09 19287 if (!force_new_state && 98ddcf389d1bb7 Andrii Nakryiko 2023-03-02 19288 env->jmps_processed - env->prev_jmps_processed < 20 && 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19289 env->insn_processed - env->prev_insn_processed < 100) 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19290 add_new_state = false; 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19291 goto miss; 2589726d12a1b1 Alexei Starovoitov 2019-06-15 19292 } c9e31900b54cad Eduard Zingerman 2025-06-11 19293 /* See comments for mark_all_regs_read_and_precise() */ c9e31900b54cad Eduard Zingerman 2025-06-11 19294 loop = incomplete_read_marks(env, &sl->state); c9e31900b54cad Eduard Zingerman 2025-06-11 19295 if (states_equal(env, &sl->state, cur, loop ? RANGE_WITHIN : NOT_EXACT)) { 06accc8779c1d5 Andrii Nakryiko 2023-03-08 19296 hit: 9f4686c41bdff0 Alexei Starovoitov 2019-04-01 19297 sl->hit_cnt++; a3ce685dd01a78 Alexei Starovoitov 2019-06-28 19298 a3ce685dd01a78 Alexei Starovoitov 2019-06-28 19299 /* if previous state reached the exit with precision and a7de265cb2d849 Rafael Passos 2024-04-17 19300 * current state is equivalent to it (except precision marks) a3ce685dd01a78 Alexei Starovoitov 2019-06-28 19301 * the precision needs to be propagated back in a3ce685dd01a78 Alexei Starovoitov 2019-06-28 19302 * the current state. a3ce685dd01a78 Alexei Starovoitov 2019-06-28 19303 */ 41f6f64e6999a8 Andrii Nakryiko 2023-12-05 19304 if (is_jmp_point(env, env->insn_idx)) baaebe0928bf32 Eduard Zingerman 2025-06-11 @19305 err = err ? : push_jmp_history(env, cur, 0, 0); ^^^ err needs to be initialized to zero at the start. Btw, I really encourage people to use CONFIG_INIT_STACK_ALL_PATTERN=y in their testing. (In production everyone should use CONFIG_INIT_STACK_ALL_ZERO=y). 23b37d616565c8 Eduard Zingerman 2025-06-11 19306 err = err ? : propagate_precision(env, &sl->state, cur, NULL); f4d7e40a5b7157 Alexei Starovoitov 2017-12-14 19307 if (err) f4d7e40a5b7157 Alexei Starovoitov 2017-12-14 19308 return err; c9e31900b54cad Eduard Zingerman 2025-06-11 19309 /* When processing iterator based loops above propagate_liveness and c9e31900b54cad Eduard Zingerman 2025-06-11 19310 * propagate_precision calls are not sufficient to transfer all relevant c9e31900b54cad Eduard Zingerman 2025-06-11 19311 * read and precision marks. E.g. consider the following case: -- 0-DAY CI Kernel Test Service https://github.com/intel/lkp-tests/wiki ^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [PATCH bpf-next v1 09/10] bpf: disable and remove registers chain based liveness 2025-09-12 8:17 ` Dan Carpenter @ 2025-09-12 16:48 ` Eduard Zingerman 0 siblings, 0 replies; 22+ messages in thread From: Eduard Zingerman @ 2025-09-12 16:48 UTC (permalink / raw) To: Dan Carpenter, oe-kbuild, bpf, ast, andrii Cc: lkp, oe-kbuild-all, daniel, martin.lau, kernel-team, yonghong.song On Fri, 2025-09-12 at 11:17 +0300, Dan Carpenter wrote: > Hi Eduard, > > kernel test robot noticed the following build warnings: > > url: https://github.com/intel-lab-lkp/linux/commits/Eduard-Zingerman/bpf-bpf_verifier_state-cleaned-flag-instead-of-REG_LIVE_DONE/20250911-090604 > base: https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf-next.git master > patch link: https://lore.kernel.org/r/20250911010437.2779173-10-eddyz87%40gmail.com > patch subject: [PATCH bpf-next v1 09/10] bpf: disable and remove registers chain based liveness > config: arm-randconfig-r071-20250911 (https://download.01.org/0day-ci/archive/20250912/202509120205.YfzyI2gp-lkp@intel.com/config) > compiler: clang version 22.0.0git (https://github.com/llvm/llvm-project 21857ae337e0892a5522b6e7337899caa61de2a6) > > If you fix the issue in a separate patch/commit (i.e. not just a new version of > the same patch/commit), kindly add following tags > > Reported-by: kernel test robot <lkp@intel.com> > > Reported-by: Dan Carpenter <dan.carpenter@linaro.org> > > Closes: https://lore.kernel.org/r/202509120205.YfzyI2gp-lkp@intel.com/ > > smatch warnings: > kernel/bpf/verifier.c:19305 is_state_visited() error: uninitialized symbol 'err'. Hi Dan, Thank you for the report, kernel test robot already notified me here: https://lore.kernel.org/bpf/20250911010437.2779173-1-eddyz87@gmail.com/T/#m103a269f4c096b34043bef16fa5f1d629b794968 Thanks, Eduard [...] ^ permalink raw reply [flat|nested] 22+ messages in thread
* [PATCH bpf-next v1 10/10] bpf: table based bpf_insn_successors() 2025-09-11 1:04 [PATCH bpf-next v1 00/10] bpf: replace path-sensitive with path-insensitive live stack analysis Eduard Zingerman ` (8 preceding siblings ...) 2025-09-11 1:04 ` [PATCH bpf-next v1 09/10] bpf: disable and remove registers chain based liveness Eduard Zingerman @ 2025-09-11 1:04 ` Eduard Zingerman 2025-09-11 6:57 ` [syzbot ci] Re: bpf: replace path-sensitive with path-insensitive live stack analysis syzbot ci 10 siblings, 0 replies; 22+ messages in thread From: Eduard Zingerman @ 2025-09-11 1:04 UTC (permalink / raw) To: bpf, ast, andrii; +Cc: daniel, martin.lau, kernel-team, yonghong.song, eddyz87 Converting bpf_insn_successors() to use lookup table makes it ~1.5 times faster. Also remove unnecessary conditionals: - `idx + 1 < prog->len` is unnecessary because after check_cfg() all jump targets are guaranteed to be within a program; - `i == 0 || succ[0] != dst` is unnecessary because any client of bpf_insn_successors() can handle duplicate edges: - compute_live_registers() - compute_scc() Moving bpf_insn_successors() to liveness.c allows its inlining in liveness.c:__update_stack_liveness(). Such inlining speeds up __update_stack_liveness() by ~40%. bpf_insn_successors() is used in both verifier.c and liveness.c. perf shows such move does not negatively impact users in verifier.c, as these are executed only once before main varification pass. Unlike __update_stack_liveness() which can be triggered multiple times. Signed-off-by: Eduard Zingerman <eddyz87@gmail.com> --- include/linux/bpf_verifier.h | 1 + kernel/bpf/liveness.c | 51 +++++++++++++++++++++++++ kernel/bpf/verifier.c | 72 +----------------------------------- 3 files changed, 53 insertions(+), 71 deletions(-) diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h index c7515da8500c..4c497e839526 100644 --- a/include/linux/bpf_verifier.h +++ b/include/linux/bpf_verifier.h @@ -1049,6 +1049,7 @@ void print_insn_state(struct bpf_verifier_env *env, const struct bpf_verifier_st u32 frameno); struct bpf_subprog_info *bpf_find_containing_subprog(struct bpf_verifier_env *env, int off); +int bpf_jmp_offset(struct bpf_insn *insn); int bpf_insn_successors(struct bpf_prog *prog, u32 idx, u32 succ[2]); void bpf_fmt_stack_mask(char *buf, ssize_t buf_sz, u64 stack_mask); bool bpf_calls_callback(struct bpf_verifier_env *env, int insn_idx); diff --git a/kernel/bpf/liveness.c b/kernel/bpf/liveness.c index 2b2e909ec944..6fb79a63d216 100644 --- a/kernel/bpf/liveness.c +++ b/kernel/bpf/liveness.c @@ -428,6 +428,57 @@ static void log_mask_change(struct bpf_verifier_env *env, struct callchain *call bpf_log(&env->log, "\n"); } +int bpf_jmp_offset(struct bpf_insn *insn) +{ + u8 code = insn->code; + + if (code == (BPF_JMP32 | BPF_JA)) + return insn->imm; + return insn->off; +} + +inline int bpf_insn_successors(struct bpf_prog *prog, u32 idx, u32 succ[2]) +{ + static const struct opcode_info { + bool can_jump; + bool can_fallthrough; + } opcode_info_tbl[256] = { + [0 ... 255] = {.can_jump = false, .can_fallthrough = true}, + #define _J(code, ...) \ + [BPF_JMP | code] = __VA_ARGS__, \ + [BPF_JMP32 | code] = __VA_ARGS__ + + _J(BPF_EXIT, {.can_jump = false, .can_fallthrough = false}), + _J(BPF_JA, {.can_jump = true, .can_fallthrough = false}), + _J(BPF_JEQ, {.can_jump = true, .can_fallthrough = true}), + _J(BPF_JNE, {.can_jump = true, .can_fallthrough = true}), + _J(BPF_JLT, {.can_jump = true, .can_fallthrough = true}), + _J(BPF_JLE, {.can_jump = true, .can_fallthrough = true}), + _J(BPF_JGT, {.can_jump = true, .can_fallthrough = true}), + _J(BPF_JGE, {.can_jump = true, .can_fallthrough = true}), + _J(BPF_JSGT, {.can_jump = true, .can_fallthrough = true}), + _J(BPF_JSGE, {.can_jump = true, .can_fallthrough = true}), + _J(BPF_JSLT, {.can_jump = true, .can_fallthrough = true}), + _J(BPF_JSLE, {.can_jump = true, .can_fallthrough = true}), + _J(BPF_JCOND, {.can_jump = true, .can_fallthrough = true}), + _J(BPF_JSET, {.can_jump = true, .can_fallthrough = true}), + #undef _J + }; + struct bpf_insn *insn = &prog->insnsi[idx]; + const struct opcode_info *opcode_info; + int i = 0, insn_sz; + + opcode_info = &opcode_info_tbl[BPF_CLASS(insn->code) | BPF_OP(insn->code)]; + insn_sz = bpf_is_ldimm64(insn) ? 2 : 1; + if (opcode_info->can_fallthrough) + succ[i++] = idx + insn_sz; + + if (opcode_info->can_jump) + succ[i++] = idx + bpf_jmp_offset(insn) + 1; + + return i; +} + static struct func_instance *get_outer_instance(struct bpf_verifier_env *env, struct func_instance *instance) { diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 6efb555a1e8a..9fd75a3e45b3 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -3470,15 +3470,6 @@ static int add_subprog_and_kfunc(struct bpf_verifier_env *env) return 0; } -static int jmp_offset(struct bpf_insn *insn) -{ - u8 code = insn->code; - - if (code == (BPF_JMP32 | BPF_JA)) - return insn->imm; - return insn->off; -} - static int check_subprogs(struct bpf_verifier_env *env) { int i, subprog_start, subprog_end, off, cur_subprog = 0; @@ -3505,7 +3496,7 @@ static int check_subprogs(struct bpf_verifier_env *env) goto next; if (BPF_OP(code) == BPF_EXIT || BPF_OP(code) == BPF_CALL) goto next; - off = i + jmp_offset(&insn[i]) + 1; + off = i + bpf_jmp_offset(&insn[i]) + 1; if (off < subprog_start || off >= subprog_end) { verbose(env, "jump out of range from insn %d to %d\n", i, off); return -EINVAL; @@ -23907,67 +23898,6 @@ static int process_fd_array(struct bpf_verifier_env *env, union bpf_attr *attr, return 0; } -static bool can_fallthrough(struct bpf_insn *insn) -{ - u8 class = BPF_CLASS(insn->code); - u8 opcode = BPF_OP(insn->code); - - if (class != BPF_JMP && class != BPF_JMP32) - return true; - - if (opcode == BPF_EXIT || opcode == BPF_JA) - return false; - - return true; -} - -static bool can_jump(struct bpf_insn *insn) -{ - u8 class = BPF_CLASS(insn->code); - u8 opcode = BPF_OP(insn->code); - - if (class != BPF_JMP && class != BPF_JMP32) - return false; - - switch (opcode) { - case BPF_JA: - case BPF_JEQ: - case BPF_JNE: - case BPF_JLT: - case BPF_JLE: - case BPF_JGT: - case BPF_JGE: - case BPF_JSGT: - case BPF_JSGE: - case BPF_JSLT: - case BPF_JSLE: - case BPF_JCOND: - case BPF_JSET: - return true; - } - - return false; -} - -int bpf_insn_successors(struct bpf_prog *prog, u32 idx, u32 succ[2]) -{ - struct bpf_insn *insn = &prog->insnsi[idx]; - int i = 0, insn_sz; - u32 dst; - - insn_sz = bpf_is_ldimm64(insn) ? 2 : 1; - if (can_fallthrough(insn) && idx + 1 < prog->len) - succ[i++] = idx + insn_sz; - - if (can_jump(insn)) { - dst = idx + jmp_offset(insn) + 1; - if (i == 0 || succ[0] != dst) - succ[i++] = dst; - } - - return i; -} - /* Each field is a register bitmask */ struct insn_live_regs { u16 use; /* registers read by instruction */ -- 2.47.3 ^ permalink raw reply related [flat|nested] 22+ messages in thread
* [syzbot ci] Re: bpf: replace path-sensitive with path-insensitive live stack analysis 2025-09-11 1:04 [PATCH bpf-next v1 00/10] bpf: replace path-sensitive with path-insensitive live stack analysis Eduard Zingerman ` (9 preceding siblings ...) 2025-09-11 1:04 ` [PATCH bpf-next v1 10/10] bpf: table based bpf_insn_successors() Eduard Zingerman @ 2025-09-11 6:57 ` syzbot ci 2025-09-11 21:09 ` Eduard Zingerman 10 siblings, 1 reply; 22+ messages in thread From: syzbot ci @ 2025-09-11 6:57 UTC (permalink / raw) To: andrii, ast, bpf, daniel, eddyz87, kernel-team, martin.lau, yonghong.song Cc: syzbot, syzkaller-bugs syzbot ci has tested the following series [v1] bpf: replace path-sensitive with path-insensitive live stack analysis https://lore.kernel.org/all/20250911010437.2779173-1-eddyz87@gmail.com * [PATCH bpf-next v1 01/10] bpf: bpf_verifier_state->cleaned flag instead of REG_LIVE_DONE * [PATCH bpf-next v1 02/10] bpf: use compute_live_registers() info in clean_func_state * [PATCH bpf-next v1 03/10] bpf: remove redundant REG_LIVE_READ check in stacksafe() * [PATCH bpf-next v1 04/10] bpf: declare a few utility functions as internal api * [PATCH bpf-next v1 05/10] bpf: compute instructions postorder per subprogram * [PATCH bpf-next v1 06/10] bpf: callchain sensitive stack liveness tracking using CFG * [PATCH bpf-next v1 07/10] bpf: enable callchain sensitive stack liveness tracking * [PATCH bpf-next v1 08/10] bpf: signal error if old liveness is more conservative than new * [PATCH bpf-next v1 09/10] bpf: disable and remove registers chain based liveness * [PATCH bpf-next v1 10/10] bpf: table based bpf_insn_successors() and found the following issue: KASAN: slab-out-of-bounds Write in compute_postorder Full report is available here: https://ci.syzbot.org/series/c42e236b-f40c-4d72-8ae7-da4e21c37e17 *** KASAN: slab-out-of-bounds Write in compute_postorder tree: bpf-next URL: https://kernel.googlesource.com/pub/scm/linux/kernel/git/bpf/bpf-next.git base: e12873ee856ffa6f104869b8ea10c0f741606f13 arch: amd64 compiler: Debian clang version 20.1.8 (++20250708063551+0c9f909b7976-1~exp1~20250708183702.136), Debian LLD 20.1.8 config: https://ci.syzbot.org/builds/6d2bc952-3d65-4bcd-9a84-1207b810a1b5/config C repro: https://ci.syzbot.org/findings/338e6ce4-7207-484f-a508-9b00b3121701/c_repro syz repro: https://ci.syzbot.org/findings/338e6ce4-7207-484f-a508-9b00b3121701/syz_repro ================================================================== BUG: KASAN: slab-out-of-bounds in compute_postorder+0x802/0xcb0 kernel/bpf/verifier.c:17840 Write of size 4 at addr ffff88801f1d4b98 by task syz.0.17/5991 CPU: 0 UID: 0 PID: 5991 Comm: syz.0.17 Not tainted syzkaller #0 PREEMPT(full) Hardware name: QEMU Standard PC (Q35 + ICH9, 2009), BIOS 1.16.2-debian-1.16.2-1 04/01/2014 Call Trace: <TASK> dump_stack_lvl+0x189/0x250 lib/dump_stack.c:120 print_address_description mm/kasan/report.c:378 [inline] print_report+0xca/0x240 mm/kasan/report.c:482 kasan_report+0x118/0x150 mm/kasan/report.c:595 compute_postorder+0x802/0xcb0 kernel/bpf/verifier.c:17840 bpf_check+0x1f90/0x1d440 kernel/bpf/verifier.c:24437 bpf_prog_load+0x1318/0x1930 kernel/bpf/syscall.c:2979 __sys_bpf+0x528/0x870 kernel/bpf/syscall.c:6029 __do_sys_bpf kernel/bpf/syscall.c:6139 [inline] __se_sys_bpf kernel/bpf/syscall.c:6137 [inline] __x64_sys_bpf+0x7c/0x90 kernel/bpf/syscall.c:6137 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0xfa/0x3b0 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f RIP: 0033:0x7f366058eba9 Code: ff ff c3 66 2e 0f 1f 84 00 00 00 00 00 0f 1f 40 00 48 89 f8 48 89 f7 48 89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73 01 c3 48 c7 c1 a8 ff ff ff f7 d8 64 89 01 48 RSP: 002b:00007ffef8486b28 EFLAGS: 00000246 ORIG_RAX: 0000000000000141 RAX: ffffffffffffffda RBX: 00007f36607d5fa0 RCX: 00007f366058eba9 RDX: 0000000000000070 RSI: 0000200000000440 RDI: 0000000000000005 RBP: 00007f3660611e19 R08: 0000000000000000 R09: 0000000000000000 R10: 0000000000000000 R11: 0000000000000246 R12: 0000000000000000 R13: 00007f36607d5fa0 R14: 00007f36607d5fa0 R15: 0000000000000003 </TASK> Allocated by task 5991: kasan_save_stack mm/kasan/common.c:47 [inline] kasan_save_track+0x3e/0x80 mm/kasan/common.c:68 poison_kmalloc_redzone mm/kasan/common.c:388 [inline] __kasan_kmalloc+0x93/0xb0 mm/kasan/common.c:405 kasan_kmalloc include/linux/kasan.h:260 [inline] __do_kmalloc_node mm/slub.c:4365 [inline] __kvmalloc_node_noprof+0x30d/0x5f0 mm/slub.c:5052 kvmalloc_array_node_noprof include/linux/slab.h:1065 [inline] compute_postorder+0xd6/0xcb0 kernel/bpf/verifier.c:17823 bpf_check+0x1f90/0x1d440 kernel/bpf/verifier.c:24437 bpf_prog_load+0x1318/0x1930 kernel/bpf/syscall.c:2979 __sys_bpf+0x528/0x870 kernel/bpf/syscall.c:6029 __do_sys_bpf kernel/bpf/syscall.c:6139 [inline] __se_sys_bpf kernel/bpf/syscall.c:6137 [inline] __x64_sys_bpf+0x7c/0x90 kernel/bpf/syscall.c:6137 do_syscall_x64 arch/x86/entry/syscall_64.c:63 [inline] do_syscall_64+0xfa/0x3b0 arch/x86/entry/syscall_64.c:94 entry_SYSCALL_64_after_hwframe+0x77/0x7f The buggy address belongs to the object at ffff88801f1d4b80 which belongs to the cache kmalloc-cg-32 of size 32 The buggy address is located 0 bytes to the right of allocated 24-byte region [ffff88801f1d4b80, ffff88801f1d4b98) The buggy address belongs to the physical page: page: refcount:0 mapcount:0 mapping:0000000000000000 index:0xffff88801f1d4e40 pfn:0x1f1d4 memcg:ffff888026bbb801 flags: 0xfff00000000000(node=0|zone=1|lastcpupid=0x7ff) page_type: f5(slab) raw: 00fff00000000000 ffff88801a449b40 dead000000000100 dead000000000122 raw: ffff88801f1d4e40 000000008040003f 00000000f5000000 ffff888026bbb801 page dumped because: kasan: bad access detected page_owner tracks the page as allocated page last allocated via order 0, migratetype Unmovable, gfp_mask 0x52cc0(GFP_KERNEL|__GFP_NOWARN|__GFP_NORETRY|__GFP_COMP), pid 532, tgid 532 (kworker/u10:0), ts 5351847364, free_ts 0 set_page_owner include/linux/page_owner.h:32 [inline] post_alloc_hook+0x240/0x2a0 mm/page_alloc.c:1851 prep_new_page mm/page_alloc.c:1859 [inline] get_page_from_freelist+0x21e4/0x22c0 mm/page_alloc.c:3858 __alloc_frozen_pages_noprof+0x181/0x370 mm/page_alloc.c:5148 alloc_pages_mpol+0x232/0x4a0 mm/mempolicy.c:2416 alloc_slab_page mm/slub.c:2487 [inline] allocate_slab+0x8a/0x370 mm/slub.c:2655 new_slab mm/slub.c:2709 [inline] ___slab_alloc+0xbeb/0x1410 mm/slub.c:3891 __slab_alloc mm/slub.c:3981 [inline] __slab_alloc_node mm/slub.c:4056 [inline] slab_alloc_node mm/slub.c:4217 [inline] __do_kmalloc_node mm/slub.c:4364 [inline] __kmalloc_noprof+0x305/0x4f0 mm/slub.c:4377 kmalloc_noprof include/linux/slab.h:909 [inline] kzalloc_noprof include/linux/slab.h:1039 [inline] lsm_blob_alloc security/security.c:684 [inline] lsm_cred_alloc security/security.c:701 [inline] security_prepare_creds+0x52/0x390 security/security.c:3271 prepare_kernel_cred+0x2ee/0x500 kernel/cred.c:617 call_usermodehelper_exec_async+0xd0/0x360 kernel/umh.c:88 ret_from_fork+0x3fc/0x770 arch/x86/kernel/process.c:148 ret_from_fork_asm+0x1a/0x30 arch/x86/entry/entry_64.S:245 page_owner free stack trace missing Memory state around the buggy address: ffff88801f1d4a80: fa fb fb fb fc fc fc fc 00 00 00 fc fc fc fc fc ffff88801f1d4b00: 00 00 00 fc fc fc fc fc fa fb fb fb fc fc fc fc >ffff88801f1d4b80: 00 00 00 fc fc fc fc fc fa fb fb fb fc fc fc fc ^ ffff88801f1d4c00: fa fb fb fb fc fc fc fc fa fb fb fb fc fc fc fc ffff88801f1d4c80: fa fb fb fb fc fc fc fc fa fb fb fb fc fc fc fc ================================================================== *** If these findings have caused you to resend the series or submit a separate fix, please add the following tag to your commit message: Tested-by: syzbot@syzkaller.appspotmail.com --- This report is generated by a bot. It may contain errors. syzbot ci engineers can be reached at syzkaller@googlegroups.com. ^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [syzbot ci] Re: bpf: replace path-sensitive with path-insensitive live stack analysis 2025-09-11 6:57 ` [syzbot ci] Re: bpf: replace path-sensitive with path-insensitive live stack analysis syzbot ci @ 2025-09-11 21:09 ` Eduard Zingerman 2025-09-11 21:58 ` Alexei Starovoitov 0 siblings, 1 reply; 22+ messages in thread From: Eduard Zingerman @ 2025-09-11 21:09 UTC (permalink / raw) To: syzbot ci, andrii, ast, bpf, daniel, kernel-team, martin.lau, yonghong.song Cc: syzbot, syzkaller-bugs On Wed, 2025-09-10 at 23:57 -0700, syzbot ci wrote: > syzbot ci has tested the following series > > [v1] bpf: replace path-sensitive with path-insensitive live stack analysis > https://lore.kernel.org/all/20250911010437.2779173-1-eddyz87@gmail.com > * [PATCH bpf-next v1 01/10] bpf: bpf_verifier_state->cleaned flag instead of REG_LIVE_DONE > * [PATCH bpf-next v1 02/10] bpf: use compute_live_registers() info in clean_func_state > * [PATCH bpf-next v1 03/10] bpf: remove redundant REG_LIVE_READ check in stacksafe() > * [PATCH bpf-next v1 04/10] bpf: declare a few utility functions as internal api > * [PATCH bpf-next v1 05/10] bpf: compute instructions postorder per subprogram > * [PATCH bpf-next v1 06/10] bpf: callchain sensitive stack liveness tracking using CFG > * [PATCH bpf-next v1 07/10] bpf: enable callchain sensitive stack liveness tracking > * [PATCH bpf-next v1 08/10] bpf: signal error if old liveness is more conservative than new > * [PATCH bpf-next v1 09/10] bpf: disable and remove registers chain based liveness > * [PATCH bpf-next v1 10/10] bpf: table based bpf_insn_successors() > > and found the following issue: > KASAN: slab-out-of-bounds Write in compute_postorder > > Full report is available here: > https://ci.syzbot.org/series/c42e236b-f40c-4d72-8ae7-da4e21c37e17 > > *** > > KASAN: slab-out-of-bounds Write in compute_postorder > > tree: bpf-next > URL: https://kernel.googlesource.com/pub/scm/linux/kernel/git/bpf/bpf-next.git > base: e12873ee856ffa6f104869b8ea10c0f741606f13 > arch: amd64 > compiler: Debian clang version 20.1.8 (++20250708063551+0c9f909b7976-1~exp1~20250708183702.136), Debian LLD 20.1.8 > config: https://ci.syzbot.org/builds/6d2bc952-3d65-4bcd-9a84-1207b810a1b5/config > C repro: https://ci.syzbot.org/findings/338e6ce4-7207-484f-a508-9b00b3121701/c_repro > syz repro: https://ci.syzbot.org/findings/338e6ce4-7207-484f-a508-9b00b3121701/syz_repro > > ================================================================== > BUG: KASAN: slab-out-of-bounds in compute_postorder+0x802/0xcb0 kernel/bpf/verifier.c:17840 > Write of size 4 at addr ffff88801f1d4b98 by task syz.0.17/5991 The error is caused by the following program: (e5) if r15 (null) 0xffffffff goto pc-1 <---- absence of DISCOVERED/EXPLORED mark here (71) r1 = *(u8 *)(r1 +70) leads to instruction being put on stack second time (85) call pc+2 (85) call bpf_get_numa_node_id#42 (95) exit (95) exit And this is the fix: --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -17840,6 +17840,7 @@ static int compute_postorder(struct bpf_verifier_env *env) stack_sz = 1; do { top = stack[stack_sz - 1]; + state[top] |= DISCOVERED; if (state[top] & EXPLORED) { postorder[cur_postorder++] = top; stack_sz--; [...] ^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [syzbot ci] Re: bpf: replace path-sensitive with path-insensitive live stack analysis 2025-09-11 21:09 ` Eduard Zingerman @ 2025-09-11 21:58 ` Alexei Starovoitov 2025-09-11 22:06 ` Eduard Zingerman 0 siblings, 1 reply; 22+ messages in thread From: Alexei Starovoitov @ 2025-09-11 21:58 UTC (permalink / raw) To: Eduard Zingerman Cc: syzbot ci, Andrii Nakryiko, Alexei Starovoitov, bpf, Daniel Borkmann, Kernel Team, Martin KaFai Lau, Yonghong Song, syzbot, syzkaller-bugs On Thu, Sep 11, 2025 at 2:09 PM Eduard Zingerman <eddyz87@gmail.com> wrote: > > On Wed, 2025-09-10 at 23:57 -0700, syzbot ci wrote: > > syzbot ci has tested the following series > > > > [v1] bpf: replace path-sensitive with path-insensitive live stack analysis > > https://lore.kernel.org/all/20250911010437.2779173-1-eddyz87@gmail.com > > * [PATCH bpf-next v1 01/10] bpf: bpf_verifier_state->cleaned flag instead of REG_LIVE_DONE > > * [PATCH bpf-next v1 02/10] bpf: use compute_live_registers() info in clean_func_state > > * [PATCH bpf-next v1 03/10] bpf: remove redundant REG_LIVE_READ check in stacksafe() > > * [PATCH bpf-next v1 04/10] bpf: declare a few utility functions as internal api > > * [PATCH bpf-next v1 05/10] bpf: compute instructions postorder per subprogram > > * [PATCH bpf-next v1 06/10] bpf: callchain sensitive stack liveness tracking using CFG > > * [PATCH bpf-next v1 07/10] bpf: enable callchain sensitive stack liveness tracking > > * [PATCH bpf-next v1 08/10] bpf: signal error if old liveness is more conservative than new > > * [PATCH bpf-next v1 09/10] bpf: disable and remove registers chain based liveness > > * [PATCH bpf-next v1 10/10] bpf: table based bpf_insn_successors() > > > > and found the following issue: > > KASAN: slab-out-of-bounds Write in compute_postorder > > > > Full report is available here: > > https://ci.syzbot.org/series/c42e236b-f40c-4d72-8ae7-da4e21c37e17 > > > > *** > > > > KASAN: slab-out-of-bounds Write in compute_postorder > > > > tree: bpf-next > > URL: https://kernel.googlesource.com/pub/scm/linux/kernel/git/bpf/bpf-next.git > > base: e12873ee856ffa6f104869b8ea10c0f741606f13 > > arch: amd64 > > compiler: Debian clang version 20.1.8 (++20250708063551+0c9f909b7976-1~exp1~20250708183702.136), Debian LLD 20.1.8 > > config: https://ci.syzbot.org/builds/6d2bc952-3d65-4bcd-9a84-1207b810a1b5/config > > C repro: https://ci.syzbot.org/findings/338e6ce4-7207-484f-a508-9b00b3121701/c_repro > > syz repro: https://ci.syzbot.org/findings/338e6ce4-7207-484f-a508-9b00b3121701/syz_repro > > > > ================================================================== > > BUG: KASAN: slab-out-of-bounds in compute_postorder+0x802/0xcb0 kernel/bpf/verifier.c:17840 > > Write of size 4 at addr ffff88801f1d4b98 by task syz.0.17/5991 > > The error is caused by the following program: > > (e5) if r15 (null) 0xffffffff goto pc-1 <---- absence of DISCOVERED/EXPLORED mark here (null) ? Is it jset again? but insn_successors() handles it already. Or pc-1 infinite loop caused it? but we have pc-1 selftest... > (71) r1 = *(u8 *)(r1 +70) leads to instruction being put on stack second time > (85) call pc+2 > (85) call bpf_get_numa_node_id#42 > (95) exit > (95) exit > > And this is the fix: > > --- a/kernel/bpf/verifier.c > +++ b/kernel/bpf/verifier.c > @@ -17840,6 +17840,7 @@ static int compute_postorder(struct bpf_verifier_env *env) > stack_sz = 1; > do { > top = stack[stack_sz - 1]; > + state[top] |= DISCOVERED; > if (state[top] & EXPLORED) { > postorder[cur_postorder++] = top; > stack_sz--; > [...] ^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [syzbot ci] Re: bpf: replace path-sensitive with path-insensitive live stack analysis 2025-09-11 21:58 ` Alexei Starovoitov @ 2025-09-11 22:06 ` Eduard Zingerman 2025-09-11 22:11 ` Alexei Starovoitov 0 siblings, 1 reply; 22+ messages in thread From: Eduard Zingerman @ 2025-09-11 22:06 UTC (permalink / raw) To: Alexei Starovoitov Cc: syzbot ci, Andrii Nakryiko, Alexei Starovoitov, bpf, Daniel Borkmann, Kernel Team, Martin KaFai Lau, Yonghong Song, syzbot, syzkaller-bugs On Thu, 2025-09-11 at 14:58 -0700, Alexei Starovoitov wrote: > On Thu, Sep 11, 2025 at 2:09 PM Eduard Zingerman <eddyz87@gmail.com> wrote: > > > > On Wed, 2025-09-10 at 23:57 -0700, syzbot ci wrote: > > > syzbot ci has tested the following series > > > > > > [v1] bpf: replace path-sensitive with path-insensitive live stack analysis > > > https://lore.kernel.org/all/20250911010437.2779173-1-eddyz87@gmail.com > > > * [PATCH bpf-next v1 01/10] bpf: bpf_verifier_state->cleaned flag instead of REG_LIVE_DONE > > > * [PATCH bpf-next v1 02/10] bpf: use compute_live_registers() info in clean_func_state > > > * [PATCH bpf-next v1 03/10] bpf: remove redundant REG_LIVE_READ check in stacksafe() > > > * [PATCH bpf-next v1 04/10] bpf: declare a few utility functions as internal api > > > * [PATCH bpf-next v1 05/10] bpf: compute instructions postorder per subprogram > > > * [PATCH bpf-next v1 06/10] bpf: callchain sensitive stack liveness tracking using CFG > > > * [PATCH bpf-next v1 07/10] bpf: enable callchain sensitive stack liveness tracking > > > * [PATCH bpf-next v1 08/10] bpf: signal error if old liveness is more conservative than new > > > * [PATCH bpf-next v1 09/10] bpf: disable and remove registers chain based liveness > > > * [PATCH bpf-next v1 10/10] bpf: table based bpf_insn_successors() > > > > > > and found the following issue: > > > KASAN: slab-out-of-bounds Write in compute_postorder > > > > > > Full report is available here: > > > https://ci.syzbot.org/series/c42e236b-f40c-4d72-8ae7-da4e21c37e17 > > > > > > *** > > > > > > KASAN: slab-out-of-bounds Write in compute_postorder > > > > > > tree: bpf-next > > > URL: https://kernel.googlesource.com/pub/scm/linux/kernel/git/bpf/bpf-next.git > > > base: e12873ee856ffa6f104869b8ea10c0f741606f13 > > > arch: amd64 > > > compiler: Debian clang version 20.1.8 (++20250708063551+0c9f909b7976-1~exp1~20250708183702.136), Debian LLD 20.1.8 > > > config: https://ci.syzbot.org/builds/6d2bc952-3d65-4bcd-9a84-1207b810a1b5/config > > > C repro: https://ci.syzbot.org/findings/338e6ce4-7207-484f-a508-9b00b3121701/c_repro > > > syz repro: https://ci.syzbot.org/findings/338e6ce4-7207-484f-a508-9b00b3121701/syz_repro > > > > > > ================================================================== > > > BUG: KASAN: slab-out-of-bounds in compute_postorder+0x802/0xcb0 kernel/bpf/verifier.c:17840 > > > Write of size 4 at addr ffff88801f1d4b98 by task syz.0.17/5991 > > > > The error is caused by the following program: > > > > (e5) if r15 (null) 0xffffffff goto pc-1 <---- absence of DISCOVERED/EXPLORED mark here > > (null) ? The `code` byte is 0xe5, BPF_OP(0xe5) == 0xe0, which is an invalid opcode. But opcodes are verified after check_cfg()/compute_postorder(). > Is it jset again? but insn_successors() handles it already. > Or pc-1 infinite loop caused it? > but we have pc-1 selftest... It's not infinite, but it causes instruction to be put twice on the stack array, and this array is allocated expecting max prog->len instructions. KASAN would only catch this error if program really needs to consume full stack depth during postorder construction, as far as I understand. [...] ^ permalink raw reply [flat|nested] 22+ messages in thread
* Re: [syzbot ci] Re: bpf: replace path-sensitive with path-insensitive live stack analysis 2025-09-11 22:06 ` Eduard Zingerman @ 2025-09-11 22:11 ` Alexei Starovoitov 0 siblings, 0 replies; 22+ messages in thread From: Alexei Starovoitov @ 2025-09-11 22:11 UTC (permalink / raw) To: Eduard Zingerman Cc: syzbot ci, Andrii Nakryiko, Alexei Starovoitov, bpf, Daniel Borkmann, Kernel Team, Martin KaFai Lau, Yonghong Song, syzbot, syzkaller-bugs On Thu, Sep 11, 2025 at 3:06 PM Eduard Zingerman <eddyz87@gmail.com> wrote: > > On Thu, 2025-09-11 at 14:58 -0700, Alexei Starovoitov wrote: > > On Thu, Sep 11, 2025 at 2:09 PM Eduard Zingerman <eddyz87@gmail.com> wrote: > > > > > > On Wed, 2025-09-10 at 23:57 -0700, syzbot ci wrote: > > > > syzbot ci has tested the following series > > > > > > > > [v1] bpf: replace path-sensitive with path-insensitive live stack analysis > > > > https://lore.kernel.org/all/20250911010437.2779173-1-eddyz87@gmail.com > > > > * [PATCH bpf-next v1 01/10] bpf: bpf_verifier_state->cleaned flag instead of REG_LIVE_DONE > > > > * [PATCH bpf-next v1 02/10] bpf: use compute_live_registers() info in clean_func_state > > > > * [PATCH bpf-next v1 03/10] bpf: remove redundant REG_LIVE_READ check in stacksafe() > > > > * [PATCH bpf-next v1 04/10] bpf: declare a few utility functions as internal api > > > > * [PATCH bpf-next v1 05/10] bpf: compute instructions postorder per subprogram > > > > * [PATCH bpf-next v1 06/10] bpf: callchain sensitive stack liveness tracking using CFG > > > > * [PATCH bpf-next v1 07/10] bpf: enable callchain sensitive stack liveness tracking > > > > * [PATCH bpf-next v1 08/10] bpf: signal error if old liveness is more conservative than new > > > > * [PATCH bpf-next v1 09/10] bpf: disable and remove registers chain based liveness > > > > * [PATCH bpf-next v1 10/10] bpf: table based bpf_insn_successors() > > > > > > > > and found the following issue: > > > > KASAN: slab-out-of-bounds Write in compute_postorder > > > > > > > > Full report is available here: > > > > https://ci.syzbot.org/series/c42e236b-f40c-4d72-8ae7-da4e21c37e17 > > > > > > > > *** > > > > > > > > KASAN: slab-out-of-bounds Write in compute_postorder > > > > > > > > tree: bpf-next > > > > URL: https://kernel.googlesource.com/pub/scm/linux/kernel/git/bpf/bpf-next.git > > > > base: e12873ee856ffa6f104869b8ea10c0f741606f13 > > > > arch: amd64 > > > > compiler: Debian clang version 20.1.8 (++20250708063551+0c9f909b7976-1~exp1~20250708183702.136), Debian LLD 20.1.8 > > > > config: https://ci.syzbot.org/builds/6d2bc952-3d65-4bcd-9a84-1207b810a1b5/config > > > > C repro: https://ci.syzbot.org/findings/338e6ce4-7207-484f-a508-9b00b3121701/c_repro > > > > syz repro: https://ci.syzbot.org/findings/338e6ce4-7207-484f-a508-9b00b3121701/syz_repro > > > > > > > > ================================================================== > > > > BUG: KASAN: slab-out-of-bounds in compute_postorder+0x802/0xcb0 kernel/bpf/verifier.c:17840 > > > > Write of size 4 at addr ffff88801f1d4b98 by task syz.0.17/5991 > > > > > > The error is caused by the following program: > > > > > > (e5) if r15 (null) 0xffffffff goto pc-1 <---- absence of DISCOVERED/EXPLORED mark here > > > > (null) ? > > The `code` byte is 0xe5, BPF_OP(0xe5) == 0xe0, which is an invalid > opcode. But opcodes are verified after check_cfg()/compute_postorder(). hmm. resolve_pseudo_ldimm64() -> bpf_opcode_in_insntable() does it before check_cfg. ^ permalink raw reply [flat|nested] 22+ messages in thread
end of thread, other threads:[~2025-09-12 16:48 UTC | newest] Thread overview: 22+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2025-09-11 1:04 [PATCH bpf-next v1 00/10] bpf: replace path-sensitive with path-insensitive live stack analysis Eduard Zingerman 2025-09-11 1:04 ` [PATCH bpf-next v1 01/10] bpf: bpf_verifier_state->cleaned flag instead of REG_LIVE_DONE Eduard Zingerman 2025-09-11 1:04 ` [PATCH bpf-next v1 02/10] bpf: use compute_live_registers() info in clean_func_state Eduard Zingerman 2025-09-11 1:04 ` [PATCH bpf-next v1 03/10] bpf: remove redundant REG_LIVE_READ check in stacksafe() Eduard Zingerman 2025-09-11 1:04 ` [PATCH bpf-next v1 04/10] bpf: declare a few utility functions as internal api Eduard Zingerman 2025-09-11 1:04 ` [PATCH bpf-next v1 05/10] bpf: compute instructions postorder per subprogram Eduard Zingerman 2025-09-11 1:04 ` [PATCH bpf-next v1 06/10] bpf: callchain sensitive stack liveness tracking using CFG Eduard Zingerman 2025-09-11 1:04 ` [PATCH bpf-next v1 07/10] bpf: enable callchain sensitive stack liveness tracking Eduard Zingerman 2025-09-11 1:04 ` [PATCH bpf-next v1 08/10] bpf: signal error if old liveness is more conservative than new Eduard Zingerman 2025-09-11 1:04 ` [PATCH bpf-next v1 09/10] bpf: disable and remove registers chain based liveness Eduard Zingerman 2025-09-11 14:19 ` kernel test robot 2025-09-11 21:26 ` Eduard Zingerman 2025-09-11 22:00 ` Alexei Starovoitov 2025-09-11 22:07 ` Eduard Zingerman 2025-09-12 8:17 ` Dan Carpenter 2025-09-12 16:48 ` Eduard Zingerman 2025-09-11 1:04 ` [PATCH bpf-next v1 10/10] bpf: table based bpf_insn_successors() Eduard Zingerman 2025-09-11 6:57 ` [syzbot ci] Re: bpf: replace path-sensitive with path-insensitive live stack analysis syzbot ci 2025-09-11 21:09 ` Eduard Zingerman 2025-09-11 21:58 ` Alexei Starovoitov 2025-09-11 22:06 ` Eduard Zingerman 2025-09-11 22:11 ` Alexei Starovoitov
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox