diff mbox series

[bpf-next,v8,3/4] bpf: Create argument information for nullable arguments.

Message ID 20240209023750.1153905-4-thinker.li@gmail.com (mailing list archive)
State Accepted
Commit 1611603537a4b88cec7993f32b70c03113801a46
Delegated to: BPF
Headers show
Series Support PTR_MAYBE_NULL for struct_ops arguments. | expand

Checks

Context Check Description
netdev/series_format success Posting correctly formatted
netdev/tree_selection success Clearly marked for bpf-next, async
netdev/ynl success Generated files up to date; no warnings/errors; no diff in generated;
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 7798 this patch: 7798
netdev/build_tools success Errors and warnings before: 0 this patch: 0
netdev/cc_maintainers warning 9 maintainers not CCed: jolsa@kernel.org daniel@iogearbox.net john.fastabend@gmail.com yonghong.song@linux.dev netdev@vger.kernel.org sdf@google.com eddyz87@gmail.com kpsingh@kernel.org haoluo@google.com
netdev/build_clang success Errors and warnings before: 2369 this patch: 2369
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 8292 this patch: 8292
netdev/checkpatch warning WARNING: line length of 96 exceeds 80 columns
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc success Errors and warnings before: 6 this patch: 6
netdev/source_inline success Was 0 now: 0
bpf/vmtest-bpf-next-VM_Test-11 success Logs for s390x-gcc / build / build for s390x with gcc
bpf/vmtest-bpf-next-VM_Test-38 success Logs for x86_64-llvm-18 / test (test_progs, false, 360) / test_progs on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-39 success Logs for x86_64-llvm-18 / test (test_progs_cpuv4, false, 360) / test_progs_cpuv4 on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-40 success Logs for x86_64-llvm-18 / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-41 success Logs for x86_64-llvm-18 / test (test_verifier, false, 360) / test_verifier on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-42 success Logs for x86_64-llvm-18 / veristat
bpf/vmtest-bpf-next-VM_Test-0 success Logs for Lint
bpf/vmtest-bpf-next-VM_Test-1 success Logs for ShellCheck
bpf/vmtest-bpf-next-VM_Test-2 success Logs for Unittests
bpf/vmtest-bpf-next-VM_Test-3 success Logs for Validate matrix.py
bpf/vmtest-bpf-next-VM_Test-5 success Logs for aarch64-gcc / build-release
bpf/vmtest-bpf-next-VM_Test-4 success Logs for aarch64-gcc / build / build for aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-12 success Logs for s390x-gcc / build-release
bpf/vmtest-bpf-next-VM_Test-10 success Logs for aarch64-gcc / veristat
bpf/vmtest-bpf-next-VM_Test-13 success Logs for set-matrix
bpf/vmtest-bpf-next-VM_Test-15 success Logs for x86_64-gcc / build-release
bpf/vmtest-bpf-next-VM_Test-6 success Logs for aarch64-gcc / test (test_maps, false, 360) / test_maps on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-9 success Logs for aarch64-gcc / test (test_verifier, false, 360) / test_verifier on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-7 success Logs for aarch64-gcc / test (test_progs, false, 360) / test_progs on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-8 success Logs for aarch64-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-29 success Logs for x86_64-llvm-17 / veristat
bpf/vmtest-bpf-next-VM_Test-30 success Logs for x86_64-llvm-18 / build / build for x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-37 success Logs for x86_64-llvm-18 / veristat
bpf/vmtest-bpf-next-VM_Test-14 success Logs for x86_64-gcc / build / build for x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-23 success Logs for x86_64-llvm-17 / build / build for x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-28 success Logs for x86_64-llvm-17 / test (test_verifier, false, 360) / test_verifier on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-31 success Logs for x86_64-llvm-18 / build-release / build for x86_64 with llvm-18 and -O2 optimization
bpf/vmtest-bpf-next-VM_Test-20 success Logs for x86_64-gcc / test (test_progs_parallel, true, 30) / test_progs_parallel on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-22 success Logs for x86_64-gcc / veristat / veristat on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-19 success Logs for x86_64-gcc / test (test_progs_no_alu32_parallel, true, 30) / test_progs_no_alu32_parallel on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-18 success Logs for x86_64-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-21 success Logs for x86_64-gcc / test (test_verifier, false, 360) / test_verifier on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-16 success Logs for x86_64-gcc / test (test_maps, false, 360) / test_maps on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-25 success Logs for x86_64-llvm-17 / test (test_maps, false, 360) / test_maps on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-26 success Logs for x86_64-llvm-17 / test (test_progs, false, 360) / test_progs on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-27 success Logs for x86_64-llvm-17 / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with llvm-17
bpf/vmtest-bpf-next-VM_Test-32 success Logs for x86_64-llvm-18 / test (test_maps, false, 360) / test_maps on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-36 success Logs for x86_64-llvm-18 / test (test_verifier, false, 360) / test_verifier on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-24 success Logs for x86_64-llvm-17 / build-release / build for x86_64 with llvm-17 and -O2 optimization
bpf/vmtest-bpf-next-VM_Test-33 success Logs for x86_64-llvm-18 / test (test_progs, false, 360) / test_progs on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-35 success Logs for x86_64-llvm-18 / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-34 success Logs for x86_64-llvm-18 / test (test_progs_cpuv4, false, 360) / test_progs_cpuv4 on x86_64 with llvm-18
bpf/vmtest-bpf-next-VM_Test-17 success Logs for x86_64-gcc / test (test_progs, false, 360) / test_progs on x86_64 with gcc
bpf/vmtest-bpf-next-PR fail merge-conflict

Commit Message

Kui-Feng Lee Feb. 9, 2024, 2:37 a.m. UTC
From: Kui-Feng Lee <thinker.li@gmail.com>

Collect argument information from the type information of stub functions to
mark arguments of BPF struct_ops programs with PTR_MAYBE_NULL if they are
nullable.  A nullable argument is annotated by suffixing "__nullable" at
the argument name of stub function.

For nullable arguments, this patch sets a struct bpf_ctx_arg_aux to label
their reg_type with PTR_TO_BTF_ID | PTR_TRUSTED | PTR_MAYBE_NULL. This
makes the verifier to check programs and ensure that they properly check
the pointer. The programs should check if the pointer is null before
accessing the pointed memory.

The implementer of a struct_ops type should annotate the arguments that can
be null. The implementer should define a stub function (empty) as a
placeholder for each defined operator. The name of a stub function should
be in the pattern "<st_op_type>__<operator name>". For example, for
test_maybe_null of struct bpf_testmod_ops, it's stub function name should
be "bpf_testmod_ops__test_maybe_null". You mark an argument nullable by
suffixing the argument name with "__nullable" at the stub function.

