mirror of
https://github.com/torvalds/linux.git
synced 2026-11-06 21:58:01 +01:00
Merge branch 'bpf-fix-stack-argument-precision-propagation'
Xu Yunxiang says:
====================
bpf: Fix stack argument precision propagation
Outgoing stack arguments retain scalar state separately from registers
and ordinary spills. Their precision dependencies must reach ancestor
states when verification reuses a state or falls back to marking all
scalars precise. Otherwise, an out-of-bounds map access can reuse a state
validated for an in-bounds argument.
Patch 1 covers outgoing scalar argument slots in both precision paths.
Patch 2 adds two load-only asm regressions in verifier_stack_arg.c, based
on Alexei's examples.
Changes in v2:
- Keep the patch 1 fix and explanation unchanged; rebase onto bpf/master
at 03b0138974.
- Replace the standalone instruction/BTF test builder with two asm tests
in the existing verifier_stack_arg.c, as requested by Alexei.
- Reuse that file's compiler capability and architecture guards, which
already cover x86-64, arm64 and RV64. Drop the separate host-side guard.
- Rewrite patch 2's explanation for the two replacement tests.
v1:
https://lore.kernel.org/bpf/20261027061303.1754535-1-xyx2021@mail.ustc.edu.cn/
Review:
https://lore.kernel.org/bpf/DMJI3GWXQOC9.UA3K9X139AR@gmail.com/
Validation on x86-64 with Clang 23.1.4, JIT enabled and panic_on_warn=1:
- Built all four kernel variants and their BPF test modules, plus the
complete test_progs runner with BPF_STRICT_BUILD=1.
- The two new tests distinguish the two precision paths:
State reuse Fallback
Unmodified baseline wrongly accepts wrongly accepts
Propagation fix only rejects wrongly accepts
Fallback fix only wrongly accepts rejects
Complete fix rejects rejects
Rejections report the intended map-value bounds violation. The two
corresponding in-bounds controls load successfully on all four kernels.
These programs are never attached or executed.
- All 274 subtests passed, with no skips, across verifier_precision,
verifier_spill_fill, verifier_subprog_precision, verifier_scalar_ids,
stack_arg_precision, verifier_stack_arg, verifier_stack_arg_order and
iters on the complete candidate. No kernel WARN/Oops or panic occurred.
- Compiled the test object under x86-64, arm64 and RV64 guard definitions;
Clang 20 correctly selects the existing unsupported-compiler dummy.
Runtime testing was x86-64 only, and the unfiltered suite was not run.
Codex assisted with implementation, test integration, commit messages
and validation. The v2 test code was supplied by Alexei and is used
unchanged.
====================
Link: https://patch.msgid.link/20261102190350.788433-1-xyx2021@mail.ustc.edu.cn
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
commit
38bce81d47
|
|
@ -746,6 +746,17 @@ void bpf_mark_all_scalars_precise(struct bpf_verifier_env *env,
|
|||
i, -(j + 1) * 8);
|
||||
}
|
||||
}
|
||||
for (j = 0; j < func->out_stack_arg_cnt; j++) {
|
||||
reg = &func->stack_arg_regs[j];
|
||||
if (reg->type != SCALAR_VALUE || reg->precise)
|
||||
continue;
|
||||
reg->precise = true;
|
||||
if (env->log.level & BPF_LOG_LEVEL2) {
|
||||
verbose(env,
|
||||
"force_precise: frame%d: forcing r11%d to be precise\n",
|
||||
i, -(j + 1) * BPF_REG_SIZE);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1116,6 +1116,20 @@ static int propagate_precision(struct bpf_verifier_env *env,
|
|||
bpf_bt_set_frame_slot(&env->bt, fr, i);
|
||||
first = false;
|
||||
}
|
||||
for (i = 0; i < state->out_stack_arg_cnt; i++) {
|
||||
state_reg = &state->stack_arg_regs[i];
|
||||
if (state_reg->type != SCALAR_VALUE || !state_reg->precise)
|
||||
continue;
|
||||
if (env->log.level & BPF_LOG_LEVEL2) {
|
||||
if (first)
|
||||
verbose(env, "frame %d: propagating r11%d",
|
||||
fr, -(i + 1) * BPF_REG_SIZE);
|
||||
else
|
||||
verbose(env, ",r11%d", -(i + 1) * BPF_REG_SIZE);
|
||||
}
|
||||
bt_set_frame_stack_arg_slot(&env->bt, fr, i);
|
||||
first = false;
|
||||
}
|
||||
if (!first && (env->log.level & BPF_LOG_LEVEL2))
|
||||
verbose(env, "\n");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -552,6 +552,121 @@ __naked void stack_arg_may_goto_in_caller(void)
|
|||
);
|
||||
}
|
||||
|
||||
/* arg6 is an offset into the value of map_hash_8b. 0-7 are in bounds. */
|
||||
__naked __noinline __used
|
||||
static long arg6_is_map_value_off(long a, long b, long c, long d, long e, long off)
|
||||
{
|
||||
asm volatile (
|
||||
"r6 = *(u64 *)(r11 + 8);"
|
||||
"r1 = 0;"
|
||||
"*(u64 *)(r10 - 8) = r1;"
|
||||
"r2 = r10;"
|
||||
"r2 += -8;"
|
||||
"r1 = %[map_hash_8b] ll;"
|
||||
"call %[bpf_map_lookup_elem];"
|
||||
"if r0 == 0 goto l0_%=;"
|
||||
"r0 += r6;"
|
||||
"r0 = *(u8 *)(r0 + 0);"
|
||||
"l0_%=:"
|
||||
"exit;"
|
||||
:
|
||||
: __imm(bpf_map_lookup_elem),
|
||||
__imm_addr(map_hash_8b)
|
||||
: __clobber_all
|
||||
);
|
||||
}
|
||||
|
||||
SEC("tc")
|
||||
__description("stack_arg: a pruned state propagates precision of arg6")
|
||||
__failure __msg("invalid access to map value")
|
||||
__flag(BPF_F_TEST_STATE_FREQ)
|
||||
__naked void stack_arg_precision_pruned_state(void)
|
||||
{
|
||||
asm volatile (
|
||||
"call %[bpf_get_prandom_u32];"
|
||||
"if r0 != 0 goto l0_%=;"
|
||||
/* Path 1: arg6 = 0. It makes arg6 precise at l3. */
|
||||
"*(u64 *)(r11 - 8) = 0;"
|
||||
"goto l3_%=;"
|
||||
"l0_%=:"
|
||||
"if r0 == 1 goto l1_%=;"
|
||||
/* Path 2: arg6 = r6 = 0. Pruned at l3. It has to make r6 precise at l2. */
|
||||
"r6 = 0;"
|
||||
"goto l2_%=;"
|
||||
"l1_%=:"
|
||||
/* Path 3: arg6 = r6 = 8. Out of bounds. */
|
||||
"r6 = 8;"
|
||||
"l2_%=:"
|
||||
"*(u64 *)(r11 - 8) = r6;"
|
||||
"l3_%=:"
|
||||
"r1 = 0;"
|
||||
"r2 = 0;"
|
||||
"r3 = 0;"
|
||||
"r4 = 0;"
|
||||
"r5 = 0;"
|
||||
"call arg6_is_map_value_off;"
|
||||
"exit;"
|
||||
:: __imm(bpf_get_prandom_u32)
|
||||
: __clobber_all
|
||||
);
|
||||
}
|
||||
|
||||
/* The same, but backtracking of r1 sees a store through r1 and marks all scalars precise. */
|
||||
__naked __noinline __used
|
||||
static long arg6_is_map_value_off_fallback(long a, long b, long c, long d, long e, long off)
|
||||
{
|
||||
asm volatile (
|
||||
"r6 = *(u64 *)(r11 + 8);"
|
||||
"r1 = 0;"
|
||||
"*(u64 *)(r10 - 8) = r1;"
|
||||
"r2 = r10;"
|
||||
"r2 += -8;"
|
||||
"r1 = %[map_hash_8b] ll;"
|
||||
"call %[bpf_map_lookup_elem];"
|
||||
"if r0 == 0 goto l0_%=;"
|
||||
"r1 = r0;"
|
||||
"*(u8 *)(r1 + 0) = 0;"
|
||||
"r1 -= r0;"
|
||||
"r1 &= 0;"
|
||||
"r1 += r6;"
|
||||
"r0 += r1;"
|
||||
"r0 = *(u8 *)(r0 + 0);"
|
||||
"l0_%=:"
|
||||
"exit;"
|
||||
:
|
||||
: __imm(bpf_map_lookup_elem),
|
||||
__imm_addr(map_hash_8b)
|
||||
: __clobber_all
|
||||
);
|
||||
}
|
||||
|
||||
SEC("tc")
|
||||
__description("stack_arg: forcing all scalars precise covers arg6")
|
||||
__failure __msg("invalid access to map value")
|
||||
__flag(BPF_F_TEST_STATE_FREQ)
|
||||
__naked void stack_arg_precision_fallback(void)
|
||||
{
|
||||
asm volatile (
|
||||
"call %[bpf_get_prandom_u32];"
|
||||
"if r0 != 0 goto l0_%=;"
|
||||
"*(u64 *)(r11 - 8) = 0;"
|
||||
"goto l1_%=;"
|
||||
"l0_%=:"
|
||||
/* Out of bounds. Must not be pruned at l1. */
|
||||
"*(u64 *)(r11 - 8) = 8;"
|
||||
"l1_%=:"
|
||||
"r1 = 0;"
|
||||
"r2 = 0;"
|
||||
"r3 = 0;"
|
||||
"r4 = 0;"
|
||||
"r5 = 0;"
|
||||
"call arg6_is_map_value_off_fallback;"
|
||||
"exit;"
|
||||
:: __imm(bpf_get_prandom_u32)
|
||||
: __clobber_all
|
||||
);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
SEC("socket")
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user