mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-30 12:13:51 -04:00
bpf: Classify scalar kfunc arguments from BTF
Add kfunc scalar argument types, classify them in get_kfunc_arg_type() along side with pointer arguments and move scalar type verification into the main switch in check_kfunc_args(). This keeps BTF-based classification separate from register validation for every argument, paving the way for generating the kfunc argument prototype at add-call time. No functional change intended. KF_ARG_MEM_SIZE and KF_ARG_CONST_MEM_SIZE now are reachable. Therefore, remove the fallthrough from KF_ARG_PTR_TO_MEM case and adjust the register indexing. Signed-off-by: Amery Hung <ameryhung@gmail.com> Reviewed-by: Eduard Zingerman <eddyz87@gmail.com> Link: https://lore.kernel.org/bpf/20260801074633.1595644-18-ameryhung@gmail.com Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
This commit is contained in:
committed by
Kumar Kartikeya Dwivedi
parent
ba5b99470c
commit
1690dcf27c
@@ -11073,6 +11073,9 @@ static bool __btf_type_is_scalar_struct(struct bpf_verifier_env *env,
|
||||
enum kfunc_ptr_arg_type {
|
||||
KF_ARG_CONST_MEM_SIZE,
|
||||
KF_ARG_MEM_SIZE,
|
||||
KF_ARG_CONST,
|
||||
KF_ARG_CONST_ALLOC_SIZE_OR_ZERO,
|
||||
KF_ARG_ANYTHING,
|
||||
KF_ARG_PTR_TO_CTX,
|
||||
KF_ARG_PTR_TO_ALLOC_BTF_ID, /* Allocated object */
|
||||
KF_ARG_PTR_TO_REFCOUNTED_KPTR, /* Refcounted local kptr */
|
||||
@@ -11348,13 +11351,39 @@ bool bpf_is_kfunc_pkt_changing(struct bpf_call_arg_meta *meta)
|
||||
}
|
||||
|
||||
static int
|
||||
get_kfunc_ptr_arg_type(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,
|
||||
const struct btf_type *t, const struct btf_type *ref_t,
|
||||
const char *ref_tname, const struct btf_param *args,
|
||||
int arg, int nargs, argno_t argno)
|
||||
get_kfunc_arg_type(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,
|
||||
const struct btf_param *args, int arg, int nargs)
|
||||
{
|
||||
const struct btf_type *t, *ref_t = NULL;
|
||||
argno_t argno = argno_from_arg(arg + 1);
|
||||
const char *ref_tname = NULL;
|
||||
int arg_type;
|
||||
|
||||
t = btf_type_skip_modifiers(meta->btf, args[arg].type, NULL);
|
||||
|
||||
/* Scalar arguments are classified from their BTF suffix/name alone. */
|
||||
if (btf_type_is_scalar(t)) {
|
||||
if (is_kfunc_arg_constant(meta->btf, &args[arg]))
|
||||
return KF_ARG_CONST;
|
||||
if (is_kfunc_arg_const_mem_size(meta->btf, &args[arg]))
|
||||
return KF_ARG_CONST_MEM_SIZE;
|
||||
if (is_kfunc_arg_mem_size(meta->btf, &args[arg]))
|
||||
return KF_ARG_MEM_SIZE;
|
||||
if (is_kfunc_arg_scalar_with_name(meta->btf, &args[arg], "rdonly_buf_size") ||
|
||||
is_kfunc_arg_scalar_with_name(meta->btf, &args[arg], "rdwr_buf_size"))
|
||||
return KF_ARG_CONST_ALLOC_SIZE_OR_ZERO;
|
||||
return KF_ARG_ANYTHING;
|
||||
}
|
||||
|
||||
if (!btf_type_is_ptr(t)) {
|
||||
verbose(env, "Unrecognized %s type %s\n",
|
||||
reg_arg_name(env, argno), btf_type_str(t));
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ref_t = btf_type_skip_modifiers(meta->btf, t->type, NULL);
|
||||
ref_tname = btf_name_by_offset(meta->btf, ref_t->name_off);
|
||||
|
||||
/* In this function, we verify the kfunc's BTF as per the argument type,
|
||||
* leaving the rest of the verification with respect to the register
|
||||
* type to our caller. When a set of conditions hold in the BTF type of
|
||||
@@ -12043,7 +12072,6 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
|
||||
int regno = reg_from_argno(argno);
|
||||
bool btf_id_fixed_off_ok = true;
|
||||
u32 ref_id, type_size;
|
||||
bool is_ret_buf_sz = false;
|
||||
int kf_arg_type;
|
||||
|
||||
if (is_kfunc_arg_prog_aux(btf, &args[i])) {
|
||||
@@ -12067,39 +12095,7 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
|
||||
|
||||
t = btf_type_skip_modifiers(btf, args[i].type, NULL);
|
||||
|
||||
if (btf_type_is_scalar(t)) {
|
||||
if (reg->type != SCALAR_VALUE) {
|
||||
verbose(env, "%s is not a scalar\n", reg_arg_name(env, argno));
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (is_kfunc_arg_constant(meta->btf, &args[i]) ||
|
||||
is_kfunc_arg_const_mem_size(meta->btf, &args[i])) {
|
||||
ret = process_const_arg(env, reg, argno, meta);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
} else if (is_kfunc_arg_scalar_with_name(btf, &args[i], "rdonly_buf_size")) {
|
||||
meta->r0_rdonly = true;
|
||||
is_ret_buf_sz = true;
|
||||
} else if (is_kfunc_arg_scalar_with_name(btf, &args[i], "rdwr_buf_size")) {
|
||||
is_ret_buf_sz = true;
|
||||
}
|
||||
|
||||
if (is_ret_buf_sz) {
|
||||
ret = process_const_alloc_mem_size(env, reg, argno, &meta->ret_mem);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!btf_type_is_ptr(t)) {
|
||||
verbose(env, "Unrecognized %s type %s\n",
|
||||
reg_arg_name(env, argno), btf_type_str(t));
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if ((bpf_register_is_null(reg) || type_may_be_null(reg->type)) &&
|
||||
if (btf_type_is_ptr(t) && (bpf_register_is_null(reg) || type_may_be_null(reg->type)) &&
|
||||
!is_kfunc_arg_nullable(meta->btf, &args[i])) {
|
||||
verbose(env, "Possibly NULL pointer passed to trusted %s\n",
|
||||
reg_arg_name(env, argno));
|
||||
@@ -12116,11 +12112,12 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
|
||||
if (reg_is_referenced(env, reg))
|
||||
update_ref_obj(&meta->ref_obj, reg);
|
||||
|
||||
ref_t = btf_type_skip_modifiers(btf, t->type, &ref_id);
|
||||
ref_tname = btf_name_by_offset(btf, ref_t->name_off);
|
||||
if (btf_type_is_ptr(t)) {
|
||||
ref_t = btf_type_skip_modifiers(btf, t->type, &ref_id);
|
||||
ref_tname = btf_name_by_offset(btf, ref_t->name_off);
|
||||
}
|
||||
|
||||
kf_arg_type = get_kfunc_ptr_arg_type(env, meta, t, ref_t, ref_tname,
|
||||
args, i, nargs, argno);
|
||||
kf_arg_type = get_kfunc_arg_type(env, meta, args, i, nargs);
|
||||
if (kf_arg_type < 0)
|
||||
return kf_arg_type;
|
||||
|
||||
@@ -12134,6 +12131,11 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
|
||||
}
|
||||
|
||||
switch (base_type(kf_arg_type)) {
|
||||
case KF_ARG_CONST:
|
||||
case KF_ARG_CONST_MEM_SIZE:
|
||||
case KF_ARG_MEM_SIZE:
|
||||
case KF_ARG_ANYTHING:
|
||||
case KF_ARG_CONST_ALLOC_SIZE_OR_ZERO:
|
||||
case KF_ARG_PTR_TO_ALLOC_BTF_ID:
|
||||
case KF_ARG_PTR_TO_BTF_ID:
|
||||
case KF_ARG_CONST_MAP_PTR:
|
||||
@@ -12174,6 +12176,34 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
|
||||
return ret;
|
||||
|
||||
switch (base_type(kf_arg_type)) {
|
||||
case KF_ARG_CONST:
|
||||
if (reg->type != SCALAR_VALUE) {
|
||||
verbose(env, "%s is not a scalar\n", reg_arg_name(env, argno));
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = process_const_arg(env, reg, argno, meta);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
break;
|
||||
case KF_ARG_ANYTHING:
|
||||
if (reg->type != SCALAR_VALUE) {
|
||||
verbose(env, "%s is not a scalar\n", reg_arg_name(env, argno));
|
||||
return -EINVAL;
|
||||
}
|
||||
break;
|
||||
case KF_ARG_CONST_ALLOC_SIZE_OR_ZERO:
|
||||
if (reg->type != SCALAR_VALUE) {
|
||||
verbose(env, "%s is not a scalar\n", reg_arg_name(env, argno));
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (is_kfunc_arg_scalar_with_name(btf, &args[i], "rdonly_buf_size"))
|
||||
meta->r0_rdonly = true;
|
||||
ret = process_const_alloc_mem_size(env, reg, argno, &meta->ret_mem);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
break;
|
||||
case KF_ARG_PTR_TO_CTX:
|
||||
if (reg->type != PTR_TO_CTX) {
|
||||
verbose(env, "%s expected pointer to ctx, but got %s\n",
|
||||
@@ -12407,22 +12437,33 @@ static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_call_arg_me
|
||||
ret = check_mem_reg(env, reg, argno, type_size, BPF_READ | BPF_WRITE, meta);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
break;
|
||||
}
|
||||
fallthrough;
|
||||
break;
|
||||
case KF_ARG_CONST_MEM_SIZE:
|
||||
ret = process_const_arg(env, reg, argno, meta);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
fallthrough;
|
||||
case KF_ARG_MEM_SIZE:
|
||||
{
|
||||
struct bpf_reg_state *buff_reg = reg;
|
||||
struct bpf_reg_state *size_reg = get_func_arg_reg(caller, regs, i + 1);
|
||||
argno_t next_argno = argno_from_arg(i + 2);
|
||||
struct bpf_reg_state *buff_reg = get_func_arg_reg(caller, regs, i - 1);
|
||||
struct bpf_reg_state *size_reg = reg;
|
||||
argno_t buff_argno = argno_from_arg(i);
|
||||
|
||||
ret = check_mem_size_reg(env, buff_reg, size_reg, argno, next_argno,
|
||||
if (reg->type != SCALAR_VALUE) {
|
||||
verbose(env, "%s is not a scalar\n", reg_arg_name(env, argno));
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (bpf_register_is_null(buff_reg))
|
||||
break;
|
||||
|
||||
ret = check_mem_size_reg(env, buff_reg, size_reg, buff_argno, argno,
|
||||
BPF_READ | BPF_WRITE, true, meta);
|
||||
if (ret < 0) {
|
||||
verbose(env, "%s and ", reg_arg_name(env, argno));
|
||||
verbose(env, "%s and ", reg_arg_name(env, buff_argno));
|
||||
verbose(env, "%s memory, len pair leads to invalid memory access\n",
|
||||
reg_arg_name(env, next_argno));
|
||||
reg_arg_name(env, argno));
|
||||
return ret;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -1589,7 +1589,7 @@ int xdp_invalid_ctx(void *ctx)
|
||||
__u32 hdr_size = sizeof(struct ethhdr);
|
||||
/* Can't pass in variable-sized len to bpf_dynptr_slice */
|
||||
SEC("?tc")
|
||||
__failure __msg("unbounded memory access")
|
||||
__failure __msg("must be a known constant")
|
||||
int dynptr_slice_var_len1(struct __sk_buff *skb)
|
||||
{
|
||||
struct bpf_dynptr ptr;
|
||||
|
||||
Reference in New Issue
Block a user