Re: [PATCH bpf-next 05/11] bpf: allow specifying a set of BTF IDs for helper arguments

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



On Wed, 9 Sep 2020 at 05:47, Andrii Nakryiko <andrii.nakryiko@xxxxxxxxx> wrote:
>
> On Fri, Sep 4, 2020 at 4:30 AM Lorenz Bauer <lmb@xxxxxxxxxxxxxx> wrote:
> >
> > Function prototypes using ARG_PTR_TO_BTF_ID currently use two ways to signal
> > which BTF IDs are acceptable. First, bpf_func_proto.btf_id is an array of
> > IDs, one for each argument. This array is only accessed up to the highest
> > numbered argument that uses ARG_PTR_TO_BTF_ID and may therefore be less than
> > five arguments long. It usually points at a BTF_ID_LIST. Second, check_btf_id
> > is a function pointer that is called by the verifier if present. It gets the
> > actual BTF ID of the register, and the argument number we're currently checking.
> > It turns out that the only user check_arg_btf_id ignores the argument, and is
> > simply used to check whether the BTF ID matches one of the socket types.
> >
> > Replace both of these mechanisms with explicit btf_id_sets for each argument
> > in a function proto. The verifier can now check that a PTR_TO_BTF_ID is one
> > of several IDs, and the code that does the type checking becomes simpler.
> >
> > Add a small optimisation to btf_set_contains for the common case of a set with
> > a single entry.
> >
> > Signed-off-by: Lorenz Bauer <lmb@xxxxxxxxxxxxxx>
> > ---
>
> You are replacing a more generic and powerful capability with a more
> restricted one because no one is yet using a generic one fully. It
> might be ok and we'll never need a more generic way to check BTF IDs.
> But it will be funny if we will be adding this back soon because a
> static set of BTF IDs don't cut it for some cases :)
>
> I don't mind this change, but I wonder what others think about this.
>
> >  include/linux/bpf.h            | 22 ++++++++++---------
> >  kernel/bpf/bpf_inode_storage.c |  8 +++----
> >  kernel/bpf/btf.c               | 22 ++++++-------------
> >  kernel/bpf/stackmap.c          |  5 +++--
> >  kernel/bpf/verifier.c          | 39 +++++++++++++---------------------
> >  kernel/trace/bpf_trace.c       | 15 +++++++------
> >  net/core/bpf_sk_storage.c      | 10 +++++----
> >  net/core/filter.c              | 31 ++++++++++-----------------
> >  net/ipv4/bpf_tcp_ca.c          | 24 +++++++++------------
> >  9 files changed, 76 insertions(+), 100 deletions(-)
> >
>
> [...]
>
> > diff --git a/kernel/bpf/bpf_inode_storage.c b/kernel/bpf/bpf_inode_storage.c
> > index 75be02799c0f..d447d2655cce 100644
> > --- a/kernel/bpf/bpf_inode_storage.c
> > +++ b/kernel/bpf/bpf_inode_storage.c
> > @@ -249,9 +249,9 @@ const struct bpf_map_ops inode_storage_map_ops = {
> >         .map_owner_storage_ptr = inode_storage_ptr,
> >  };
> >
> > -BTF_ID_LIST(bpf_inode_storage_btf_ids)
> > -BTF_ID_UNUSED
> > +BTF_SET_START(bpf_inode_storage_btf_ids)
> >  BTF_ID(struct, inode)
> > +BTF_SET_END(bpf_inode_storage_btf_ids)
>
> with your change single-element BTF ID set becomes a very common case,
> so having a simple macro that combines BTF_SET_START + BTF_ID +
> BTF_SET_END in one simple macro would be useful, I think

Good idea. If Martin's idea pans out I'll add something similar for
BTF_ID_LIST instead.

>
> >
> >  const struct bpf_func_proto bpf_inode_storage_get_proto = {
> >         .func           = bpf_inode_storage_get,
> > @@ -259,9 +259,9 @@ const struct bpf_func_proto bpf_inode_storage_get_proto = {
> >         .ret_type       = RET_PTR_TO_MAP_VALUE_OR_NULL,
> >         .arg1_type      = ARG_CONST_MAP_PTR,
> >         .arg2_type      = ARG_PTR_TO_BTF_ID,
> > +       .arg2_btf_ids   = &bpf_inode_storage_btf_ids,
> >         .arg3_type      = ARG_PTR_TO_MAP_VALUE_OR_NULL,
> >         .arg4_type      = ARG_ANYTHING,
> > -       .btf_id         = bpf_inode_storage_btf_ids,
> >  };
> >
>
> [...]
>
> > @@ -4065,26 +4066,21 @@ static int check_func_arg(struct bpf_verifier_env *env, u32 arg,
> >         }
> >
> >         if (type == PTR_TO_BTF_ID) {
> > -               bool ids_match = false;
> > +               if (fn->arg_btf_ids[arg])
> > +                       btf_ids = fn->arg_btf_ids[arg];
>
> nit: no need for the if part, just assign directly, even if its NULL

There is a purpose here: btf_ids can be inferred from the arg_type (in
this set PTR_TO_SOCKET). However, function prototype declarations take
precedence over arg_type.

Maybe I should add a comment here? Or prevent this outright? There is
currently no user of this.

>
> >
> > -               if (!fn->check_btf_id) {
> > -                       if (reg->btf_id != meta->btf_id) {
> > -                               ids_match = btf_struct_ids_match(&env->log, reg->off, reg->btf_id,
> > -                                                                meta->btf_id);
> > -                               if (!ids_match) {
> > -                                       verbose(env, "Helper has type %s got %s in R%d\n",
> > -                                               kernel_type_name(meta->btf_id),
> > -                                               kernel_type_name(reg->btf_id), regno);
> > -                                       return -EACCES;
> > -                               }
> > -                       }
> > -               } else if (!fn->check_btf_id(reg->btf_id, arg)) {
> > -                       verbose(env, "Helper does not support %s in R%d\n",
> > -                               kernel_type_name(reg->btf_id), regno);
> > +               if (!btf_ids) {
> > +                       verbose(env, "verifier internal error: missing BTF IDs\n");
> > +                       return -EFAULT;
> > +               }
> >
> > +               if (!btf_struct_ids_match(&env->log, reg->off, reg->btf_id,
> > +                                         btf_ids)) {
> > +                       verbose(env, "R%d has incompatible type %s\n", regno,
> > +                               kernel_type_name(reg->btf_id));
> >                         return -EACCES;
> >                 }
> > -               if ((reg->off && !ids_match) || !tnum_is_const(reg->var_off) || reg->var_off.value) {
> > +               if (!tnum_is_const(reg->var_off) || reg->var_off.value) {
> >                         verbose(env, "R%d is a pointer to in-kernel struct with non-zero offset\n",
> >                                 regno);
> >                         return -EACCES;
>
> [...]



-- 
Lorenz Bauer  |  Systems Engineer
6th Floor, County Hall/The Riverside Building, SE1 7PB, UK

www.cloudflare.com



[Index of Archives]     [Linux Samsung SoC]     [Linux Rockchip SoC]     [Linux Actions SoC]     [Linux for Synopsys ARC Processors]     [Linux NFS]     [Linux NILFS]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]


  Powered by Linux