Since we already has stub functions for kCFI, we just reuse these stub
functions with the naming convention mentioned earlier. These stub
functions with the naming convention is only required if there are nullable
arguments to annotate. For functions having not nullable arguments, stub
functions are not necessary for the purpose of this patch.

This patch will prepare a list of struct bpf_ctx_arg_aux, aka arg_info, for
each member field of a struct_ops type.  "arg_info" will be assigned to
"prog->aux->ctx_arg_info" of BPF struct_ops programs in
check_struct_ops_btf_id() so that it can be used by btf_ctx_access() later
to set reg_type properly for the verifier.

Signed-off-by: Kui-Feng Lee <thinker.li@gmail.com>
---
 include/linux/bpf.h         |  21 ++++
 include/linux/btf.h         |   2 +
 kernel/bpf/bpf_struct_ops.c | 207 +++++++++++++++++++++++++++++++++---
 kernel/bpf/btf.c            |  27 +++++
 kernel/bpf/verifier.c       |   6 ++
 5 files changed, 251 insertions(+), 12 deletions(-)

Comments

Martin KaFai Lau Feb. 11, 2024, 7:49 p.m. UTC | #1
On 2/8/24 6:37 PM, thinker.li@gmail.com wrote:
> +/* Prepare argument info for every nullable argument of a member of a
> + * struct_ops type.
> + *
> + * Initialize a struct bpf_struct_ops_arg_info according to type info of
> + * the arguments of a stub function. (Check kCFI for more information about
> + * stub functions.)
> + *
> + * Each member in the struct_ops type has a struct bpf_struct_ops_arg_info
> + * to provide an array of struct bpf_ctx_arg_aux, which in turn provides
> + * the information that used by the verifier to check the arguments of the
> + * BPF struct_ops program assigned to the member. Here, we only care about
> + * the arguments that are marked as __nullable.
> + *
> + * The array of struct bpf_ctx_arg_aux is eventually assigned to
> + * prog->aux->ctx_arg_info of BPF struct_ops programs and passed to the
> + * verifier. (See check_struct_ops_btf_id())
> + *
> + * arg_info->info will be the list of struct bpf_ctx_arg_aux if success. If
> + * fails, it will be kept untouched.
> + */
> +static int prepare_arg_info(struct btf *btf,
> +			    const char *st_ops_name,
> +			    const char *member_name,
> +			    const struct btf_type *func_proto,
> +			    struct bpf_struct_ops_arg_info *arg_info)
> +{
> +	const struct btf_type *stub_func_proto, *pointed_type;
> +	const struct btf_param *stub_args, *args;
> +	struct bpf_ctx_arg_aux *info, *info_buf;
> +	u32 nargs, arg_no, info_cnt = 0;
> +	s32 arg_btf_id;
> +	int offset;
> +
> +	stub_func_proto = find_stub_func_proto(btf, st_ops_name, member_name);
> +	if (!stub_func_proto)
> +		return 0;
> +
> +	/* Check if the number of arguments of the stub function is the same
> +	 * as the number of arguments of the function pointer.
> +	 */
> +	nargs = btf_type_vlen(func_proto);
> +	if (nargs != btf_type_vlen(stub_func_proto)) {
> +		pr_warn("the number of arguments of the stub function %s__%s does not match the number of arguments of the member %s of struct %s\n",
> +			st_ops_name, member_name, member_name, st_ops_name);
> +		return -EINVAL;
> +	}
> +
> +	args = btf_params(func_proto);
> +	stub_args = btf_params(stub_func_proto);
> +
> +	info_buf = kcalloc(nargs, sizeof(*info_buf), GFP_KERNEL);
> +	if (!info_buf)
> +		return -ENOMEM;
> +
> +	/* Prepare info for every nullable argument */
> +	info = info_buf;
> +	for (arg_no = 0; arg_no < nargs; arg_no++) {
> +		/* Skip arguments that is not suffixed with
> +		 * "__nullable".
> +		 */
> +		if (!btf_param_match_suffix(btf, &stub_args[arg_no],
> +					    MAYBE_NULL_SUFFIX))
> +			continue;
> +
> +		/* Should be a pointer to struct */
> +		pointed_type = btf_type_resolve_ptr(btf,
> +						    args[arg_no].type,
> +						    &arg_btf_id);
> +		if (!pointed_type ||
> +		    !btf_type_is_struct(pointed_type)) {
> +			pr_warn("stub function %s__%s has %s tagging to an unsupported type\n",
> +				st_ops_name, member_name, MAYBE_NULL_SUFFIX);
> +			goto err_out;
> +		}

We briefly talked about this and compiler can probably catch any arg
type inconsistency between the stub func_proto and the original func_proto.

I still think it is better to be strict at the
beginning and ensure the "stub_args" type is the same as the original "args"
type. It is to bar any type inconsistency going forward on the __nullable
tagged argument (e.g. changing the original func_proto but forgot to
change the stub func_proto).

We can revisit in the future if the following type comparison does not work well.

                 if (args[arg_no].type != stub_args[arg_no].type) {
			pr_warn("arg#%u type in stub func_proto %s__%s does not match with its original func_proto\n",
				arg_no, st_ops_name, member_name);
			goto err_out;
                 }

> +
> +		offset = btf_ctx_arg_offset(btf, func_proto, arg_no);
> +		if (offset < 0) {
> +			pr_warn("stub function %s__%s has an invalid trampoline ctx offset for arg#%u\n",
> +				st_ops_name, member_name, arg_no);
> +			goto err_out;
> +		}
> +
> +		/* Fill the information of the new argument */
> +		info->reg_type =
> +			PTR_TRUSTED | PTR_TO_BTF_ID | PTR_MAYBE_NULL;
> +		info->btf_id = arg_btf_id;
> +		info->btf = btf;
> +		info->offset = offset;
> +
> +		info++;
> +		info_cnt++;
> +	}
> +
> +	if (info_cnt) {
> +		arg_info->info = info_buf;
> +		arg_info->cnt = info_cnt;
> +	} else {
> +		kfree(info_buf);
> +	}
> +
> +	return 0;
> +
> +err_out:
> +	kfree(info_buf);
> +
> +	return -EINVAL;
> +}
> +
> +/* Clean up the arg_info in a struct bpf_struct_ops_desc. */
> +void bpf_struct_ops_desc_release(struct bpf_struct_ops_desc *st_ops_desc)
> +{
> +	struct bpf_struct_ops_arg_info *arg_info;
> +	int i;
> +
> +	arg_info = st_ops_desc->arg_info;
> +	if (!arg_info)

nit. I think this check is unnecessary ?

If the above two comments make sense to you, I can make the adjustment. No need to resend.

Patch 4 lgtm.

> +		return;
> +
> +	for (i = 0; i < btf_type_vlen(st_ops_desc->type); i++)
> +		kfree(arg_info[i].info);
> +
> +	kfree(arg_info);
> +}
> +
Jiri Olsa Feb. 12, 2024, 11:45 a.m. UTC | #2
On Thu, Feb 08, 2024 at 06:37:49PM -0800, thinker.li@gmail.com wrote:

