Variable-offset stack reads can read back bytes that belong to a previously spilled scalar constant zero. Today mark_reg_stack_read() only treats STACK_ZERO bytes as known zero bytes, so the destination register becomes unknown for such reads even though every byte in the read range is known to be zero. This can lead to rejecting otherwise valid programs once the loaded byte is used as a pointer offset. This pattern is not limited to hand-written verifier tests. clang 22.1.6 at -O2 and -O3 emits it from a small helper-based BPF C reproducer; the resulting program is rejected before this change and accepted afterwards. No deployed-program regression is currently known, so target bpf-next rather than bpf. Teach the variable-offset stack read path to also consider STACK_SPILL bytes backed by a spilled scalar constant zero as zero bytes. When a zero result depends on such a spill, mark the contributing stack slots precise before accepting the const-zero result so pruning cannot reuse a zero-spill state for a later non-zero spill state. Keep fixed-offset stack reads out of this eager precision marking path, as the new behaviour is only needed when filling a register from a variable-offset stack byte range. Assisted-by: opencode:gpt-5.5 Signed-off-by: Woojin Ji --- include/linux/bpf_verifier.h | 5 ++++ kernel/bpf/verifier.c | 55 ++++++++++++++++++++++++++++++++++++-------- 2 files changed, 50 insertions(+), 10 deletions(-) diff --git a/include/linux/bpf_verifier.h b/include/linux/bpf_verifier.h index d57b339a8..c2e665044 100644 --- a/include/linux/bpf_verifier.h +++ b/include/linux/bpf_verifier.h @@ -1242,6 +1242,11 @@ static inline void bpf_bt_set_frame_slot(struct backtrack_state *bt, u32 frame, bt->stack_masks[frame] |= 1ull << slot; } +static inline void bpf_bt_set_frame_slot_mask(struct backtrack_state *bt, u32 frame, u64 mask) +{ + bt->stack_masks[frame] |= mask; +} + static inline void bt_set_frame_stack_arg_slot(struct backtrack_state *bt, u32 frame, u32 slot) { bt->stack_arg_masks[frame] |= 1 << slot; diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index eb46a81a8..46cadb83f 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -3696,14 +3696,23 @@ static int check_stack_write_var_off(struct bpf_verifier_env *env, * SCALAR. This function does not deal with register filling; the caller must * ensure that all spilled registers in the stack range have been marked as * read. + * + * If requested, STACK_SPILL bytes backed by spilled scalar const zeroes are + * also considered zero bytes. In that case, mark the contributing stack slots + * precise so pruning cannot reuse a zero-spill state for a later non-zero + * spill state. + * + * Returns an error if precision backtracking fails. */ -static void mark_reg_stack_read(struct bpf_verifier_env *env, - /* func where src register points to */ - struct bpf_func_state *ptr_state, - int min_off, int max_off, int dst_regno) +static int mark_reg_stack_read(struct bpf_verifier_env *env, + /* func where src register points to */ + struct bpf_func_state *ptr_state, + int min_off, int max_off, int dst_regno, + bool mark_zero_spills) { struct bpf_verifier_state *vstate = env->cur_state; struct bpf_func_state *state = vstate->frame[vstate->curframe]; + u64 zero_spill_mask = 0; int i, slot, spi; u8 *stype; int zeros = 0; @@ -3713,19 +3722,37 @@ static void mark_reg_stack_read(struct bpf_verifier_env *env, spi = slot / BPF_REG_SIZE; mark_stack_slot_scratched(env, spi); stype = ptr_state->stack[spi].slot_type; - if (stype[slot % BPF_REG_SIZE] != STACK_ZERO) - break; - zeros++; + if (stype[slot % BPF_REG_SIZE] == STACK_ZERO) { + zeros++; + continue; + } + if (mark_zero_spills && + stype[slot % BPF_REG_SIZE] == STACK_SPILL && + bpf_is_spilled_scalar_reg(&ptr_state->stack[spi]) && + tnum_is_const(ptr_state->stack[spi].spilled_ptr.var_off) && + ptr_state->stack[spi].spilled_ptr.var_off.value == 0) { + zero_spill_mask |= 1ull << spi; + zeros++; + continue; + } + break; } if (zeros == max_off - min_off) { /* Any access_size read into register is zero extended, * so the whole register == const_zero. */ __mark_reg_const_zero(env, &state->regs[dst_regno]); + if (zero_spill_mask) { + bpf_bt_set_frame_slot_mask(&env->bt, ptr_state->frameno, + zero_spill_mask); + return mark_chain_precision_batch(env, env->cur_state); + } } else { /* have read misc data from the stack */ mark_reg_unknown(env, state->regs, dst_regno); } + + return 0; } /* Read the stack at 'off' and put the results into the register indicated by @@ -3747,6 +3774,7 @@ static int check_stack_read_fixed_off(struct bpf_verifier_env *env, int i, slot = -off - 1, spi = slot / BPF_REG_SIZE; struct bpf_reg_state *reg; u8 *stype, type; + int err; int insn_flags = INSN_F_STACK_ACCESS; int hist_spi = spi, hist_frame = reg_state->frameno; @@ -3874,8 +3902,12 @@ static int check_stack_read_fixed_off(struct bpf_verifier_env *env, } return -EACCES; } - if (dst_regno >= 0) - mark_reg_stack_read(env, reg_state, off, off + size, dst_regno); + if (dst_regno >= 0) { + err = mark_reg_stack_read(env, reg_state, off, off + size, + dst_regno, false); + if (err) + return err; + } insn_flags = 0; /* we are not restoring spilled register */ } if (insn_flags) @@ -3929,7 +3961,10 @@ static int check_stack_read_var_off(struct bpf_verifier_env *env, struct bpf_reg min_off = reg_smin(reg) + off; max_off = reg_smax(reg) + off; - mark_reg_stack_read(env, ptr_state, min_off, max_off + size, dst_regno); + err = mark_reg_stack_read(env, ptr_state, min_off, max_off + size, + dst_regno, true); + if (err) + return err; check_fastcall_stack_contract(env, ptr_state, env->insn_idx, min_off); return 0; } -- 2.54.0