mirror of
https://github.com/torvalds/linux.git
synced 2026-04-18 06:44:00 -04:00
selftests/bpf: arg tracking for imprecise/multi-offset BPF_ST/STX
Add test cases for clear_stack_for_all_offs and dst_is_local_fp handling of multi-offset and ARG_IMPRECISE stack pointers: - st_imm_join_with_multi_off: BPF_ST through multi-offset dst should join at_stack with none instead of overwriting both candidate slots. - st_imm_join_with_imprecise_off: BPF_ST through offset-imprecise dst should join at_stack with none instead of clearing all slots. - st_imm_join_with_single_off: a canary checking that BPF_ST with a known offset overwrites slot instead of joining. - imprecise_dst_spill_join: BPF_STX through ARG_IMPRECISE dst should be recognized as a local spill and join at_stack with the written value. Signed-off-by: Eduard Zingerman <eddyz87@gmail.com> Link: https://lore.kernel.org/r/20260413-stacklive-fixes-v2-2-398e126e5cf3@gmail.com Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
committed by
Alexei Starovoitov
parent
ecdd4fd8a5
commit
d97cc8fc99
@@ -2647,3 +2647,196 @@ __naked void spill_join_with_imprecise_off(void)
|
||||
"exit;"
|
||||
::: __clobber_all);
|
||||
}
|
||||
|
||||
/*
|
||||
* Same as spill_join_with_multi_off but the write is BPF_ST (store
|
||||
* immediate) instead of BPF_STX. BPF_ST goes through
|
||||
* clear_stack_for_all_offs() rather than spill_to_stack(), and that
|
||||
* path also needs to join instead of overwriting.
|
||||
*
|
||||
* fp-8 = &fp-24
|
||||
* fp-16 = &fp-32
|
||||
* r1 = fp-8 or fp-16 (two offsets from branch)
|
||||
* *(u64 *)(r1 + 0) = 0 -- BPF_ST with immediate
|
||||
* r0 = *(u64 *)(r10 - 16) -- fill from fp-16
|
||||
* r0 = *(u64 *)(r0 + 0) -- deref: should produce use
|
||||
*/
|
||||
SEC("socket")
|
||||
__log_level(2)
|
||||
__failure
|
||||
__msg("15: (7a) *(u64 *)(r1 +0) = 0 fp-8: fp0-24 -> fp0-24|fp0+0 fp-16: fp0-32 -> fp0-32|fp0+0")
|
||||
__msg("17: (79) r0 = *(u64 *)(r0 +0) ; use: fp0-32")
|
||||
__naked void st_imm_join_with_multi_off(void)
|
||||
{
|
||||
asm volatile (
|
||||
"*(u64 *)(r10 - 24) = 0;"
|
||||
"*(u64 *)(r10 - 32) = 0;"
|
||||
"r1 = r10;"
|
||||
"r1 += -24;"
|
||||
"*(u64 *)(r10 - 8) = r1;"
|
||||
"r1 = r10;"
|
||||
"r1 += -32;"
|
||||
"*(u64 *)(r10 - 16) = r1;"
|
||||
/* create r1 with two candidate offsets: fp-8 or fp-16 */
|
||||
"call %[bpf_get_prandom_u32];"
|
||||
"if r0 == 0 goto 1f;"
|
||||
"r1 = r10;"
|
||||
"r1 += -8;"
|
||||
"goto 2f;"
|
||||
"1:"
|
||||
"r1 = r10;"
|
||||
"r1 += -16;"
|
||||
"2:"
|
||||
/* BPF_ST: store immediate through multi-offset r1 */
|
||||
"*(u64 *)(r1 + 0) = 0;"
|
||||
/* read back fp-16 and deref */
|
||||
"r0 = *(u64 *)(r10 - 16);"
|
||||
"r0 = *(u64 *)(r0 + 0);"
|
||||
"r0 = 0;"
|
||||
"exit;"
|
||||
:: __imm(bpf_get_prandom_u32)
|
||||
: __clobber_all);
|
||||
}
|
||||
|
||||
/*
|
||||
* Check that BPF_ST with a known offset fully overwrites stack slot
|
||||
* from the arg tracking point of view.
|
||||
*/
|
||||
SEC("socket")
|
||||
__log_level(2)
|
||||
__success
|
||||
__msg("5: (7a) *(u64 *)(r1 +0) = 0 fp-8: fp0-16 -> _{{$}}")
|
||||
__naked void st_imm_join_with_single_off(void)
|
||||
{
|
||||
asm volatile (
|
||||
"r2 = r10;"
|
||||
"r2 += -16;"
|
||||
"*(u64 *)(r10 - 8) = r2;"
|
||||
"r1 = r10;"
|
||||
"r1 += -8;"
|
||||
"*(u64 *)(r1 + 0) = 0;"
|
||||
"r0 = 0;"
|
||||
"exit;"
|
||||
::: __clobber_all);
|
||||
}
|
||||
|
||||
/*
|
||||
* Same as spill_join_with_imprecise_off but the write is BPF_ST.
|
||||
* Use "r2 = -8; r1 += r2" to make arg tracking lose offset
|
||||
* precision while the main verifier keeps r1 as fixed-offset.
|
||||
*
|
||||
* fp-8 = &fp-24
|
||||
* fp-16 = &fp-32
|
||||
* r1 = fp-8 (imprecise to arg tracking)
|
||||
* *(u64 *)(r1 + 0) = 0 -- BPF_ST with immediate
|
||||
* r0 = *(u64 *)(r10 - 16) -- fill from fp-16
|
||||
* r0 = *(u64 *)(r0 + 0) -- deref: should produce use
|
||||
*/
|
||||
SEC("socket")
|
||||
__log_level(2)
|
||||
__success
|
||||
__msg("13: (79) r0 = *(u64 *)(r0 +0) ; use: fp0-32")
|
||||
__naked void st_imm_join_with_imprecise_off(void)
|
||||
{
|
||||
asm volatile (
|
||||
"*(u64 *)(r10 - 24) = 0;"
|
||||
"*(u64 *)(r10 - 32) = 0;"
|
||||
"r1 = r10;"
|
||||
"r1 += -24;"
|
||||
"*(u64 *)(r10 - 8) = r1;"
|
||||
"r1 = r10;"
|
||||
"r1 += -32;"
|
||||
"*(u64 *)(r10 - 16) = r1;"
|
||||
/* r1 = fp-8 but arg tracking sees off_cnt == 0 */
|
||||
"r1 = r10;"
|
||||
"r2 = -8;"
|
||||
"r1 += r2;"
|
||||
/* store immediate through imprecise r1 */
|
||||
"*(u64 *)(r1 + 0) = 0;"
|
||||
/* read back fp-16 */
|
||||
"r0 = *(u64 *)(r10 - 16);"
|
||||
/* deref: should produce use */
|
||||
"r0 = *(u64 *)(r0 + 0);"
|
||||
"r0 = 0;"
|
||||
"exit;"
|
||||
::: __clobber_all);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test that spilling through an ARG_IMPRECISE pointer joins with
|
||||
* existing at_stack values. Subprog receives r1 = fp0-24 and
|
||||
* r2 = map_value, creates an ARG_IMPRECISE pointer by joining caller
|
||||
* and callee FP on two branches.
|
||||
*
|
||||
* Setup: callee spills &fp1-16 to fp1-8 (precise, tracked).
|
||||
* Then writes map_value through ARG_IMPRECISE r1 — on path A
|
||||
* this hits fp1-8, on path B it hits caller stack.
|
||||
* Since spill_to_stack is skipped for ARG_IMPRECISE dst,
|
||||
* fp1-8 tracking isn't joined with none.
|
||||
*
|
||||
* Expected after the imprecise write:
|
||||
* - arg tracking should show fp1-8 = fp1-16|fp1+0 (joined with none)
|
||||
* - read from fp1-8 and deref should produce use for fp1-16
|
||||
* - write through it should NOT produce def for fp1-16
|
||||
*/
|
||||
SEC("socket")
|
||||
__log_level(2)
|
||||
__success
|
||||
__msg("26: (79) r0 = *(u64 *)(r10 -8) // r1=IMP3 r6=fp0-24 r7=fp1-16 fp-8=fp1-16|fp1+0")
|
||||
__naked void imprecise_dst_spill_join(void)
|
||||
{
|
||||
asm volatile (
|
||||
"*(u64 *)(r10 - 24) = 0;"
|
||||
/* map lookup for a valid non-FP pointer */
|
||||
"*(u32 *)(r10 - 32) = 0;"
|
||||
"r1 = %[map] ll;"
|
||||
"r2 = r10;"
|
||||
"r2 += -32;"
|
||||
"call %[bpf_map_lookup_elem];"
|
||||
"if r0 == 0 goto 1f;"
|
||||
/* r1 = &caller_fp-24, r2 = map_value */
|
||||
"r1 = r10;"
|
||||
"r1 += -24;"
|
||||
"r2 = r0;"
|
||||
"call imprecise_dst_spill_join_sub;"
|
||||
"1:"
|
||||
"r0 = 0;"
|
||||
"exit;"
|
||||
:: __imm_addr(map),
|
||||
__imm(bpf_map_lookup_elem)
|
||||
: __clobber_all);
|
||||
}
|
||||
|
||||
static __used __naked void imprecise_dst_spill_join_sub(void)
|
||||
{
|
||||
asm volatile (
|
||||
/* r6 = &caller_fp-24 (frame=0), r8 = map_value */
|
||||
"r6 = r1;"
|
||||
"r8 = r2;"
|
||||
/* spill &fp1-16 to fp1-8: at_stack[0] = fp1-16 */
|
||||
"*(u64 *)(r10 - 16) = 0;"
|
||||
"r7 = r10;"
|
||||
"r7 += -16;"
|
||||
"*(u64 *)(r10 - 8) = r7;"
|
||||
/* branch to create ARG_IMPRECISE pointer */
|
||||
"call %[bpf_get_prandom_u32];"
|
||||
/* path B: r1 = caller fp-24 (frame=0) */
|
||||
"r1 = r6;"
|
||||
"if r0 == 0 goto 1f;"
|
||||
/* path A: r1 = callee fp-8 (frame=1) */
|
||||
"r1 = r10;"
|
||||
"r1 += -8;"
|
||||
"1:"
|
||||
/* r1 = ARG_IMPRECISE{mask=BIT(0)|BIT(1)}.
|
||||
* Write map_value (non-FP) through r1. On path A this overwrites fp1-8.
|
||||
* Should join at_stack[0] with none: fp1-16|fp1+0.
|
||||
*/
|
||||
"*(u64 *)(r1 + 0) = r8;"
|
||||
/* read fp1-8: should be fp1-16|fp1+0 (joined) */
|
||||
"r0 = *(u64 *)(r10 - 8);"
|
||||
"*(u64 *)(r0 + 0) = 42;"
|
||||
"r0 = 0;"
|
||||
"exit;"
|
||||
:: __imm(bpf_get_prandom_u32)
|
||||
: __clobber_all);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user