SNIP

>  enum bpf_struct_ops_state {
> @@ -1790,6 +1806,7 @@ int bpf_struct_ops_desc_init(struct bpf_struct_ops_desc *st_ops_desc,
>  			     struct btf *btf,
>  			     struct bpf_verifier_log *log);
>  void bpf_map_struct_ops_info_fill(struct bpf_map_info *info, struct bpf_map *map);
> +void bpf_struct_ops_desc_release(struct bpf_struct_ops_desc *st_ops_desc);
>  #else
>  #define register_bpf_struct_ops(st_ops, type) ({ (void *)(st_ops); 0; })
>  static inline bool bpf_try_module_get(const void *data, struct module *owner)
> @@ -1814,6 +1831,10 @@ static inline void bpf_map_struct_ops_info_fill(struct bpf_map_info *info, struc
>  {
>  }
>  
> +static inline void bpf_struct_ops_desc_release(struct bpf_struct_ops_desc *st_ops_desc, int len)
> +{
> +}

extra len argument?

jirka


SNIP

> +/* Clean up the arg_info in a struct bpf_struct_ops_desc. */
> +void bpf_struct_ops_desc_release(struct bpf_struct_ops_desc *st_ops_desc)
> +{
> +	struct bpf_struct_ops_arg_info *arg_info;
> +	int i;
> +
> +	arg_info = st_ops_desc->arg_info;
> +	if (!arg_info)
> +		return;
> +
> +	for (i = 0; i < btf_type_vlen(st_ops_desc->type); i++)
> +		kfree(arg_info[i].info);
> +
> +	kfree(arg_info);
> +}
> +
>  int bpf_struct_ops_desc_init(struct bpf_struct_ops_desc *st_ops_desc,
>  			     struct btf *btf,
>  			     struct bpf_verifier_log *log)
>  {
>  	struct bpf_struct_ops *st_ops = st_ops_desc->st_ops;
> +	struct bpf_struct_ops_arg_info *arg_info;
>  	const struct btf_member *member;
>  	const struct btf_type *t;
>  	s32 type_id, value_id;
>  	char value_name[128];
>  	const char *mname;
> -	int i;
> +	int i, err;
>  
>  	if (strlen(st_ops->name) + VALUE_PREFIX_LEN >=
>  	    sizeof(value_name)) {
> @@ -160,6 +320,17 @@ int bpf_struct_ops_desc_init(struct bpf_struct_ops_desc *st_ops_desc,
>  	if (!is_valid_value_type(btf, value_id, t, value_name))
>  		return -EINVAL;
>  
> +	arg_info = kcalloc(btf_type_vlen(t), sizeof(*arg_info),
> +			   GFP_KERNEL);
> +	if (!arg_info)
> +		return -ENOMEM;
> +
> +	st_ops_desc->arg_info = arg_info;
> +	st_ops_desc->type = t;
> +	st_ops_desc->type_id = type_id;
> +	st_ops_desc->value_id = value_id;
> +	st_ops_desc->value_type = btf_type_by_id(btf, value_id);
> +
>  	for_each_member(i, t, member) {
>  		const struct btf_type *func_proto;
>  
> @@ -167,40 +338,52 @@ int bpf_struct_ops_desc_init(struct bpf_struct_ops_desc *st_ops_desc,
>  		if (!*mname) {
>  			pr_warn("anon member in struct %s is not supported\n",
>  				st_ops->name);
> -			return -EOPNOTSUPP;
> +			err = -EOPNOTSUPP;
> +			goto errout;
>  		}
>  
>  		if (__btf_member_bitfield_size(t, member)) {
>  			pr_warn("bit field member %s in struct %s is not supported\n",
>  				mname, st_ops->name);
> -			return -EOPNOTSUPP;
> +			err = -EOPNOTSUPP;
> +			goto errout;
>  		}
>  
>  		func_proto = btf_type_resolve_func_ptr(btf,
>  						       member->type,
>  						       NULL);
> -		if (func_proto &&
> -		    btf_distill_func_proto(log, btf,
> +		if (!func_proto)
> +			continue;
> +
> +		if (btf_distill_func_proto(log, btf,
>  					   func_proto, mname,
>  					   &st_ops->func_models[i])) {
>  			pr_warn("Error in parsing func ptr %s in struct %s\n",
>  				mname, st_ops->name);
> -			return -EINVAL;
> +			err = -EINVAL;
> +			goto errout;
>  		}
> +
> +		err = prepare_arg_info(btf, st_ops->name, mname,
> +				       func_proto,
> +				       arg_info + i);
> +		if (err)
> +			goto errout;
>  	}
>  
>  	if (st_ops->init(btf)) {
>  		pr_warn("Error in init bpf_struct_ops %s\n",
>  			st_ops->name);
> -		return -EINVAL;
> +		err = -EINVAL;
> +		goto errout;
>  	}
>  
> -	st_ops_desc->type_id = type_id;
> -	st_ops_desc->type = t;
> -	st_ops_desc->value_id = value_id;
> -	st_ops_desc->value_type = btf_type_by_id(btf, value_id);
> -
>  	return 0;
> +
> +errout:
> +	bpf_struct_ops_desc_release(st_ops_desc);
> +
> +	return err;
>  }
>  
>  static int bpf_struct_ops_map_get_next_key(struct bpf_map *map, void *key,
> diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
> index db53bb76387e..533f02b92c94 100644
> --- a/kernel/bpf/btf.c
> +++ b/kernel/bpf/btf.c
> @@ -1699,6 +1699,13 @@ static void btf_free_struct_meta_tab(struct btf *btf)
>  static void btf_free_struct_ops_tab(struct btf *btf)
>  {
>  	struct btf_struct_ops_tab *tab = btf->struct_ops_tab;
> +	int i;
> +
> +	if (!tab)
> +		return;
> +
> +	for (i = 0; i < tab->cnt; i++)
> +		bpf_struct_ops_desc_release(&tab->ops[i]);
>  
>  	kfree(tab);
>  	btf->struct_ops_tab = NULL;
> @@ -6130,6 +6137,26 @@ static bool prog_args_trusted(const struct bpf_prog *prog)
>  	}
>  }
>  
> +int btf_ctx_arg_offset(struct btf *btf, const struct btf_type *func_proto,
> +		       u32 arg_no)
> +{
> +	const struct btf_param *args;
> +	const struct btf_type *t;
> +	int off = 0, i;
> +	u32 sz;
> +
> +	args = btf_params(func_proto);
> +	for (i = 0; i < arg_no; i++) {
> +		t = btf_type_by_id(btf, args[i].type);
> +		t = btf_resolve_size(btf, t, &sz);
> +		if (IS_ERR(t))
> +			return PTR_ERR(t);
> +		off += roundup(sz, 8);
> +	}
> +
> +	return off;
> +}
> +
>  bool btf_ctx_access(int off, int size, enum bpf_access_type type,
>  		    const struct bpf_prog *prog,
>  		    struct bpf_insn_access_aux *info)
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index c92d6af7d975..72ca27f49616 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -20419,6 +20419,12 @@ static int check_struct_ops_btf_id(struct bpf_verifier_env *env)
>  		}
>  	}
>  
> +	/* btf_ctx_access() used this to provide argument type info */
> +	prog->aux->ctx_arg_info =
> +		st_ops_desc->arg_info[member_idx].info;
> +	prog->aux->ctx_arg_info_size =
> +		st_ops_desc->arg_info[member_idx].cnt;
> +
>  	prog->aux->attach_func_proto = func_proto;
>  	prog->aux->attach_func_name = mname;
>  	env->ops = st_ops->verifier_ops;
> -- 
> 2.34.1
> 
>
Kui-Feng Lee Feb. 12, 2024, 5:09 p.m. UTC | #3
On 2/11/24 11:49, Martin KaFai Lau wrote:
> On 2/8/24 6:37 PM, thinker.li@gmail.com wrote:
>> +/* Prepare argument info for every nullable argument of a member of a
>> + * struct_ops type.
>> + *
>> + * Initialize a struct bpf_struct_ops_arg_info according to type info of
>> + * the arguments of a stub function. (Check kCFI for more information 
>> about
>> + * stub functions.)
>> + *
>> + * Each member in the struct_ops type has a struct 
>> bpf_struct_ops_arg_info
>> + * to provide an array of struct bpf_ctx_arg_aux, which in turn provides
>> + * the information that used by the verifier to check the arguments 
>> of the
>> + * BPF struct_ops program assigned to the member. Here, we only care 
>> about
>> + * the arguments that are marked as __nullable.
>> + *
>> + * The array of struct bpf_ctx_arg_aux is eventually assigned to
>> + * prog->aux->ctx_arg_info of BPF struct_ops programs and passed to the
>> + * verifier. (See check_struct_ops_btf_id())
>> + *
>> + * arg_info->info will be the list of struct bpf_ctx_arg_aux if 
>> success. If
>> + * fails, it will be kept untouched.
>> + */
>> +static int prepare_arg_info(struct btf *btf,
>> +                const char *st_ops_name,
>> +                const char *member_name,
>> +                const struct btf_type *func_proto,
>> +                struct bpf_struct_ops_arg_info *arg_info)
>> +{
>> +    const struct btf_type *stub_func_proto, *pointed_type;
>> +    const struct btf_param *stub_args, *args;
>> +    struct bpf_ctx_arg_aux *info, *info_buf;
>> +    u32 nargs, arg_no, info_cnt = 0;
>> +    s32 arg_btf_id;
>> +    int offset;
>> +
>> +    stub_func_proto = find_stub_func_proto(btf, st_ops_name, 
>> member_name);
>> +    if (!stub_func_proto)
>> +        return 0;
>> +
>> +    /* Check if the number of arguments of the stub function is the same
>> +     * as the number of arguments of the function pointer.
>> +     */
>> +    nargs = btf_type_vlen(func_proto);
>> +    if (nargs != btf_type_vlen(stub_func_proto)) {
>> +        pr_warn("the number of arguments of the stub function %s__%s 
>> does not match the number of arguments of the member %s of struct %s\n",
>> +            st_ops_name, member_name, member_name, st_ops_name);
>> +        return -EINVAL;
>> +    }
>> +
>> +    args = btf_params(func_proto);
>> +    stub_args = btf_params(stub_func_proto);
>> +
>> +    info_buf = kcalloc(nargs, sizeof(*info_buf), GFP_KERNEL);
>> +    if (!info_buf)
>> +        return -ENOMEM;
>> +
>> +    /* Prepare info for every nullable argument */
>> +    info = info_buf;
>> +    for (arg_no = 0; arg_no < nargs; arg_no++) {
>> +        /* Skip arguments that is not suffixed with
>> +         * "__nullable".
>> +         */
>> +        if (!btf_param_match_suffix(btf, &stub_args[arg_no],
>> +                        MAYBE_NULL_SUFFIX))
>> +            continue;
>> +
>> +        /* Should be a pointer to struct */
>> +        pointed_type = btf_type_resolve_ptr(btf,
>> +                            args[arg_no].type,
>> +                            &arg_btf_id);
>> +        if (!pointed_type ||
>> +            !btf_type_is_struct(pointed_type)) {
>> +            pr_warn("stub function %s__%s has %s tagging to an 
>> unsupported type\n",
>> +                st_ops_name, member_name, MAYBE_NULL_SUFFIX);
>> +            goto err_out;
>> +        }
> 
> We briefly talked about this and compiler can probably catch any arg
> type inconsistency between the stub func_proto and the original func_proto.
> 
> I still think it is better to be strict at the
> beginning and ensure the "stub_args" type is the same as the original 
> "args"
> type. It is to bar any type inconsistency going forward on the __nullable
> tagged argument (e.g. changing the original func_proto but forgot to
> change the stub func_proto).
> 
> We can revisit in the future if the following type comparison does not 
> work well.
> 
>                  if (args[arg_no].type != stub_args[arg_no].type) {
>              pr_warn("arg#%u type in stub func_proto %s__%s does not 
> match with its original func_proto\n",
>                  arg_no, st_ops_name, member_name);
>              goto err_out;
>                  }


Agree!

> 
>> +
>> +        offset = btf_ctx_arg_offset(btf, func_proto, arg_no);
>> +        if (offset < 0) {
>> +            pr_warn("stub function %s__%s has an invalid trampoline 
>> ctx offset for arg#%u\n",
>> +                st_ops_name, member_name, arg_no);
>> +            goto err_out;
>> +        }
>> +
>> +        /* Fill the information of the new argument */
>> +        info->reg_type =
>> +            PTR_TRUSTED | PTR_TO_BTF_ID | PTR_MAYBE_NULL;
>> +        info->btf_id = arg_btf_id;
>> +        info->btf = btf;
>> +        info->offset = offset;
>> +
>> +        info++;
>> +        info_cnt++;
>> +    }
>> +
>> +    if (info_cnt) {
>> +        arg_info->info = info_buf;
>> +        arg_info->cnt = info_cnt;
>> +    } else {
>> +        kfree(info_buf);
>> +    }
>> +
>> +    return 0;
>> +
>> +err_out:
>> +    kfree(info_buf);
>> +
>> +    return -EINVAL;
>> +}
>> +
>> +/* Clean up the arg_info in a struct bpf_struct_ops_desc. */
>> +void bpf_struct_ops_desc_release(struct bpf_struct_ops_desc 
>> *st_ops_desc)
>> +{
>> +    struct bpf_struct_ops_arg_info *arg_info;
>> +    int i;
>> +
>> +    arg_info = st_ops_desc->arg_info;
>> +    if (!arg_info)
> 
> nit. I think this check is unnecessary ?
> 
> If the above two comments make sense to you, I can make the adjustment. 
> No need to resend.


Agree!

> 
> Patch 4 lgtm.
> 
>> +        return;
>> +
>> +    for (i = 0; i < btf_type_vlen(st_ops_desc->type); i++)
>> +        kfree(arg_info[i].info);
>> +
>> +    kfree(arg_info);
>> +}
>> +
>
Kui-Feng Lee Feb. 12, 2024, 5:50 p.m. UTC | #4
On 2/12/24 03:45, Jiri Olsa wrote:
> On Thu, Feb 08, 2024 at 06:37:49PM -0800, thinker.li@gmail.com wrote:
> 
> SNIP
> 
>>   enum bpf_struct_ops_state {
>> @@ -1790,6 +1806,7 @@ int bpf_struct_ops_desc_init(struct bpf_struct_ops_desc *st_ops_desc,
>>   			     struct btf *btf,
>>   			     struct bpf_verifier_log *log);
>>   void bpf_map_struct_ops_info_fill(struct bpf_map_info *info, struct bpf_map *map);
>> +void bpf_struct_ops_desc_release(struct bpf_struct_ops_desc *st_ops_desc);
>>   #else
>>   #define register_bpf_struct_ops(st_ops, type) ({ (void *)(st_ops); 0; })
>>   static inline bool bpf_try_module_get(const void *data, struct module *owner)
>> @@ -1814,6 +1831,10 @@ static inline void bpf_map_struct_ops_info_fill(struct bpf_map_info *info, struc
>>   {
>>   }
>>   
>> +static inline void bpf_struct_ops_desc_release(struct bpf_struct_ops_desc *st_ops_desc, int len)
>> +{
>> +}
> 
> extra len argument?

Yes, this one should be removed.

> 
> jirka
> 
> 
> SNIP
> 
>> +/* Clean up the arg_info in a struct bpf_struct_ops_desc. */
>> +void bpf_struct_ops_desc_release(struct bpf_struct_ops_desc *st_ops_desc)
>> +{
>> +	struct bpf_struct_ops_arg_info *arg_info;
>> +	int i;
>> +
>> +	arg_info = st_ops_desc->arg_info;
>> +	if (!arg_info)
>> +		return;
>> +
>> +	for (i = 0; i < btf_type_vlen(st_ops_desc->type); i++)
>> +		kfree(arg_info[i].info);
>> +
>> +	kfree(arg_info);
>> +}
>> +
>>   int bpf_struct_ops_desc_init(struct bpf_struct_ops_desc *st_ops_desc,
>>   			     struct btf *btf,
>>   			     struct bpf_verifier_log *log)
>>   {
>>   	struct bpf_struct_ops *st_ops = st_ops_desc->st_ops;
>> +	struct bpf_struct_ops_arg_info *arg_info;
>>   	const struct btf_member *member;
>>   	const struct btf_type *t;
>>   	s32 type_id, value_id;
>>   	char value_name[128];
>>   	const char *mname;
>> -	int i;
>> +	int i, err;
>>   
>>   	if (strlen(st_ops->name) + VALUE_PREFIX_LEN >=
>>   	    sizeof(value_name)) {
>> @@ -160,6 +320,17 @@ int bpf_struct_ops_desc_init(struct bpf_struct_ops_desc *st_ops_desc,
>>   	if (!is_valid_value_type(btf, value_id, t, value_name))
>>   		return -EINVAL;
>>   
>> +	arg_info = kcalloc(btf_type_vlen(t), sizeof(*arg_info),
>> +			   GFP_KERNEL);
>> +	if (!arg_info)
>> +		return -ENOMEM;
>> +
>> +	st_ops_desc->arg_info = arg_info;
>> +	st_ops_desc->type = t;
>> +	st_ops_desc->type_id = type_id;
>> +	st_ops_desc->value_id = value_id;
>> +	st_ops_desc->value_type = btf_type_by_id(btf, value_id);
>> +
>>   	for_each_member(i, t, member) {
>>   		const struct btf_type *func_proto;
>>   
>> @@ -167,40 +338,52 @@ int bpf_struct_ops_desc_init(struct bpf_struct_ops_desc *st_ops_desc,
>>   		if (!*mname) {
>>   			pr_warn("anon member in struct %s is not supported\n",
>>   				st_ops->name);
>> -			return -EOPNOTSUPP;
>> +			err = -EOPNOTSUPP;
>> +			goto errout;
>>   		}
>>   
>>   		if (__btf_member_bitfield_size(t, member)) {
>>   			pr_warn("bit field member %s in struct %s is not supported\n",
>>   				mname, st_ops->name);
>> -			return -EOPNOTSUPP;
>> +			err = -EOPNOTSUPP;
>> +			goto errout;
>>   		}
>>   
>>   		func_proto = btf_type_resolve_func_ptr(btf,
>>   						       member->type,
>>   						       NULL);
>> -		if (func_proto &&
>> -		    btf_distill_func_proto(log, btf,
>> +		if (!func_proto)
>> +			continue;
>> +
>> +		if (btf_distill_func_proto(log, btf,
>>   					   func_proto, mname,
>>   					   &st_ops->func_models[i])) {
>>   			pr_warn("Error in parsing func ptr %s in struct %s\n",
>>   				mname, st_ops->name);
>> -			return -EINVAL;
>> +			err = -EINVAL;
>> +			goto errout;
>>   		}
>> +
>> +		err = prepare_arg_info(btf, st_ops->name, mname,
>> +				       func_proto,
>> +				       arg_info + i);
>> +		if (err)
>> +			goto errout;
>>   	}
>>   
>>   	if (st_ops->init(btf)) {
>>   		pr_warn("Error in init bpf_struct_ops %s\n",
>>   			st_ops->name);
>> -		return -EINVAL;
>> +		err = -EINVAL;
>> +		goto errout;
>>   	}
>>   
>> -	st_ops_desc->type_id = type_id;
>> -	st_ops_desc->type = t;
>> -	st_ops_desc->value_id = value_id;
>> -	st_ops_desc->value_type = btf_type_by_id(btf, value_id);
>> -
>>   	return 0;
>> +
>> +errout:
>> +	bpf_struct_ops_desc_release(st_ops_desc);
>> +
>> +	return err;
>>   }
>>   
>>   static int bpf_struct_ops_map_get_next_key(struct bpf_map *map, void *key,
>> diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
>> index db53bb76387e..533f02b92c94 100644
>> --- a/kernel/bpf/btf.c
>> +++ b/kernel/bpf/btf.c
>> @@ -1699,6 +1699,13 @@ static void btf_free_struct_meta_tab(struct btf *btf)
>>   static void btf_free_struct_ops_tab(struct btf *btf)
>>   {
>>   	struct btf_struct_ops_tab *tab = btf->struct_ops_tab;
>> +	int i;
>> +
>> +	if (!tab)
>> +		return;
>> +
>> +	for (i = 0; i < tab->cnt; i++)
>> +		bpf_struct_ops_desc_release(&tab->ops[i]);
>>   
>>   	kfree(tab);
>>   	btf->struct_ops_tab = NULL;
>> @@ -6130,6 +6137,26 @@ static bool prog_args_trusted(const struct bpf_prog *prog)
>>   	}
>>   }
>>   
>> +int btf_ctx_arg_offset(struct btf *btf, const struct btf_type *func_proto,
>> +		       u32 arg_no)
>> +{
>> +	const struct btf_param *args;
>> +	const struct btf_type *t;
>> +	int off = 0, i;
>> +	u32 sz;
>> +
>> +	args = btf_params(func_proto);
>> +	for (i = 0; i < arg_no; i++) {
>> +		t = btf_type_by_id(btf, args[i].type);
>> +		t = btf_resolve_size(btf, t, &sz);
>> +		if (IS_ERR(t))
>> +			return PTR_ERR(t);
>> +		off += roundup(sz, 8);
>> +	}
>> +
>> +	return off;
>> +}
>> +
>>   bool btf_ctx_access(int off, int size, enum bpf_access_type type,
>>   		    const struct bpf_prog *prog,
>>   		    struct bpf_insn_access_aux *info)
>> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
>> index c92d6af7d975..72ca27f49616 100644
>> --- a/kernel/bpf/verifier.c
>> +++ b/kernel/bpf/verifier.c
>> @@ -20419,6 +20419,12 @@ static int check_struct_ops_btf_id(struct bpf_verifier_env *env)
>>   		}
>>   	}
>>   
>> +	/* btf_ctx_access() used this to provide argument type info */
>> +	prog->aux->ctx_arg_info =
>> +		st_ops_desc->arg_info[member_idx].info;
>> +	prog->aux->ctx_arg_info_size =
>> +		st_ops_desc->arg_info[member_idx].cnt;
>> +
>>   	prog->aux->attach_func_proto = func_proto;
>>   	prog->aux->attach_func_name = mname;
>>   	env->ops = st_ops->verifier_ops;
>> -- 
>> 2.34.1
>>
>>
Martin KaFai Lau Feb. 13, 2024, 11:27 p.m. UTC | #5
On 2/12/24 3:45 AM, Jiri Olsa wrote:
> On Thu, Feb 08, 2024 at 06:37:49PM -0800, thinker.li@gmail.com wrote:
> 
> SNIP
> 
>>   enum bpf_struct_ops_state {
>> @@ -1790,6 +1806,7 @@ int bpf_struct_ops_desc_init(struct bpf_struct_ops_desc *st_ops_desc,
>>   			     struct btf *btf,
>>   			     struct bpf_verifier_log *log);
>>   void bpf_map_struct_ops_info_fill(struct bpf_map_info *info, struct bpf_map *map);
>> +void bpf_struct_ops_desc_release(struct bpf_struct_ops_desc *st_ops_desc);
>>   #else
>>   #define register_bpf_struct_ops(st_ops, type) ({ (void *)(st_ops); 0; })
>>   static inline bool bpf_try_module_get(const void *data, struct module *owner)
>> @@ -1814,6 +1831,10 @@ static inline void bpf_map_struct_ops_info_fill(struct bpf_map_info *info, struc
>>   {
>>   }
>>   
>> +static inline void bpf_struct_ops_desc_release(struct bpf_struct_ops_desc *st_ops_desc, int len)
>> +{
>> +}
> 
> extra len argument?

Good catch. Fixed and applied. Also changed some inconsistent integer usage by 
s/s32/u32/ (e.g. s/s32/u32/ arg_btf_id)
diff mbox series

Patch

diff --git a/include/linux/bpf.h b/include/linux/bpf.h
index 9a2ee9456989..b4efec1ace48 100644
--- a/include/linux/bpf.h
+++ b/include/linux/bpf.h
@@ -1709,6 +1709,19 @@  struct bpf_struct_ops {
 	struct btf_func_model func_models[BPF_STRUCT_OPS_MAX_NR_MEMBERS];
 };
 
+/* Every member of a struct_ops type has an instance even a member is not
+ * an operator (function pointer). The "info" field will be assigned to
+ * prog->aux->ctx_arg_info of BPF struct_ops programs to provide the
+ * argument information required by the verifier to verify the program.
+ *
+ * btf_ctx_access() will lookup prog->aux->ctx_arg_info to find the
+ * corresponding entry for an given argument.
+ */
+struct bpf_struct_ops_arg_info {
+	struct bpf_ctx_arg_aux *info;
+	u32 cnt;
+};
+
 struct bpf_struct_ops_desc {
 	struct bpf_struct_ops *st_ops;
 
@@ -1716,6 +1729,9 @@  struct bpf_struct_ops_desc {
 	const struct btf_type *value_type;
 	u32 type_id;
 	u32 value_id;
+
+	/* Collection of argument information for each member */
+	struct bpf_struct_ops_arg_info *arg_info;
 };
 
 enum bpf_struct_ops_state {
@@ -1790,6 +1806,7 @@  int bpf_struct_ops_desc_init(struct bpf_struct_ops_desc *st_ops_desc,
 			     struct btf *btf,
 			     struct bpf_verifier_log *log);
 void bpf_map_struct_ops_info_fill(struct bpf_map_info *info, struct bpf_map *map);
+void bpf_struct_ops_desc_release(struct bpf_struct_ops_desc *st_ops_desc);
 #else
 #define register_bpf_struct_ops(st_ops, type) ({ (void *)(st_ops); 0; })
 static inline bool bpf_try_module_get(const void *data, struct module *owner)
@@ -1814,6 +1831,10 @@  static inline void bpf_map_struct_ops_info_fill(struct bpf_map_info *info, struc
 {
 }
 
+static inline void bpf_struct_ops_desc_release(struct bpf_struct_ops_desc *st_ops_desc, int len)
+{
+}
+
 #endif
 
 #if defined(CONFIG_CGROUP_BPF) && defined(CONFIG_BPF_LSM)
diff --git a/include/linux/btf.h b/include/linux/btf.h
index df76a14c64f6..15ee845e6b38 100644
--- a/include/linux/btf.h
+++ b/include/linux/btf.h
@@ -498,6 +498,8 @@  static inline void *btf_id_set8_contains(const struct btf_id_set8 *set, u32 id)
 bool btf_param_match_suffix(const struct btf *btf,
 			    const struct btf_param *arg,
 			    const char *suffix);
+int btf_ctx_arg_offset(struct btf *btf, const struct btf_type *func_proto,
+		       u32 arg_no);
 
 struct bpf_verifier_log;
 
diff --git a/kernel/bpf/bpf_struct_ops.c b/kernel/bpf/bpf_struct_ops.c
index f98f580de77a..f1aef9de94b2 100644
--- a/kernel/bpf/bpf_struct_ops.c
+++ b/kernel/bpf/bpf_struct_ops.c
@@ -116,17 +116,177 @@  static bool is_valid_value_type(struct btf *btf, s32 value_id,
 	return true;
 }
 
+#define MAYBE_NULL_SUFFIX "__nullable"
+#define MAX_STUB_NAME 128
+
+/* Return the type info of a stub function, if it exists.
+ *
+ * The name of a stub function is made up of the name of the struct_ops and
+ * the name of the function pointer member, separated by "__". For example,
+ * if the struct_ops type is named "foo_ops" and the function pointer
+ * member is named "bar", the stub function name would be "foo_ops__bar".
+ */
+static const struct btf_type *
+find_stub_func_proto(struct btf *btf, const char *st_op_name,
+		     const char *member_name)
+{
+	char stub_func_name[MAX_STUB_NAME];
+	const struct btf_type *func_type;
+	s32 btf_id;
+	int cp;
+
+	cp = snprintf(stub_func_name, MAX_STUB_NAME, "%s__%s",
+		      st_op_name, member_name);
+	if (cp >= MAX_STUB_NAME) {
+		pr_warn("Stub function name too long\n");
+		return NULL;
+	}
+	btf_id = btf_find_by_name_kind(btf, stub_func_name, BTF_KIND_FUNC);
+	if (btf_id < 0)
+		return NULL;
+	func_type = btf_type_by_id(btf, btf_id);
+	if (!func_type)
+		return NULL;
+
+	return btf_type_by_id(btf, func_type->type); /* FUNC_PROTO */
+}
+
+/* Prepare argument info for every nullable argument of a member of a
+ * struct_ops type.
+ *
+ * Initialize a struct bpf_struct_ops_arg_info according to type info of
+ * the arguments of a stub function. (Check kCFI for more information about
+ * stub functions.)
+ *
+ * Each member in the struct_ops type has a struct bpf_struct_ops_arg_info
+ * to provide an array of struct bpf_ctx_arg_aux, which in turn provides
+ * the information that used by the verifier to check the arguments of the
+ * BPF struct_ops program assigned to the member. Here, we only care about
+ * the arguments that are marked as __nullable.
+ *
+ * The array of struct bpf_ctx_arg_aux is eventually assigned to
+ * prog->aux->ctx_arg_info of BPF struct_ops programs and passed to the
+ * verifier. (See check_struct_ops_btf_id())
+ *
+ * arg_info->info will be the list of struct bpf_ctx_arg_aux if success. If
+ * fails, it will be kept untouched.
+ */
+static int prepare_arg_info(struct btf *btf,
+			    const char *st_ops_name,
+			    const char *member_name,
+			    const struct btf_type *func_proto,
+			    struct bpf_struct_ops_arg_info *arg_info)
+{
+	const struct btf_type *stub_func_proto, *pointed_type;
+	const struct btf_param *stub_args, *args;
+	struct bpf_ctx_arg_aux *info, *info_buf;
+	u32 nargs, arg_no, info_cnt = 0;
+	s32 arg_btf_id;
+	int offset;
+
+	stub_func_proto = find_stub_func_proto(btf, st_ops_name, member_name);
+	if (!stub_func_proto)
+		return 0;
+
+	/* Check if the number of arguments of the stub function is the same
+	 * as the number of arguments of the function pointer.
+	 */
+	nargs = btf_type_vlen(func_proto);
+	if (nargs != btf_type_vlen(stub_func_proto)) {
+		pr_warn("the number of arguments of the stub function %s__%s does not match the number of arguments of the member %s of struct %s\n",
+			st_ops_name, member_name, member_name, st_ops_name);
+		return -EINVAL;
+	}
+
+	args = btf_params(func_proto);
+	stub_args = btf_params(stub_func_proto);
+
+	info_buf = kcalloc(nargs, sizeof(*info_buf), GFP_KERNEL);
+	if (!info_buf)
+		return -ENOMEM;
+
+	/* Prepare info for every nullable argument */
+	info = info_buf;
+	for (arg_no = 0; arg_no < nargs; arg_no++) {
+		/* Skip arguments that is not suffixed with
+		 * "__nullable".
+		 */
+		if (!btf_param_match_suffix(btf, &stub_args[arg_no],
+					    MAYBE_NULL_SUFFIX))
+			continue;
+
+		/* Should be a pointer to struct */
+		pointed_type = btf_type_resolve_ptr(btf,
+						    args[arg_no].type,
+						    &arg_btf_id);
+		if (!pointed_type ||
+		    !btf_type_is_struct(pointed_type)) {
+			pr_warn("stub function %s__%s has %s tagging to an unsupported type\n",
+				st_ops_name, member_name, MAYBE_NULL_SUFFIX);
+			goto err_out;
+		}
+
+		offset = btf_ctx_arg_offset(btf, func_proto, arg_no);
+		if (offset < 0) {
+			pr_warn("stub function %s__%s has an invalid trampoline ctx offset for arg#%u\n",
+				st_ops_name, member_name, arg_no);
+			goto err_out;
+		}
+
+		/* Fill the information of the new argument */
+		info->reg_type =
+			PTR_TRUSTED | PTR_TO_BTF_ID | PTR_MAYBE_NULL;
+		info->btf_id = arg_btf_id;
+		info->btf = btf;
+		info->offset = offset;
+
+		info++;
+		info_cnt++;
+	}
+
+	if (info_cnt) {
+		arg_info->info = info_buf;
+		arg_info->cnt = info_cnt;
+	} else {
+		kfree(info_buf);
+	}
+
+	return 0;
+
+err_out:
+	kfree(info_buf);
+
+	return -EINVAL;
+}
+
+/* Clean up the arg_info in a struct bpf_struct_ops_desc. */
+void bpf_struct_ops_desc_release(struct bpf_struct_ops_desc *st_ops_desc)
+{
+	struct bpf_struct_ops_arg_info *arg_info;
+	int i;
+
+	arg_info = st_ops_desc->arg_info;
+	if (!arg_info)
+		return;
+
+	for (i = 0; i < btf_type_vlen(st_ops_desc->type); i++)
+		kfree(arg_info[i].info);
+
+	kfree(arg_info);
+}
+
 int bpf_struct_ops_desc_init(struct bpf_struct_ops_desc *st_ops_desc,
 			     struct btf *btf,
 			     struct bpf_verifier_log *log)
 {
 	struct bpf_struct_ops *st_ops = st_ops_desc->st_ops;
+	struct bpf_struct_ops_arg_info *arg_info;
 	const struct btf_member *member;
 	const struct btf_type *t;
 	s32 type_id, value_id;
 	char value_name[128];
 	const char *mname;
-	int i;
+	int i, err;
 
 	if (strlen(st_ops->name) + VALUE_PREFIX_LEN >=
 	    sizeof(value_name)) {
@@ -160,6 +320,17 @@  int bpf_struct_ops_desc_init(struct bpf_struct_ops_desc *st_ops_desc,
 	if (!is_valid_value_type(btf, value_id, t, value_name))
 		return -EINVAL;
 
+	arg_info = kcalloc(btf_type_vlen(t), sizeof(*arg_info),
+			   GFP_KERNEL);
+	if (!arg_info)
+		return -ENOMEM;
+
+	st_ops_desc->arg_info = arg_info;
+	st_ops_desc->type = t;
+	st_ops_desc->type_id = type_id;
+	st_ops_desc->value_id = value_id;
+	st_ops_desc->value_type = btf_type_by_id(btf, value_id);
+
 	for_each_member(i, t, member) {
 		const struct btf_type *func_proto;
 
@@ -167,40 +338,52 @@  int bpf_struct_ops_desc_init(struct bpf_struct_ops_desc *st_ops_desc,
 		if (!*mname) {
 			pr_warn("anon member in struct %s is not supported\n",
 				st_ops->name);
-			return -EOPNOTSUPP;
+			err = -EOPNOTSUPP;
+			goto errout;
 		}
 
 		if (__btf_member_bitfield_size(t, member)) {
 			pr_warn("bit field member %s in struct %s is not supported\n",
 				mname, st_ops->name);
-			return -EOPNOTSUPP;
+			err = -EOPNOTSUPP;
+			goto errout;
 		}
 
 		func_proto = btf_type_resolve_func_ptr(btf,
 						       member->type,
 						       NULL);
-		if (func_proto &&
-		    btf_distill_func_proto(log, btf,
+		if (!func_proto)
+			continue;
+
+		if (btf_distill_func_proto(log, btf,
 					   func_proto, mname,
 					   &st_ops->func_models[i])) {
 			pr_warn("Error in parsing func ptr %s in struct %s\n",
 				mname, st_ops->name);
-			return -EINVAL;
+			err = -EINVAL;
+			goto errout;
 		}
+
+		err = prepare_arg_info(btf, st_ops->name, mname,
+				       func_proto,
+				       arg_info + i);
+		if (err)
+			goto errout;
 	}
 
 	if (st_ops->init(btf)) {
 		pr_warn("Error in init bpf_struct_ops %s\n",
 			st_ops->name);
-		return -EINVAL;
+		err = -EINVAL;
+		goto errout;
 	}
 
-	st_ops_desc->type_id = type_id;
-	st_ops_desc->type = t;
-	st_ops_desc->value_id = value_id;
-	st_ops_desc->value_type = btf_type_by_id(btf, value_id);
-
 	return 0;
+
+errout:
+	bpf_struct_ops_desc_release(st_ops_desc);
+
+	return err;
 }
 
 static int bpf_struct_ops_map_get_next_key(struct bpf_map *map, void *key,
diff --git a/kernel/bpf/btf.c b/kernel/bpf/btf.c
index db53bb76387e..533f02b92c94 100644
--- a/kernel/bpf/btf.c
+++ b/kernel/bpf/btf.c
@@ -1699,6 +1699,13 @@  static void btf_free_struct_meta_tab(struct btf *btf)
 static void btf_free_struct_ops_tab(struct btf *btf)
 {
 	struct btf_struct_ops_tab *tab = btf->struct_ops_tab;
+	int i;
+
+	if (!tab)
+		return;
+
+	for (i = 0; i < tab->cnt; i++)
+		bpf_struct_ops_desc_release(&tab->ops[i]);
 
 	kfree(tab);
 	btf->struct_ops_tab = NULL;
@@ -6130,6 +6137,26 @@  static bool prog_args_trusted(const struct bpf_prog *prog)
 	}
 }
 
+int btf_ctx_arg_offset(struct btf *btf, const struct btf_type *func_proto,
+		       u32 arg_no)
+{
+	const struct btf_param *args;
+	const struct btf_type *t;
+	int off = 0, i;
+	u32 sz;
+
+	args = btf_params(func_proto);
+	for (i = 0; i < arg_no; i++) {
+		t = btf_type_by_id(btf, args[i].type);
+		t = btf_resolve_size(btf, t, &sz);
+		if (IS_ERR(t))
+			return PTR_ERR(t);
+		off += roundup(sz, 8);
+	}
+
+	return off;
+}
+
 bool btf_ctx_access(int off, int size, enum bpf_access_type type,
 		    const struct bpf_prog *prog,
 		    struct bpf_insn_access_aux *info)
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index c92d6af7d975..72ca27f49616 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -20419,6 +20419,12 @@  static int check_struct_ops_btf_id(struct bpf_verifier_env *env)
 		}
 	}
 
+	/* btf_ctx_access() used this to provide argument type info */
+	prog->aux->ctx_arg_info =
+		st_ops_desc->arg_info[member_idx].info;
+	prog->aux->ctx_arg_info_size =
+		st_ops_desc->arg_info[member_idx].cnt;
+
 	prog->aux->attach_func_proto = func_proto;
 	prog->aux->attach_func_name = mname;
 	env->ops = st_ops->verifier_ops;