Re: [PATCH bpf-next v5 06/13] bpf: Prevent escaping of kptr loaded from maps

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

 



On Mon, Apr 18, 2022 at 7:46 PM Kumar Kartikeya Dwivedi
<memxor@xxxxxxxxx> wrote:
>
> On Tue, Apr 19, 2022 at 05:18:38AM IST, Joanne Koong wrote:
> > On Fri, Apr 15, 2022 at 9:04 AM Kumar Kartikeya Dwivedi
> > <memxor@xxxxxxxxx> wrote:
> > >
> > > While we can guarantee that even for unreferenced kptr, the object
> > > pointer points to being freed etc. can be handled by the verifier's
> > > exception handling (normal load patching to PROBE_MEM loads), we still
> > > cannot allow the user to pass these pointers to BPF helpers and kfunc,
> > > because the same exception handling won't be done for accesses inside
> > > the kernel. The same is true if a referenced pointer is loaded using
> > > normal load instruction. Since the reference is not guaranteed to be
> > > held while the pointer is used, it must be marked as untrusted.
> > >
> > > Hence introduce a new type flag, PTR_UNTRUSTED, which is used to mark
> > > all registers loading unreferenced and referenced kptr from BPF maps,
> > > and ensure they can never escape the BPF program and into the kernel by
> > > way of calling stable/unstable helpers.
> > To me, it seems more clear / straightforward if loads are prohibited
> > altogether and the only way to get a referenced kptr from a BPF map is
> > through the *_kptr_get function, instead of allowing loads but
> > prohibiting the loaded value from going to bpf helpers + kfuncs. To me
> > it seems like 1) using the kptr in kfuncs / helper funcs will be a
> > significant portion of use cases, 2) as a user, I think it's
> > non-intuitive that I'm able to retrieve it and get a direct reference
> > to it but not be able to use it in a kfunc/helper func, and 3) this
> > would simplify this logic in the verifier where we don't need to add
> > PTR_UNTRUSTED.
> > What are your thoughts?
> >
>
> Given this is atleast needed for the unreferenced case, so the flag needs to
> stay, but considering just referenced kptr:
>
Oh I see. I was thinking about the referenced case mostly and wasn't
considering the unreferenced kptr in map case. I agree then - we'll
need it for the unreferenced case so we might as well also have it for
the referenced case.

