On Mon, 2023-12-04 at 16:23 -0800, Andrii Nakryiko wrote: [...] > > > @@ -4431,7 +4431,7 @@ static int check_stack_write_fixed_off(struct bpf_verifier_env *env, > > > * so it's aligned access and [off, off + size) are within stack limits > > > */ > > > if (!env->allow_ptr_leaks && > > > - state->stack[spi].slot_type[0] == STACK_SPILL && > > > + is_spilled_reg(&state->stack[spi]) && > > > size != BPF_REG_SIZE) { > > > verbose(env, "attempt to corrupt spilled pointer on stack\n"); > > > return -EACCES; > > > > I think there is a small detail here. > > slot_type[0] == STACK_SPILL actually checks if a spill is 64-bit. > > Hm... I wonder if the check was written like this deliberately to > prevent turning any spilled register into STACK_MISC? idk, the error is about pointers and forbidding turning pointers to STACK_MISC makes sense. Don't see why it would be useful to forbid this for scalars. > > Thus, with this patch applied the test below does not pass. > > Log fragment: > > > > 1: (57) r0 &= 65535 ; R0_w=scalar(...,var_off=(0x0; 0xffff)) > > 2: (63) *(u32 *)(r10 -8) = r0 > > 3: R0_w=scalar(...,var_off=(0x0; 0xffff)) R10=fp0 fp-8=mmmmscalar(...,var_off=(0x0; 0xffff)) > > 3: (b7) r0 = 42 ; R0_w=42 > > 4: (63) *(u32 *)(r10 -4) = r0 > > attempt to corrupt spilled pointer on stack > > What would happen if we have > > 4: *(u16 *)(r10 - 8) = 123; ? w/o this patch: 0: (85) call bpf_get_prandom_u32#7 ; R0_w=scalar() 1: (57) r0 &= 65535 ; R0_w=scalar(...,var_off=(0x0; 0xffff)) 2: (63) *(u32 *)(r10 -8) = r0 ; R0_w=scalar(...,var_off=(0x0; 0xffff)) R10=fp0 fp-8=mmmmscalar(...,var_off=(0x0; 0xffff)) 3: (b7) r0 = 123 ; R0_w=123 4: (6b) *(u16 *)(r10 -8) = r0 ; R0_w=123 R10=fp0 fp-8=mmmmmm123 5: (95) exit with this patch: 0: (85) call bpf_get_prandom_u32#7 ; R0_w=scalar() 1: (57) r0 &= 65535 ; R0_w=scalar(...,var_off=(0x0; 0xffff)) 2: (63) *(u32 *)(r10 -8) = r0 ; R0_w=scalar(...,var_off=(0x0; 0xffff)) R10=fp0 fp-8=mmmmscalar(...,var_off=(0x0; 0xffff)) 3: (b7) r0 = 123 ; R0_w=123 4: (6b) *(u16 *)(r10 -8) = r0 attempt to corrupt spilled pointer on stack > and similarly > > 4: *(u16 *)(r10 - 6) = 123; ? w/o this patch: 0: (85) call bpf_get_prandom_u32#7 ; R0_w=scalar() 1: (57) r0 &= 65535 ; R0_w=scalar(...,var_off=(0x0; 0xffff)) 2: (63) *(u32 *)(r10 -8) = r0 ; R0_w=scalar(....,var_off=(0x0; 0xffff)) R10=fp0 fp-8=mmmmscalar(...,var_off=(0x0; 0xffff)) 3: (b7) r0 = 123 ; R0_w=123 4: (6b) *(u16 *)(r10 -6) = r0 ; R0_w=123 R10=fp0 fp-8=mmmmmmmm 5: (95) exit with this patch: 0: (85) call bpf_get_prandom_u32#7 ; R0_w=scalar() 1: (57) r0 &= 65535 ; R0_w=scalar(...,var_off=(0x0; 0xffff)) 2: (63) *(u32 *)(r10 -8) = r0 ; R0_w=scalar(...,var_off=(0x0; 0xffff)) R10=fp0 fp-8=mmmmscalar(...,var_off=(0x0; 0xffff)) 3: (b7) r0 = 123 ; R0_w=123 4: (6b) *(u16 *)(r10 -6) = r0 attempt to corrupt spilled pointer on stack > So it makes me feel like the intent was to reject any partial writes > with spilled reg slots. We could probably improve that to just make > sure that we don't turn spilled pointers into STACK_MISC in unpriv, > but I'm not sure if it's worth doing that instead of keeping things > simple? You mean like below? if (!env->allow_ptr_leaks && is_spilled_reg(&state->stack[spi]) && is_spillable_regtype(state->stack[spi].spilled_ptr.type) && size != BPF_REG_SIZE) { verbose(env, "attempt to corrupt spilled pointer on stack\n"); return -EACCES; }