mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
bpf: Derive global subprog stack liveness from BTF signatures
Stack liveness currently analyzes global callees in the context of their callers. The resulting read/write marks describe only the current body, but freplace can replace it after the caller has been verified. A no-op callee taking a stack pointer can be replaced with one writing through that pointer, leaving the call's may_write marks incomplete. SCEV could then preserve a stack counter recurrence across a call that resets it, invalidating a loop termination proof. Instead, derive global call effects from BTF argument types instead, same is it done on the main verification pass. Signed-off-by: Eduard Zingerman <eddyz87@gmail.com> Signed-off-by: Alexei Starovoitov <ast@kernel.org> Link: https://patch.msgid.link/20261009-scev-minimal-rebase-v3-10-bcba9bc53ac9@gmail.com
This commit is contained in:
parent
13cffb8831
commit
8212c1c298
|
|
@ -1744,6 +1744,9 @@ bpf_helper_stack_access_bytes(struct bpf_verifier_env *env,
|
|||
struct arg_access_info
|
||||
bpf_kfunc_stack_access_bytes(struct bpf_verifier_env *env,
|
||||
struct bpf_insn *insn, int arg, int insn_idx);
|
||||
struct arg_access_info
|
||||
bpf_global_subprog_stack_access_bytes(struct bpf_verifier_env *env,
|
||||
struct bpf_insn *insn, int arg, int insn_idx);
|
||||
int bpf_compute_subprog_arg_access(struct bpf_verifier_env *env);
|
||||
|
||||
int bpf_stack_liveness_init(struct bpf_verifier_env *env);
|
||||
|
|
|
|||
|
|
@ -1715,6 +1715,8 @@ static int record_arg_access(struct bpf_verifier_env *env,
|
|||
info = bpf_helper_stack_access_bytes(env, insn, arg_idx, insn_idx);
|
||||
} else if (bpf_pseudo_kfunc_call(insn)) {
|
||||
info = bpf_kfunc_stack_access_bytes(env, insn, arg_idx, insn_idx);
|
||||
} else if (bpf_pseudo_call(insn)) {
|
||||
info = bpf_global_subprog_stack_access_bytes(env, insn, arg_idx, insn_idx);
|
||||
} else {
|
||||
for (int f = 0; f <= depth; f++) {
|
||||
err = mark_stack_read_all(env, instance, f, insn_idx);
|
||||
|
|
@ -1736,7 +1738,7 @@ static int record_arg_access(struct bpf_verifier_env *env,
|
|||
return err;
|
||||
}
|
||||
|
||||
/* Record stack access for a given 'at' state of helper/kfunc 'insn' */
|
||||
/* Record stack access for a given 'at' state of helper/kfunc/global subprog 'insn' */
|
||||
static int record_call_access(struct bpf_verifier_env *env,
|
||||
struct func_instance *instance,
|
||||
struct arg_track *at,
|
||||
|
|
@ -1744,12 +1746,19 @@ static int record_call_access(struct bpf_verifier_env *env,
|
|||
{
|
||||
struct bpf_insn *insn = &env->prog->insnsi[insn_idx];
|
||||
struct bpf_call_summary cs;
|
||||
int r, err, arg_slot_cnt = 5;
|
||||
int r, err, callee, arg_slot_cnt = 5;
|
||||
|
||||
if (bpf_pseudo_call(insn))
|
||||
return 0;
|
||||
|
||||
if (bpf_is_callx(insn))
|
||||
if (bpf_pseudo_call(insn)) {
|
||||
/*
|
||||
* analyze_subprog() handles static callees. For global callees,
|
||||
* derive effects from argument types so may_write marks remain
|
||||
* conservative under freplace.
|
||||
*/
|
||||
callee = bpf_find_subprog(env, insn_idx + insn->imm + 1);
|
||||
if (callee < 0 || !bpf_subprog_is_global(env, callee))
|
||||
return 0;
|
||||
arg_slot_cnt = MAX_BPF_FUNC_ARGS;
|
||||
} else if (bpf_is_callx(insn))
|
||||
/*
|
||||
* The callee is not known statically. Assume that all arg
|
||||
* slots are passed and let record_arg_access() conservatively
|
||||
|
|
@ -2282,7 +2291,12 @@ static int analyze_subprog(struct bpf_verifier_env *env,
|
|||
if (bpf_pseudo_call(insn)) {
|
||||
target = idx + insn->imm + 1;
|
||||
callee = bpf_find_subprog(env, target);
|
||||
if (callee < 0)
|
||||
/*
|
||||
* Global subprograms can be freplaced, so conservatively derive
|
||||
* their stack effects from their BTF signatures instead of
|
||||
* analyzing them in the context of their callers.
|
||||
*/
|
||||
if (callee < 0 || bpf_subprog_is_global(env, callee))
|
||||
continue;
|
||||
|
||||
/* Build entry args: R1-R5 and stack args from at_in at call site */
|
||||
|
|
|
|||
|
|
@ -14571,6 +14571,72 @@ bpf_kfunc_stack_access_bytes(struct bpf_verifier_env *env, struct bpf_insn *insn
|
|||
return info;
|
||||
}
|
||||
|
||||
/*
|
||||
* Called before dead code elimination, some subprograms may be genuinely unreachable.
|
||||
* Suppress diagnostics so callers can use a conservative result on error.
|
||||
* The main pass would report errors for reachable subprograms.
|
||||
*/
|
||||
static bool try_btf_prepare_func_args(struct bpf_verifier_env *env, int subprog)
|
||||
{
|
||||
struct bpf_prog *prog = env->prog;
|
||||
u32 log_level = env->log.level;
|
||||
int err;
|
||||
|
||||
if (!prog->aux->func_info || !prog->aux->func_info[subprog].type_id)
|
||||
return false;
|
||||
|
||||
env->log.level = 0;
|
||||
err = btf_prepare_func_args(env, subprog);
|
||||
env->log.level = log_level;
|
||||
return !err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Derive stack effects for a global subprog's argument slot @arg (0-based)
|
||||
* from its declared type, matching the main pass's argument checks.
|
||||
*/
|
||||
struct arg_access_info
|
||||
bpf_global_subprog_stack_access_bytes(struct bpf_verifier_env *env, struct bpf_insn *insn,
|
||||
int arg, int insn_idx)
|
||||
{
|
||||
struct arg_access_info info = {
|
||||
.size = U32_MAX,
|
||||
.may_read = true,
|
||||
.may_write = true,
|
||||
};
|
||||
struct bpf_subprog_info *sub;
|
||||
int callee;
|
||||
|
||||
callee = bpf_find_subprog(env, insn_idx + insn->imm + 1);
|
||||
if (callee < 0)
|
||||
return info;
|
||||
sub = subprog_info(env, callee);
|
||||
if (!try_btf_prepare_func_args(env, callee))
|
||||
return info;
|
||||
|
||||
switch (base_type(sub->args[arg].arg_type)) {
|
||||
case ARG_UNUSED: /* Not an argument slot. */
|
||||
case ARG_ANYTHING: /* A scalar, the callee can't dereference it. */
|
||||
return (struct arg_access_info) {};
|
||||
case ARG_PTR_TO_BTF_ID:
|
||||
/*
|
||||
* Trusted arguments cannot be stack pointers.
|
||||
* Untrusted arguments use ARG_IGNORE:
|
||||
* probe reads need no caller stack state, writes are prohibited.
|
||||
*/
|
||||
return (struct arg_access_info) {};
|
||||
case ARG_PTR_TO_MEM:
|
||||
/* mem_size is zero for untrusted pointers of unknown size. */
|
||||
if (sub->args[arg].mem_size) {
|
||||
info.size = sub->args[arg].mem_size;
|
||||
info.must_write = true;
|
||||
}
|
||||
return info;
|
||||
default:
|
||||
return info;
|
||||
}
|
||||
}
|
||||
|
||||
/* check special kfuncs and return:
|
||||
* 1 - not fall-through to 'else' branch, continue verification
|
||||
* 0 - fall-through to 'else' branch
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user