> 1) is true, but in many use cases just reading from the object is also enough,
> in those cases imposing the cost of kptr_get is too much, I think. If there are
> reasonable gurantees that the object won't go away, or some way to detect that
> the pointer changed (e.g. by detecting writer presence [0]), it should be safe
> to permit reads from such untrusted pointer without ensuring user holds a
> refcount. You can imagine case where you have programs attached to a callchain,
> and you stash a ref in a map in an invocation earlier in the chain, then inspect
> the data somewhere in the middle, and eventually drop the ref, etc. The fact
> that this can be made safe using the exception handling is a great feature IMO.
>
> 2) It can certainly be a bit surprising, but I think kptr_ref is already special
> enough that the user needs to carefully understand the semantics when making use
> of them. Even now, you will have to use kptr_get to be able to get a normal
> PTR_TO_BTF_ID they can pass to helpers, the untrusted pointer is for cases where
> you know what you are doing (and know that what you'll read is still valid at a
> later point, depending on how that data will be used).
>
> 3) We already need this flag, for this case and eventually also making this the
> default for majority of cases where we cannot prove PTR_TO_BTF_ID is safe (e.g.
> in tracing or LSM ctx). See [1] for some background. There are going to be a lot
> more cases going forward where dereference is safe (hence allowed) but passing
> to helpers or kfunc is not.
Gotcha. Thanks for the context.
>
>  [0]: https://lore.kernel.org/bpf/20220222082129.yivvpm6yo3474dp3@apollo.legion
>  [1]: https://lore.kernel.org/bpf/CAADnVQJF8yQgKRQH2CqXuB9JR-p3fQeiGRxB0+N_V7uTH2iOeA@xxxxxxxxxxxxxx
>
> > >
> > > In check_ptr_to_btf_access, the !type_may_be_null check to reject type
> > > flags is still correct, as apart from PTR_MAYBE_NULL, only MEM_USER,
> > > MEM_PERCPU, and PTR_UNTRUSTED may be set for PTR_TO_BTF_ID. The first
> > > two are checked inside the function and rejected using a proper error
> > > message, but we still want to allow dereference of untrusted case.
> > >
> > > Also, we make sure to inherit PTR_UNTRUSTED when chain of pointers are
> > > walked, so that this flag is never dropped once it has been set on a
> > > PTR_TO_BTF_ID (i.e. trusted to untrusted transition can only be in one
> > > direction).
> > >
> > > In convert_ctx_accesses, extend the switch case to consider untrusted
> > > PTR_TO_BTF_ID in addition to normal PTR_TO_BTF_ID for PROBE_MEM
> > > conversion for BPF_LDX.
> > >
> > > Signed-off-by: Kumar Kartikeya Dwivedi <memxor@xxxxxxxxx>
> > > ---
> > >  include/linux/bpf.h   | 10 +++++++++-
> > >  kernel/bpf/verifier.c | 35 ++++++++++++++++++++++++++++-------
> > >  2 files changed, 37 insertions(+), 8 deletions(-)
> > >
> > > diff --git a/include/linux/bpf.h b/include/linux/bpf.h
> > > index 61f83a23980f..7e2ac2a26bdb 100644
> > > --- a/include/linux/bpf.h
> > > +++ b/include/linux/bpf.h
> > > @@ -375,7 +375,15 @@ enum bpf_type_flag {
> > >         /* Indicates that the pointer argument will be released. */
> > >         PTR_RELEASE             = BIT(5 + BPF_BASE_TYPE_BITS),
> > >
> > > -       __BPF_TYPE_LAST_FLAG    = PTR_RELEASE,
> > > +       /* PTR is not trusted. This is only used with PTR_TO_BTF_ID, to mark
> > > +        * unreferenced and referenced kptr loaded from map value using a load
> > > +        * instruction, so that they can only be dereferenced but not escape the
> > > +        * BPF program into the kernel (i.e. cannot be passed as arguments to
> > > +        * kfunc or bpf helpers).
> > > +        */
> > > +       PTR_UNTRUSTED           = BIT(6 + BPF_BASE_TYPE_BITS),
> > > +
> > > +       __BPF_TYPE_LAST_FLAG    = PTR_UNTRUSTED,
> > >  };
> > >
> > >  /* Max number of base types. */
> > > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> > > index aa5c0d1c8495..3b89dc8d41ce 100644
> > > --- a/kernel/bpf/verifier.c
> > > +++ b/kernel/bpf/verifier.c
> > > @@ -567,6 +567,8 @@ static const char *reg_type_str(struct bpf_verifier_env *env,
> > >                 strncpy(prefix, "user_", 32);
> > >         if (type & MEM_PERCPU)
> > >                 strncpy(prefix, "percpu_", 32);
> > > +       if (type & PTR_UNTRUSTED)
> > > +               strncpy(prefix, "untrusted_", 32);
> > >
> > >         snprintf(env->type_str_buf, TYPE_STR_BUF_LEN, "%s%s%s",
> > >                  prefix, str[base_type(type)], postfix);
> > > @@ -3504,9 +3506,14 @@ static int map_kptr_match_type(struct bpf_verifier_env *env,
> > >                                struct bpf_reg_state *reg, u32 regno)
> > >  {
> > >         const char *targ_name = kernel_type_name(off_desc->kptr.btf, off_desc->kptr.btf_id);
> > > +       int perm_flags = PTR_MAYBE_NULL;
> > >         const char *reg_name = "";
> > >
> > > -       if (base_type(reg->type) != PTR_TO_BTF_ID || type_flag(reg->type) != PTR_MAYBE_NULL)
> > > +       /* Only unreferenced case accepts untrusted pointers */
> > > +       if (off_desc->type == BPF_MAP_OFF_DESC_TYPE_UNREF_KPTR)
> > > +               perm_flags |= PTR_UNTRUSTED;
> > > +
> > > +       if (base_type(reg->type) != PTR_TO_BTF_ID || (type_flag(reg->type) & ~perm_flags))
> > >                 goto bad_type;
> > >
> > >         if (!btf_is_kernel(reg->btf)) {
> > > @@ -3532,7 +3539,12 @@ static int map_kptr_match_type(struct bpf_verifier_env *env,
> > >  bad_type:
> > >         verbose(env, "invalid kptr access, R%d type=%s%s ", regno,
> > >                 reg_type_str(env, reg->type), reg_name);
> > > -       verbose(env, "expected=%s%s\n", reg_type_str(env, PTR_TO_BTF_ID), targ_name);
> > > +       verbose(env, "expected=%s%s", reg_type_str(env, PTR_TO_BTF_ID), targ_name);
> > > +       if (off_desc->type == BPF_MAP_OFF_DESC_TYPE_UNREF_KPTR)
> > > +               verbose(env, " or %s%s\n", reg_type_str(env, PTR_TO_BTF_ID | PTR_UNTRUSTED),
> > > +                       targ_name);
> > > +       else
> > > +               verbose(env, "\n");
> > >         return -EINVAL;
> > >  }
> > >
> > > @@ -3556,9 +3568,11 @@ static int check_map_kptr_access(struct bpf_verifier_env *env, u32 regno,
> > >                 return -EACCES;
> > >         }
> > >
> > > -       /* We cannot directly access kptr_ref */
> > > -       if (off_desc->type == BPF_MAP_OFF_DESC_TYPE_REF_KPTR) {
> > > -               verbose(env, "accessing referenced kptr disallowed\n");
> > > +       /* We only allow loading referenced kptr, since it will be marked as
> > > +        * untrusted, similar to unreferenced kptr.
> > > +        */
> > > +       if (class != BPF_LDX && off_desc->type == BPF_MAP_OFF_DESC_TYPE_REF_KPTR) {
> > > +               verbose(env, "store to referenced kptr disallowed\n");
> > >                 return -EACCES;
> > >         }
> > >
> > > @@ -3568,7 +3582,7 @@ static int check_map_kptr_access(struct bpf_verifier_env *env, u32 regno,
> > >                  * value from map as PTR_TO_BTF_ID, with the correct type.
> > >                  */
> > >                 mark_btf_ld_reg(env, cur_regs(env), value_regno, PTR_TO_BTF_ID, off_desc->kptr.btf,
> > > -                               off_desc->kptr.btf_id, PTR_MAYBE_NULL);
> > > +                               off_desc->kptr.btf_id, PTR_MAYBE_NULL | PTR_UNTRUSTED);
> > >                 val_reg->id = ++env->id_gen;
> > >         } else if (class == BPF_STX) {
> > >                 val_reg = reg_state(env, value_regno);
> > > @@ -4336,6 +4350,12 @@ static int check_ptr_to_btf_access(struct bpf_verifier_env *env,
> > >         if (ret < 0)
> > >                 return ret;
> > >
> > > +       /* If this is an untrusted pointer, all pointers formed by walking it
> > > +        * also inherit the untrusted flag.
> > > +        */
> > > +       if (type_flag(reg->type) & PTR_UNTRUSTED)
> > > +               flag |= PTR_UNTRUSTED;
> > > +
> > >         if (atype == BPF_READ && value_regno >= 0)
> > >                 mark_btf_ld_reg(env, regs, value_regno, ret, reg->btf, btf_id, flag);
> > >
> > > @@ -13054,7 +13074,7 @@ static int convert_ctx_accesses(struct bpf_verifier_env *env)
> > >                 if (!ctx_access)
> > >                         continue;
> > >
> > > -               switch (env->insn_aux_data[i + delta].ptr_type) {
> > > +               switch ((int)env->insn_aux_data[i + delta].ptr_type) {
> > >                 case PTR_TO_CTX:
> > >                         if (!ops->convert_ctx_access)
> > >                                 continue;
> > > @@ -13071,6 +13091,7 @@ static int convert_ctx_accesses(struct bpf_verifier_env *env)
> > >                         convert_ctx_access = bpf_xdp_sock_convert_ctx_access;
> > >                         break;
> > >                 case PTR_TO_BTF_ID:
> > > +               case PTR_TO_BTF_ID | PTR_UNTRUSTED:
> > >                         if (type == BPF_READ) {
> > >                                 insn->code = BPF_LDX | BPF_PROBE_MEM |
> > >                                         BPF_SIZE((insn)->code);
> > > --
> > > 2.35.1
> > >
>
> --
> Kartikeya



[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