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:
Amery Hung
2026-08-01 00:46:32 -07:00
committed by Kumar Kartikeya Dwivedi
parent ba5b99470c
commit 1690dcf27c
2 changed files with 92 additions and 51 deletions

View File

@@ -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;

View File

@@ -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;