]> git.ipfire.org Git - thirdparty/kernel/stable.git/commitdiff
bpf: enable callchain sensitive stack liveness tracking
authorEduard Zingerman <eddyz87@gmail.com>
Fri, 19 Sep 2025 02:18:40 +0000 (19:18 -0700)
committerAlexei Starovoitov <ast@kernel.org>
Fri, 19 Sep 2025 16:27:23 +0000 (09:27 -0700)
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>
Link: https://lore.kernel.org/r/20250918-callchain-sensitive-liveness-v3-7-c3cd27bacc60@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
kernel/bpf/verifier.c

index dc8d26dc9bf192d1d1fce89d7a4dbf91b69aa51c..bb931a144b95324990eda5ce783db8c0463ce3aa 100644 (file)
@@ -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;
@@ -3634,6 +3641,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;
@@ -5166,6 +5176,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) {
@@ -5435,12 +5457,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 = &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(&reg_state->stack[spi])) {
                u8 spill_size = 1;
@@ -8174,6 +8200,9 @@ mark:
                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
@@ -10735,6 +10764,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);
@@ -18532,7 +18563,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++) {
@@ -18545,9 +18575,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;
@@ -18560,6 +18588,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);
@@ -18645,9 +18674,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 */
@@ -19886,6 +19912,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)
@@ -20071,7 +20100,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;
@@ -20164,7 +20193,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.
@@ -20203,6 +20240,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,
@@ -24769,6 +24809,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;
@@ -24918,6 +24962,7 @@ err_unlock:
                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);