A spill narrower than its source stores the source's low 32 bits, so the slot is their zero-extension -- the same relation a 32-bit mov records, in the store direction: r6 = ... /* full 64-bit unknown */ *(u32 *)(r10 - 8) = r6; /* slot holds r6's low 32 bits */ r2 = *(u32 *)(r10 - 8); if w6 != 0 goto ... /* not taken: r6's low 32 bits are 0 */ if r2 == 0 goto ... /* not deduced today */ Record a low-32 link instead of dropping the relation. A store never sign-extends, so SUBREG_ZEXT is the only kind that arises here and no is_ldsx equivalent is needed. This composes with the fill side rather than stacking on it. The previous patch left the slot recording exactly what memory holds, so its width is already within 32 bits and the fill sees an ordinary narrow source: the destination copies the slot's descriptor, staying one hop from the same base rather than becoming a link to a link. spill_subregs_preserve_stack_zero picks up the id on its 4-byte spill. The 16- and 8-bit spills in the same program do not, which is the size gate working: a link can only describe the low 32 bits. Signed-off-by: Vineet Gupta --- v2: new, with 11/13. kernel/bpf/verifier.c | 22 ++++++++++++++++--- .../selftests/bpf/progs/verifier_spill_fill.c | 2 +- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c index 6cb35fc0d0fb..9e63eddb9023 100644 --- a/kernel/bpf/verifier.c +++ b/kernel/bpf/verifier.c @@ -3665,10 +3665,19 @@ static int check_stack_write_fixed_off(struct bpf_verifier_env *env, mark_stack_slot_scratched(env, spi); if (reg && !(off % BPF_REG_SIZE) && reg->type == SCALAR_VALUE && env->bpf_capable) { bool reg_value_fits; + bool subreg_link; reg_value_fits = get_reg_width(reg) <= BITS_PER_BYTE * size; + /* + * A narrowing spill stores the low 32 bits of the source, so + * the slot is their zero-extension: record a low-32 link + * rather than dropping the relation, as a 32-bit mov does. + * A store never sign-extends, so there is only one kind here. + */ + subreg_link = !reg_value_fits && size == 4; + /* Make sure that reg had an ID to build a relation on spill. */ - if (reg_value_fits) + if (reg_value_fits || subreg_link) assign_scalar_id_before_mov(env, reg); save_register_state(env, state, spi, reg, size); if (!reg_value_fits) { @@ -3678,8 +3687,15 @@ static int check_stack_write_fixed_off(struct bpf_verifier_env *env, * it came from. */ coerce_reg_to_size(&state->stack[spi].spilled_ptr, size); - /* Break the relation on a narrowing spill. */ - clear_scalar_id(&state->stack[spi].spilled_ptr); + if (subreg_link && reg->id) + state->stack[spi].spilled_ptr.subreg = SUBREG_ZEXT; + else + /* + * Nothing to relate: either the source has no + * id to share, or the store is narrower than + * the 32 bits a link can describe. + */ + clear_scalar_id(&state->stack[spi].spilled_ptr); } } else if (!reg && !(off % BPF_REG_SIZE) && is_bpf_st_mem(insn) && env->bpf_capable) { diff --git a/tools/testing/selftests/bpf/progs/verifier_spill_fill.c b/tools/testing/selftests/bpf/progs/verifier_spill_fill.c index 487e0a1f395c..1042856b97c2 100644 --- a/tools/testing/selftests/bpf/progs/verifier_spill_fill.c +++ b/tools/testing/selftests/bpf/progs/verifier_spill_fill.c @@ -468,7 +468,7 @@ __success * The slot records what the store put there, not the wider source it came * from, so each scalar is bounded by the size of its spill. */ -__msg("fp-8=0m??scalar(smin=0,smax=umax=0xffffffff,var_off=(0x0; 0xffffffff))") +__msg("fp-8=0m??scalar(id={{[0-9]+}}.lo32,smin=0,smax=umax=0xffffffff,var_off=(0x0; 0xffffffff))") __msg("fp-16=00mm??scalar(smin=smin32=0,smax=umax=smax32=umax32=0xffff,var_off=(0x0; 0xffff))") __msg("fp-24=00mm???scalar(smin=smin32=0,smax=umax=smax32=umax32=255,var_off=(0x0; 0xff))") __naked void spill_subregs_preserve_stack_zero(void) -- 2.53.0-Meta