From: Kumar Kartikeya Dwivedi <memxor@gmail.com>
To: Alexei Starovoitov <alexei.starovoitov@gmail.com>
Cc: bpf@vger.kernel.org, Alexei Starovoitov <ast@kernel.org>,
Andrii Nakryiko <andrii@kernel.org>,
Daniel Borkmann <daniel@iogearbox.net>,
Martin KaFai Lau <martin.lau@kernel.org>,
Dave Marchevsky <davemarchevsky@meta.com>
Subject: Re: [PATCH bpf-next v7 14/26] bpf: Rewrite kfunc argument handling
Date: Tue, 15 Nov 2022 22:21:22 +0530 [thread overview]
Message-ID: <20221115165122.4qp7dn26notjekwt@apollo> (raw)
In-Reply-To: <20221115061050.4f4i6dhqlfievr7p@macbook-pro-5.dhcp.thefacebook.com>
On Tue, Nov 15, 2022 at 11:40:50AM IST, Alexei Starovoitov wrote:
> On Tue, Nov 15, 2022 at 12:45:35AM +0530, Kumar Kartikeya Dwivedi wrote:
> > As we continue to add more features, argument types, kfunc flags, and
> > different extensions to kfuncs, the code to verify the correctness of
> > the kfunc prototype wrt the passed in registers has become ad-hoc and
> > ugly to read. To make life easier, and make a very clear split between
> > different stages of argument processing, move all the code into
> > verifier.c and refactor into easier to read helpers and functions.
> >
> > This also makes sharing code within the verifier easier with kfunc
> > argument processing. This will be more and more useful in later patches
> > as we are now moving to implement very core BPF helpers as kfuncs, to
> > keep them experimental before baking into UAPI.
> >
> > Signed-off-by: Kumar Kartikeya Dwivedi <memxor@gmail.com>
> > ---
> > [...]
> > +/* Returns true if struct is composed of scalars, 4 levels of nesting allowed */
> > +static bool __btf_type_is_scalar_struct(struct bpf_verifier_env *env,
> > + const struct btf *btf,
> > + const struct btf_type *t, int rec)
> > +{
> > + const struct btf_type *member_type;
> > + const struct btf_member *member;
> > + u32 i;
> > +
> > + if (!btf_type_is_struct(t))
> > + return false;
> > +
> > + for_each_member(i, t, member) {
> > + const struct btf_array *array;
> > +
> > + member_type = btf_type_skip_modifiers(btf, member->type, NULL);
> > + if (btf_type_is_struct(member_type)) {
> > + if (rec >= 3) {
> > + verbose(env, "max struct nesting depth exceeded\n");
> > + return false;
> > + }
> > + if (!__btf_type_is_scalar_struct(env, btf, member_type, rec + 1))
> > + return false;
> > + continue;
> > + }
> > + if (btf_type_is_array(member_type)) {
> > + array = btf_array(member_type);
> > + if (!array->nelems)
> > + return false;
> > + member_type = btf_type_skip_modifiers(btf, array->type, NULL);
> > + if (!btf_type_is_scalar(member_type))
> > + return false;
> > + continue;
> > + }
> > + if (!btf_type_is_scalar(member_type))
> > + return false;
> > + }
> > + return true;
> > +}
>
> Deleting the code from the next patch can be combined with this patch,
> since it's a pure code move?
>
> Similar to few funcs that do pure code move... it's better to have them
> in a single patch.
>
> Not sure about 2 patch split strategy in general.
> Not clear whether it helps or hurts the code review.
>
No problem, I can squash both into a single patch.
> > +
> > +
> > +static u32 *reg2btf_ids[__BPF_REG_TYPE_MAX] = {
> > +#ifdef CONFIG_NET
> > + [PTR_TO_SOCKET] = &btf_sock_ids[BTF_SOCK_TYPE_SOCK],
> > + [PTR_TO_SOCK_COMMON] = &btf_sock_ids[BTF_SOCK_TYPE_SOCK_COMMON],
> > + [PTR_TO_TCP_SOCK] = &btf_sock_ids[BTF_SOCK_TYPE_TCP],
> > +#endif
> > +};
> > +
> > +enum kfunc_ptr_arg_type {
> > + KF_ARG_PTR_TO_CTX,
> > + KF_ARG_PTR_TO_KPTR_STRONG, /* PTR_TO_KPTR but type specific */
>
> What does the STRONG suffix signify?
>
> PTR_TO_KPTR should always be safe.
> Just to make it different from ARG_PTR_TO_KPTR in kptr_xchg that
> has 'void *' arg and "auto converts" the type?
>
Yes.
> Here STRONG means that the type of the arg should match?
Yes.
>
> I think it's too verbose.
> Just KF_ARG_PTR_TO_KPTR would be clear enough.
> If we ever have another kptr_xchg that is done as kfunc
> we can add KF_ARG_PTR_TO_KPTR_AUTO or some other name that we can bikeshed later.
>
Ack, I'll drop the _STRONG suffix and simply call it ARG_PTR_TO_KPTR.
> > + KF_ARG_PTR_TO_DYNPTR,
> > + KF_ARG_PTR_TO_BTF_ID, /* Also covers reg2btf_ids conversions */
> > + KF_ARG_PTR_TO_MEM,
> > + KF_ARG_PTR_TO_MEM_SIZE, /* Size derived from next argument, skip it */
> > +};
> > +
> > +static enum kfunc_ptr_arg_type
> > +get_kfunc_ptr_arg_type(struct bpf_verifier_env *env,
> > + struct bpf_kfunc_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 argno, int nargs)
> > +{
> > + u32 regno = argno + 1;
> > + struct bpf_reg_state *regs = cur_regs(env);
> > + struct bpf_reg_state *reg = ®s[regno];
> > + bool arg_mem_size = false;
> > +
> > + /* 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
> > + * arguments, we resolve it to a known kfunc_ptr_arg_type.
> > + */
> > + if (btf_get_prog_ctx_type(&env->log, meta->btf, t, resolve_prog_type(env->prog), argno))
> > + return KF_ARG_PTR_TO_CTX;
> > +
> > + if (is_kfunc_arg_kptr_get(meta, argno)) {
> > + if (!btf_type_is_ptr(ref_t)) {
> > + verbose(env, "arg#0 BTF type must be a double pointer for kptr_get kfunc\n");
> > + return -EINVAL;
> > + }
> > + ref_t = btf_type_by_id(meta->btf, ref_t->type);
> > + ref_tname = btf_name_by_offset(meta->btf, ref_t->name_off);
> > + if (!btf_type_is_struct(ref_t)) {
> > + verbose(env, "kernel function %s args#0 pointer type %s %s is not supported\n",
> > + meta->func_name, btf_type_str(ref_t), ref_tname);
> > + return -EINVAL;
> > + }
> > + return KF_ARG_PTR_TO_KPTR_STRONG;
> > + }
> > +
> > + if (is_kfunc_arg_dynptr(meta->btf, &args[argno]))
> > + return KF_ARG_PTR_TO_DYNPTR;
> > +
> > + if ((base_type(reg->type) == PTR_TO_BTF_ID || reg2btf_ids[base_type(reg->type)])) {
> > + if (!btf_type_is_struct(ref_t)) {
> > + verbose(env, "kernel function %s args#%d pointer type %s %s is not supported\n",
> > + meta->func_name, argno, btf_type_str(ref_t), ref_tname);
> > + return -EINVAL;
> > + }
> > + return KF_ARG_PTR_TO_BTF_ID;
> > + }
> > +
> > + if (argno + 1 < nargs && is_kfunc_arg_mem_size(meta->btf, &args[argno + 1], ®s[regno + 1]))
> > + arg_mem_size = true;
> > +
> > + /* This is the catch all argument type of register types supported by
> > + * check_helper_mem_access. However, we only allow when argument type is
> > + * pointer to scalar, or struct composed (recursively) of scalars. When
> > + * arg_mem_size is true, the pointer can be void *.
> > + */
> > + if (!btf_type_is_scalar(ref_t) && !__btf_type_is_scalar_struct(env, meta->btf, ref_t, 0) &&
> > + (arg_mem_size ? !btf_type_is_void(ref_t) : 1)) {
> > + verbose(env, "arg#%d pointer type %s %s must point to %sscalar, or struct with scalar\n",
> > + argno, btf_type_str(ref_t), ref_tname, arg_mem_size ? "void, " : "");
> > + return -EINVAL;
> > + }
> > + return arg_mem_size ? KF_ARG_PTR_TO_MEM_SIZE : KF_ARG_PTR_TO_MEM;
> > +}
> > +
> > +static int process_kf_arg_ptr_to_btf_id(struct bpf_verifier_env *env,
> > + struct bpf_reg_state *reg,
> > + const struct btf_type *ref_t,
> > + const char *ref_tname, u32 ref_id,
> > + struct bpf_kfunc_call_arg_meta *meta,
> > + int argno)
> > +{
> > + const struct btf_type *reg_ref_t;
> > + bool strict_type_match = false;
> > + const struct btf *reg_btf;
> > + const char *reg_ref_tname;
> > + u32 reg_ref_id;
> > +
> > + if (reg->type == PTR_TO_BTF_ID) {
> > + reg_btf = reg->btf;
> > + reg_ref_id = reg->btf_id;
> > + } else {
> > + reg_btf = btf_vmlinux;
> > + reg_ref_id = *reg2btf_ids[base_type(reg->type)];
> > + }
> > +
> > + if (is_kfunc_trusted_args(meta) || (is_kfunc_release(meta) && reg->ref_obj_id))
> > + strict_type_match = true;
> > +
> > + reg_ref_t = btf_type_skip_modifiers(reg_btf, reg_ref_id, ®_ref_id);
> > + reg_ref_tname = btf_name_by_offset(reg_btf, reg_ref_t->name_off);
> > + if (!btf_struct_ids_match(&env->log, reg_btf, reg_ref_id, reg->off, meta->btf, ref_id, strict_type_match)) {
> > + verbose(env, "kernel function %s args#%d expected pointer to %s %s but R%d has a pointer to %s %s\n",
> > + meta->func_name, argno, btf_type_str(ref_t), ref_tname, argno + 1,
> > + btf_type_str(reg_ref_t), reg_ref_tname);
> > + return -EINVAL;
> > + }
> > + return 0;
> > +}
> > +
> > +static int process_kf_arg_ptr_to_kptr_strong(struct bpf_verifier_env *env,
> > + struct bpf_reg_state *reg,
> > + const struct btf_type *ref_t,
> > + const char *ref_tname,
> > + struct bpf_kfunc_call_arg_meta *meta,
> > + int argno)
> > +{
> > + struct btf_field *kptr_field;
> > +
> > + /* check_func_arg_reg_off allows var_off for
> > + * PTR_TO_MAP_VALUE, but we need fixed offset to find
> > + * off_desc.
> > + */
> > + if (!tnum_is_const(reg->var_off)) {
> > + verbose(env, "arg#0 must have constant offset\n");
> > + return -EINVAL;
> > + }
> > +
> > + kptr_field = btf_record_find(reg->map_ptr->record, reg->off + reg->var_off.value, BPF_KPTR);
> > + if (!kptr_field || kptr_field->type != BPF_KPTR_REF) {
> > + verbose(env, "arg#0 no referenced kptr at map value offset=%llu\n",
> > + reg->off + reg->var_off.value);
> > + return -EINVAL;
> > + }
> > +
> > + if (!btf_struct_ids_match(&env->log, meta->btf, ref_t->type, 0, kptr_field->kptr.btf,
> > + kptr_field->kptr.btf_id, true)) {
> > + verbose(env, "kernel function %s args#%d expected pointer to %s %s\n",
> > + meta->func_name, argno, btf_type_str(ref_t), ref_tname);
> > + return -EINVAL;
> > + }
> > + return 0;
> > +}
> > +
> > +static int check_kfunc_args(struct bpf_verifier_env *env, struct bpf_kfunc_call_arg_meta *meta)
> > +{
> > + const char *func_name = meta->func_name, *ref_tname;
> > + const struct btf *btf = meta->btf;
> > + const struct btf_param *args;
> > + u32 i, nargs;
> > + int ret;
> > +
> > + args = (const struct btf_param *)(meta->func_proto + 1);
> > + nargs = btf_type_vlen(meta->func_proto);
> > + if (nargs > MAX_BPF_FUNC_REG_ARGS) {
> > + verbose(env, "Function %s has %d > %d args\n", func_name, nargs,
> > + MAX_BPF_FUNC_REG_ARGS);
> > + return -EINVAL;
> > + }
> > +
> > + /* Check that BTF function arguments match actual types that the
> > + * verifier sees.
> > + */
> > + for (i = 0; i < nargs; i++) {
> > + struct bpf_reg_state *regs = cur_regs(env), *reg = ®s[i + 1];
> > + const struct btf_type *t, *ref_t, *resolve_ret;
> > + enum bpf_arg_type arg_type = ARG_DONTCARE;
> > + u32 regno = i + 1, ref_id, type_size;
> > + bool is_ret_buf_sz = false;
> > + int kf_arg_type;
> > +
> > + t = btf_type_skip_modifiers(btf, args[i].type, NULL);
> > + if (btf_type_is_scalar(t)) {
> > + if (reg->type != SCALAR_VALUE) {
> > + verbose(env, "R%d is not a scalar\n", regno);
> > + return -EINVAL;
> > + }
> > + if (is_kfunc_arg_ret_buf_size(btf, &args[i], reg, "rdonly_buf_size")) {
> > + meta->r0_rdonly = true;
> > + is_ret_buf_sz = true;
> > + } else if (is_kfunc_arg_ret_buf_size(btf, &args[i], reg, "rdwr_buf_size")) {
> > + is_ret_buf_sz = true;
> > + }
>
> is_kfunc_arg_ret_buf_size() is more generic than its name says so.
> Maybe is_scalar_arg_with_name() ?
>
I named it is_kfunc_scalar_arg_with_name for consistency.
> Also looks wrong to triple check inside it:
> + if (!btf_type_is_scalar(t) || reg->type != SCALAR_VALUE)
> + return false;
> when there was a check above.
>
Right, this was in the original code that I copied over, but it's no longer
needed given where it's called now.
next prev parent reply other threads:[~2022-11-15 16:51 UTC|newest]
Thread overview: 48+ messages / expand[flat|nested] mbox.gz Atom feed top
2022-11-14 19:15 [PATCH bpf-next v7 00/26] Allocated objects, BPF linked lists Kumar Kartikeya Dwivedi
2022-11-14 19:15 ` [PATCH bpf-next v7 01/26] bpf: Remove local kptr references in documentation Kumar Kartikeya Dwivedi
2022-11-14 19:15 ` [PATCH bpf-next v7 02/26] bpf: Remove BPF_MAP_OFF_ARR_MAX Kumar Kartikeya Dwivedi
2022-11-14 19:15 ` [PATCH bpf-next v7 03/26] bpf: Fix copy_map_value, zero_map_value Kumar Kartikeya Dwivedi
2022-11-14 19:15 ` [PATCH bpf-next v7 04/26] bpf: Support bpf_list_head in map values Kumar Kartikeya Dwivedi
2022-11-14 19:15 ` [PATCH bpf-next v7 05/26] bpf: Rename RET_PTR_TO_ALLOC_MEM Kumar Kartikeya Dwivedi
2022-11-14 19:15 ` [PATCH bpf-next v7 06/26] bpf: Rename MEM_ALLOC to MEM_RINGBUF Kumar Kartikeya Dwivedi
2022-11-14 19:15 ` [PATCH bpf-next v7 07/26] bpf: Refactor btf_struct_access Kumar Kartikeya Dwivedi
2022-11-14 19:15 ` [PATCH bpf-next v7 08/26] bpf: Introduce allocated objects support Kumar Kartikeya Dwivedi
2022-11-15 5:48 ` Alexei Starovoitov
2022-11-15 16:47 ` Kumar Kartikeya Dwivedi
2022-11-14 19:15 ` [PATCH bpf-next v7 09/26] bpf: Recognize lock and list fields in allocated objects Kumar Kartikeya Dwivedi
2022-11-15 5:50 ` Alexei Starovoitov
2022-11-15 16:48 ` Kumar Kartikeya Dwivedi
2022-11-14 19:15 ` [PATCH bpf-next v7 10/26] bpf: Verify ownership relationships for user BTF types Kumar Kartikeya Dwivedi
2022-11-14 19:15 ` [PATCH bpf-next v7 11/26] bpf: Allow locking bpf_spin_lock in allocated objects Kumar Kartikeya Dwivedi
2022-11-14 19:15 ` [PATCH bpf-next v7 12/26] bpf: Allow locking bpf_spin_lock global variables Kumar Kartikeya Dwivedi
2022-11-15 17:33 ` Dave Marchevsky
2022-11-15 19:37 ` Kumar Kartikeya Dwivedi
2022-11-14 19:15 ` [PATCH bpf-next v7 13/26] bpf: Allow locking bpf_spin_lock in inner map values Kumar Kartikeya Dwivedi
2022-11-14 19:15 ` [PATCH bpf-next v7 14/26] bpf: Rewrite kfunc argument handling Kumar Kartikeya Dwivedi
2022-11-15 6:10 ` Alexei Starovoitov
2022-11-15 16:51 ` Kumar Kartikeya Dwivedi [this message]
2022-11-14 19:15 ` [PATCH bpf-next v7 15/26] bpf: Drop kfunc bits from btf_check_func_arg_match Kumar Kartikeya Dwivedi
2022-11-14 19:15 ` [PATCH bpf-next v7 16/26] bpf: Support constant scalar arguments for kfuncs Kumar Kartikeya Dwivedi
2022-11-14 19:15 ` [PATCH bpf-next v7 17/26] bpf: Introduce bpf_obj_new Kumar Kartikeya Dwivedi
2022-11-15 6:19 ` Alexei Starovoitov
2022-11-15 16:53 ` Kumar Kartikeya Dwivedi
2022-11-14 19:15 ` [PATCH bpf-next v7 18/26] bpf: Introduce bpf_obj_drop Kumar Kartikeya Dwivedi
2022-11-14 19:15 ` [PATCH bpf-next v7 19/26] bpf: Permit NULL checking pointer with non-zero fixed offset Kumar Kartikeya Dwivedi
2022-11-15 6:23 ` Alexei Starovoitov
2022-11-15 16:57 ` Kumar Kartikeya Dwivedi
2022-11-14 19:15 ` [PATCH bpf-next v7 20/26] bpf: Introduce single ownership BPF linked list API Kumar Kartikeya Dwivedi
2022-11-15 6:26 ` Alexei Starovoitov
2022-11-15 16:59 ` Kumar Kartikeya Dwivedi
2022-11-15 18:26 ` Alexei Starovoitov
2022-11-15 19:36 ` Kumar Kartikeya Dwivedi
2022-11-15 19:45 ` Alexei Starovoitov
2022-11-14 19:15 ` [PATCH bpf-next v7 21/26] bpf: Add 'release on unlock' logic for bpf_list_push_{front,back} Kumar Kartikeya Dwivedi
2022-11-15 16:52 ` Dave Marchevsky
2022-11-15 17:01 ` Kumar Kartikeya Dwivedi
2022-11-14 19:15 ` [PATCH bpf-next v7 22/26] selftests/bpf: Add __contains macro to bpf_experimental.h Kumar Kartikeya Dwivedi
2022-11-15 16:40 ` Dave Marchevsky
2022-11-14 19:15 ` [PATCH bpf-next v7 23/26] selftests/bpf: Update spinlock selftest Kumar Kartikeya Dwivedi
2022-11-14 19:15 ` [PATCH bpf-next v7 24/26] selftests/bpf: Add failure test cases for spin lock pairing Kumar Kartikeya Dwivedi
2022-11-14 19:15 ` [PATCH bpf-next v7 25/26] selftests/bpf: Add BPF linked list API tests Kumar Kartikeya Dwivedi
2022-11-14 19:15 ` [PATCH bpf-next v7 26/26] selftests/bpf: Add BTF sanity tests Kumar Kartikeya Dwivedi
2022-11-15 6:00 ` [PATCH bpf-next v7 00/26] Allocated objects, BPF linked lists patchwork-bot+netdevbpf
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20221115165122.4qp7dn26notjekwt@apollo \
--to=memxor@gmail.com \
--cc=alexei.starovoitov@gmail.com \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=davemarchevsky@meta.com \
--cc=martin.lau@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox