mirror of
https://github.com/torvalds/linux.git
synced 2026-11-06 21:58:01 +01:00
bpf: Describe helper stack accesses with arg_access_info
Adjust bpf_helper_stack_access_bytes() to return results as a struct arg_access_info. The result is based on bpf_func_proto->argX_type value as in the following table: | Argument flags | allow_uninit_stack | Meaning | |----------------------+--------------------+----------------------| | MEM_WRITE | true | may_read, must_write | | MEM_WRITE+MEM_UNINIT | true | must_write | | MEM_WRITE | false | may_read, must_write | | MEM_WRITE+MEM_UNINIT | false | may_read, may_write | | 0 | true/false | may_read | MEM_WRITE now produces must_write when allow_uninit_stack is true: the verifier discards previous stack knowledge over the affected range. Previously, liveness only treated MEM_UNINIT this way. Also use resolve_map_arg_type() to account for bpf_map_peek_elem() treating its buffer as an input for bloom filters. Annotate ringbuf dynptr submit and discard arguments with MEM_WRITE. Their existing OBJ_RELEASE handling already invalidates the dynptr, but the prototypes need to describe the write as well as the release. 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-2-bcba9bc53ac9@gmail.com
This commit is contained in:
parent
5b6beb95f7
commit
830e4372de
|
|
@ -1731,9 +1731,9 @@ struct arg_access_info {
|
|||
u8 must_write:1; /* Prior verifier state is destroyed throughout size. */
|
||||
};
|
||||
|
||||
s64 bpf_helper_stack_access_bytes(struct bpf_verifier_env *env,
|
||||
struct bpf_insn *insn, int arg,
|
||||
int insn_idx);
|
||||
struct arg_access_info
|
||||
bpf_helper_stack_access_bytes(struct bpf_verifier_env *env,
|
||||
struct bpf_insn *insn, int arg, int insn_idx);
|
||||
s64 bpf_kfunc_stack_access_bytes(struct bpf_verifier_env *env,
|
||||
struct bpf_insn *insn, int arg,
|
||||
int insn_idx);
|
||||
|
|
|
|||
|
|
@ -1604,7 +1604,7 @@ static int record_load_store_access(struct bpf_verifier_env *env,
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* Adapt the signed helper/kfunc access size until both producers are converted. */
|
||||
/* Adapt the signed kfunc access size until its producer is converted. */
|
||||
static struct arg_access_info stack_access_info(s64 bytes)
|
||||
{
|
||||
bool write = bytes < 0 && bytes != S64_MIN && -bytes < U32_MAX;
|
||||
|
|
@ -1634,8 +1634,7 @@ static int record_arg_access(struct bpf_verifier_env *env,
|
|||
return 0;
|
||||
|
||||
if (bpf_helper_call(insn)) {
|
||||
info = stack_access_info(bpf_helper_stack_access_bytes(env, insn,
|
||||
arg_idx, insn_idx));
|
||||
info = bpf_helper_stack_access_bytes(env, insn, arg_idx, insn_idx);
|
||||
} else if (bpf_pseudo_kfunc_call(insn)) {
|
||||
info = stack_access_info(bpf_kfunc_stack_access_bytes(env, insn,
|
||||
arg_idx, insn_idx));
|
||||
|
|
|
|||
|
|
@ -722,7 +722,7 @@ BPF_CALL_2(bpf_ringbuf_submit_dynptr, struct bpf_dynptr_kern *, ptr, u64, flags)
|
|||
const struct bpf_func_proto bpf_ringbuf_submit_dynptr_proto = {
|
||||
.func = bpf_ringbuf_submit_dynptr,
|
||||
.ret_type = RET_VOID,
|
||||
.arg1_type = ARG_PTR_TO_DYNPTR | DYNPTR_TYPE_RINGBUF | OBJ_RELEASE,
|
||||
.arg1_type = ARG_PTR_TO_DYNPTR | DYNPTR_TYPE_RINGBUF | OBJ_RELEASE | MEM_WRITE,
|
||||
.arg2_type = ARG_ANYTHING,
|
||||
};
|
||||
|
||||
|
|
@ -741,7 +741,7 @@ BPF_CALL_2(bpf_ringbuf_discard_dynptr, struct bpf_dynptr_kern *, ptr, u64, flags
|
|||
const struct bpf_func_proto bpf_ringbuf_discard_dynptr_proto = {
|
||||
.func = bpf_ringbuf_discard_dynptr,
|
||||
.ret_type = RET_VOID,
|
||||
.arg1_type = ARG_PTR_TO_DYNPTR | DYNPTR_TYPE_RINGBUF | OBJ_RELEASE,
|
||||
.arg1_type = ARG_PTR_TO_DYNPTR | DYNPTR_TYPE_RINGBUF | OBJ_RELEASE | MEM_WRITE,
|
||||
.arg2_type = ARG_ANYTHING,
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -14358,35 +14358,32 @@ int bpf_fetch_kfunc_arg_meta(struct bpf_verifier_env *env,
|
|||
}
|
||||
|
||||
/*
|
||||
* Determine how many bytes a helper accesses through a stack pointer at
|
||||
* argument position @arg (0-based, corresponding to R1-R5).
|
||||
*
|
||||
* Returns:
|
||||
* > 0 known read access size in bytes
|
||||
* 0 doesn't read anything directly
|
||||
* S64_MIN unknown
|
||||
* < 0 known write access of (-return) bytes
|
||||
* Describe a helper's stack access through argument @arg (0-based, R1-R5).
|
||||
* must_write means the verifier destroys prior state throughout size bytes.
|
||||
* In other words, must_write follows the verifier's model of the
|
||||
* function's behaviour, which is safe to use for is_state_visited() pruning.
|
||||
*/
|
||||
s64 bpf_helper_stack_access_bytes(struct bpf_verifier_env *env, struct bpf_insn *insn,
|
||||
int arg, int insn_idx)
|
||||
struct arg_access_info
|
||||
bpf_helper_stack_access_bytes(struct bpf_verifier_env *env, struct bpf_insn *insn,
|
||||
int arg, int insn_idx)
|
||||
{
|
||||
struct bpf_insn_aux_data *aux = &env->insn_aux_data[insn_idx];
|
||||
struct arg_access_info info = {
|
||||
.size = U32_MAX,
|
||||
.may_read = true,
|
||||
.may_write = true,
|
||||
};
|
||||
const struct bpf_func_proto *fn;
|
||||
enum bpf_access_type access_type;
|
||||
enum bpf_arg_type at;
|
||||
bool full_write;
|
||||
s64 size;
|
||||
bool exact_size = true;
|
||||
u64 size = U32_MAX;
|
||||
|
||||
if (bpf_get_helper_proto(env, insn->imm, &fn) < 0)
|
||||
return S64_MIN;
|
||||
return info;
|
||||
|
||||
at = fn->arg_type[arg];
|
||||
/*
|
||||
* Generic outputs may leave bytes untouched. Keep prior initialization
|
||||
* live when the caller cannot read uninitialized bytes. Constructors of
|
||||
* special objects, such as dynptrs, still define their storage.
|
||||
*/
|
||||
full_write = (at & MEM_UNINIT) &&
|
||||
(!arg_type_is_raw_mem(at) || env->allow_uninit_stack);
|
||||
access_type = func_arg_access_type(at);
|
||||
|
||||
switch (base_type(at)) {
|
||||
case ARG_PTR_TO_MAP_KEY:
|
||||
|
|
@ -14409,6 +14406,10 @@ s64 bpf_helper_stack_access_bytes(struct bpf_verifier_env *env, struct bpf_insn
|
|||
|
||||
i = aux->const_reg_vals[map_reg];
|
||||
if (i < env->used_map_cnt) {
|
||||
/* Bloom-filter peek reads the value buffer. */
|
||||
if (!is_key && insn->imm == BPF_FUNC_map_peek_elem &&
|
||||
env->used_maps[i]->map_type == BPF_MAP_TYPE_BLOOM_FILTER)
|
||||
access_type = BPF_READ;
|
||||
size = is_key ? env->used_maps[i]->key_size
|
||||
: env->used_maps[i]->value_size;
|
||||
goto out;
|
||||
|
|
@ -14418,7 +14419,12 @@ s64 bpf_helper_stack_access_bytes(struct bpf_verifier_env *env, struct bpf_insn
|
|||
* Map pointer is not known at this call site (e.g. different
|
||||
* maps on merged paths). Conservatively return the largest
|
||||
* key_size or value_size across all maps used by the program.
|
||||
* This is only an upper bound, so it cannot establish a definite
|
||||
* write. Map-dependent argument types can also turn an output
|
||||
* into an input, so conservatively retain a read dependency.
|
||||
*/
|
||||
exact_size = false;
|
||||
access_type |= BPF_READ;
|
||||
val = 0;
|
||||
for (i = 0; i < env->used_map_cnt; i++) {
|
||||
struct bpf_map *map = env->used_maps[i];
|
||||
|
|
@ -14434,7 +14440,7 @@ s64 bpf_helper_stack_access_bytes(struct bpf_verifier_env *env, struct bpf_insn
|
|||
}
|
||||
}
|
||||
if (!val)
|
||||
return S64_MIN;
|
||||
return info;
|
||||
size = val;
|
||||
goto out;
|
||||
}
|
||||
|
|
@ -14448,18 +14454,12 @@ s64 bpf_helper_stack_access_bytes(struct bpf_verifier_env *env, struct bpf_insn
|
|||
int size_reg = BPF_REG_1 + arg + 1;
|
||||
|
||||
if (aux->const_reg_mask & BIT(size_reg)) {
|
||||
size = (s64)aux->const_reg_vals[size_reg];
|
||||
size = aux->const_reg_vals[size_reg];
|
||||
goto out;
|
||||
}
|
||||
/*
|
||||
* Size arg is const on each path but differs across merged
|
||||
* paths. Reads may extend anywhere up to the frame top.
|
||||
*/
|
||||
if (full_write)
|
||||
return 0;
|
||||
return S64_MIN;
|
||||
}
|
||||
return S64_MIN;
|
||||
/* Preserve access directions even when the extent is unknown. */
|
||||
goto out;
|
||||
case ARG_PTR_TO_DYNPTR:
|
||||
size = BPF_DYNPTR_SIZE;
|
||||
break;
|
||||
|
|
@ -14469,18 +14469,21 @@ s64 bpf_helper_stack_access_bytes(struct bpf_verifier_env *env, struct bpf_insn
|
|||
* doesn't access stack. The callback subprog does and it's
|
||||
* analyzed separately.
|
||||
*/
|
||||
return 0;
|
||||
return (struct arg_access_info) {};
|
||||
default:
|
||||
return S64_MIN;
|
||||
return info;
|
||||
}
|
||||
out:
|
||||
/*
|
||||
* Other accesses keep the previous state live, including untouched bytes
|
||||
* of an unprivileged generic output.
|
||||
*/
|
||||
if (full_write)
|
||||
return -size;
|
||||
return size;
|
||||
info.size = min_t(u64, size, U32_MAX);
|
||||
info.may_read = !!(access_type & BPF_READ);
|
||||
info.may_write = !!(access_type & BPF_WRITE);
|
||||
info.must_write = info.may_write && exact_size && info.size != U32_MAX;
|
||||
/* Generic unprivileged outputs retain prior initialization state. */
|
||||
if (!env->allow_uninit_stack && arg_type_is_raw_mem(at)) {
|
||||
info.may_read = true;
|
||||
info.must_write = false;
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user