Re: [PATCH bpf-next v2 3/7] bpf: Add bpf_dynptr_from_mem, bpf_dynptr_alloc, bpf_dynptr_put

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

 



On Tue, Apr 26, 2022 at 4:45 PM Joanne Koong <joannelkoong@xxxxxxxxx> wrote:
>
> On Thu, Apr 21, 2022 at 7:52 PM Alexei Starovoitov
> <alexei.starovoitov@xxxxxxxxx> wrote:
> >
> > On Fri, Apr 15, 2022 at 11:34:25PM -0700, Joanne Koong wrote:
> > > This patch adds 3 new APIs and the bulk of the verifier work for
> > > supporting dynamic pointers in bpf.
> > >
> > > There are different types of dynptrs. This patch starts with the most
> > > basic ones, ones that reference a program's local memory
> > > (eg a stack variable) and ones that reference memory that is dynamically
> > > allocated on behalf of the program. If the memory is dynamically
> > > allocated by the program, the program *must* free it before the program
> > > exits. This is enforced by the verifier.
> > >
> > > The added APIs are:
> > >
> > > long bpf_dynptr_from_mem(void *data, u32 size, u64 flags, struct bpf_dynptr *ptr);
> > > long bpf_dynptr_alloc(u32 size, u64 flags, struct bpf_dynptr *ptr);
> > > void bpf_dynptr_put(struct bpf_dynptr *ptr);
> > >
> > > This patch sets up the verifier to support dynptrs. Dynptrs will always
> > > reside on the program's stack frame. As such, their state is tracked
> > > in their corresponding stack slot, which includes the type of dynptr
> > > (DYNPTR_LOCAL vs. DYNPTR_MALLOC).
> > >
> > > When the program passes in an uninitialized dynptr (ARG_PTR_TO_DYNPTR |
> > > MEM_UNINIT), the stack slots corresponding to the frame pointer
> > > where the dynptr resides at are marked as STACK_DYNPTR. For helper functions
> > > that take in initialized dynptrs (such as the next patch in this series
> > > which supports dynptr reads/writes), the verifier enforces that the
> > > dynptr has been initialized by checking that their corresponding stack
> > > slots have been marked as STACK_DYNPTR. Dynptr release functions
> > > (eg bpf_dynptr_put) will clear the stack slots. The verifier enforces at
> > > program exit that there are no acquired dynptr stack slots that need
> > > to be released.
> > >
> > > There are other constraints that are enforced by the verifier as
> > > well, such as that the dynptr cannot be written to directly by the bpf
> > > program or by non-dynptr helper functions. The last patch in this series
> > > contains tests that trigger different cases that the verifier needs to
> > > successfully reject.
> > >
> > > For now, local dynptrs cannot point to referenced memory since the
> > > memory can be freed anytime. Support for this will be added as part
> > > of a separate patchset.
> > >
> > > Signed-off-by: Joanne Koong <joannelkoong@xxxxxxxxx>
> > > ---
> > >  include/linux/bpf.h            |  68 +++++-
> > >  include/linux/bpf_verifier.h   |  28 +++
> > >  include/uapi/linux/bpf.h       |  44 ++++
> > >  kernel/bpf/helpers.c           | 110 ++++++++++
> > >  kernel/bpf/verifier.c          | 372 +++++++++++++++++++++++++++++++--
> > >  scripts/bpf_doc.py             |   2 +
> > >  tools/include/uapi/linux/bpf.h |  44 ++++
> > >  7 files changed, 654 insertions(+), 14 deletions(-)
> > >
> [...]
> > > +     for (i = 0; i < BPF_REG_SIZE; i++) {
> > > +             state->stack[spi].slot_type[i] = STACK_INVALID;
> > > +             state->stack[spi - 1].slot_type[i] = STACK_INVALID;
> > > +     }
> > > +
> > > +     state->stack[spi].spilled_ptr.dynptr.type = 0;
> > > +     state->stack[spi - 1].spilled_ptr.dynptr.type = 0;
> > > +
> > > +     state->stack[spi].spilled_ptr.dynptr.first_slot = 0;
> > > +
> > > +     return 0;
> > > +}
> > > +
> > > +static int mark_as_dynptr_data(struct bpf_verifier_env *env, const struct bpf_func_proto *fn,
> > > +                            struct bpf_reg_state *regs)
> > > +{
> > > +     struct bpf_func_state *state = cur_func(env);
> > > +     struct bpf_reg_state *reg, *mem_reg = NULL;
> > > +     enum bpf_arg_type arg_type;
> > > +     u64 mem_size;
> > > +     u32 nr_slots;
> > > +     int i, spi;
> > > +
> > > +     /* We must protect against the case where a program tries to do something
> > > +      * like this:
> > > +      *
> > > +      * bpf_dynptr_from_mem(&ptr, sizeof(ptr), 0, &local);
> > > +      * bpf_dynptr_alloc(16, 0, &ptr);
> > > +      * bpf_dynptr_write(&local, 0, corrupt_data, sizeof(ptr));
> > > +      *
> > > +      * If ptr is a variable on the stack, we must mark the stack slot as
> > > +      * dynptr data when a local dynptr to it is created.
> > > +      */
> > > +     for (i = 0; i < MAX_BPF_FUNC_REG_ARGS; i++) {
> > > +             arg_type = fn->arg_type[i];
> > > +             reg = &regs[BPF_REG_1 + i];
> > > +
> > > +             if (base_type(arg_type) == ARG_PTR_TO_MEM) {
> > > +                     if (base_type(reg->type) == PTR_TO_STACK) {
> > > +                             mem_reg = reg;
> > > +                             continue;
> > > +                     }
> > > +                     /* if it's not a PTR_TO_STACK, then we don't need to
> > > +                      * mark anything since it can never be used as a dynptr.
> > > +                      * We can just return here since there will always be
> > > +                      * only one ARG_PTR_TO_MEM in fn.
> > > +                      */
> > > +                     return 0;
> >
> > I think the assumption here that NO_OBJ_REF flag reduces ARG_PTR_TO_MEM
> > to be stack, a pointer to packet or map value, right?
> > Since dynptr can only be on stack, map value and packet memory
> > cannot be used to store dynptr.
> > So bpf_dynptr_alloc(16, 0, &ptr); is not possible where &ptr
> > points to packet or map value?
> > So that's what 'return 0' above doing?
> > That's probably ok.
> >
> > Just thinking out loud:
> > bpf_dynptr_from_mem(&ptr, sizeof(ptr), 0, &local);
> > where &local is a dynptr on stack, but &ptr is a map value?
> > The lifetime of the memory tracked by dynptr is not going
> > to outlive program execution.
> > Probably ok too.
> >
> After our conversation, I will remove local dynptrs for now.


bpf_dynptr_from_mem(&ptr, sizeof(ptr), 0, &local) where ptr is
PTR_TO_MAP_VALUE is still ok. So it's only a special case of ptr being
PTR_TO_STACK that will be disallowed, right? It's still LOCAL type of
dynptr, it just can't point to memory on the stack.

> > > +             } else if (arg_type_is_mem_size(arg_type)) {
> > > +                     mem_size = roundup(reg->var_off.value, BPF_REG_SIZE);
> > > +             }
> > > +     }
> > > +
> > > +     if (!mem_reg || !mem_size) {
> > > +             verbose(env, "verifier internal error: invalid ARG_PTR_TO_MEM args for %s\n", __func__);
> > > +             return -EFAULT;
> > > +     }
> > > +
> > > +     spi = get_spi(mem_reg->off);
> > > +     if (!is_spi_bounds_valid(state, spi, mem_size)) {
> > > +             verbose(env, "verifier internal error: variable not initialized on stack in %s\n", __func__);
> > > +             return -EFAULT;
> > > +     }
> > > +
> > > +     nr_slots = mem_size / BPF_REG_SIZE;
> > > +     for (i = 0; i < nr_slots; i++)
> > > +             state->stack[spi - i].spilled_ptr.is_dynptr_data = true;
> >

[...]



[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