Re: [PATCH bpf] bpf: fix tracking of stack size for var-off access

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

 



On Tue, Nov 21, 2023 at 12:16 PM Hao Sun <sunhao.th@xxxxxxxxx> wrote:
>
> On Tue, Nov 21, 2023 at 1:46 AM Andrii Nakryiko
> <andrii.nakryiko@xxxxxxxxx> wrote:
> >
> >
> > It *feels* like this stack depth update *and* growing allocated stack
> > slots should happen somewhere in check_stack_access_within_bounds() or
> > right after it. It shouldn't matter whether we read or write to the
> > stack slot: either way that slot becomes part of the verifier state
> > that we should take into account during state comparison. Eduard not
> > so long ago added a change that allows reading STACK_INVALID slots, so
> > it's completely valid to read something that was never written to (and
> > so grow_stack_state() wasn't called for that slot, as it is
> > implemented right now). So I think we should fix that.
> >
>
> Agree. The following cases are currently confusing to me.

Like Hao, I'm also confused about when reads from unitialized stack slots are
supposed to be allowed and when they're not. For example, the "uninitialized
stack1" test ([0]) seems to check that at least some type of uninitialized read
is not permitted. The reason why the respective access is rejected and this
test currently passes is the following check in check_stack_range_initialized:
[1]. We summarily reject the access to slots beyond state->allocated_stack. If
we were to not reject them there, and instead treat the slots as STACK_INVALID,
then the test's access would be allowed just below, through the combination of
env->allow_uninit_stack and `clobber`.

So, assuming that this tests (and a few others) are sane, Andrii's suggestion
of calling grow_stack_state()/update_stack_depth() in
check_stack_access_within_bounds() does not immediately work: doing so
would change
the behavior in check_stack_range_initialized() and allow the access.

On the other hand, perhaps the test is not sane and the access should be
permitted, in the spirit of allowing reads of uninitialized stack? Perhaps the
different treatment of slots beyond state->allocated_stack and STACK_INVALID
slots is not intentional. Though, I am fairly confused about the idea of
allowing such reads in general - don't they allow leaking of kernel memory from
the uninit stack to user space? Even if restricted to root, is the leak ok?

To also state the obvious - I'm happy to continue working on this patch and fix
a bug I've caused. However, if someone who actually knows the deal wants to
take over, I'm certainly not attached to anything :).

[0] https://github.com/torvalds/linux/blob/98b1cc82c4affc16f5598d4fa14b1858671b2263/tools/testing/selftests/bpf/progs/verifier_basic_stack.c#L28-L44
[1] https://github.com/torvalds/linux/blob/98b1cc82c4affc16f5598d4fa14b1858671b2263/kernel/bpf/verifier.c#L7280


>
> The verifier accepts the following program, which goes from #4 to #8
> and directly read the stack at runtime without any previous write:
> func#0 @0
> 0: R1=ctx() R10=fp0
> 0: (bf) r6 = r10                      ; R6_w=fp0 R10=fp0
> 1: (85) call bpf_get_prandom_u32#7    ; R0_w=scalar()
> 2: (bf) r3 = r0                       ; R0_w=scalar(id=1) R3_w=scalar(id=1)
> 3: (bf) r8 = r0                       ; R0_w=scalar(id=1) R8_w=scalar(id=1)
> 4: (4e) if w0 & w3 goto pc+3          ; R0_w=scalar(id=1) R3_w=scalar(id=1)
> 5: (63) *(u32 *)(r6 -196) = r3        ; R3_w=scalar(id=1) R6_w=fp0
> fp-200=mmmm????
> 6: (18) r7 = 0x19                     ; R7=25
> 8: (61) r7 = *(u32 *)(r6 -200)        ; R6=fp0
> R7_w=scalar(smin=0,smax=umax=0xffffffff,var_off=(0x0; 0xffffffff))
> fp-200=mmmm????
> 9: (95) exit
>
> from 4 to 8: safe
> verification time 358 usec
> stack depth 200
> processed 10 insns (limit 1000000) max_states_per_insn 0 total_states
> 1 peak_states 1 mark_read 1
>
> The state is pruned, because of this:
> static bool stacksafe(...)
>          ....
>          if (env->allow_uninit_stack &&
>              old->stack[spi].slot_type[i % BPF_REG_SIZE] == STACK_MISC)
>              continue;
>
> Yet, the sample direct read would be rejected:
>
> func#0 @0
> 0: R1=ctx() R10=fp0
> 0: (bf) r6 = r10                      ; R6_w=fp0 R10=fp0
> 1: (61) r7 = *(u32 *)(r6 -200)
> invalid read from stack R6 off=-200 size=4
>
> Eduard, you added support for reading uninit slots, should we also add something
> like the following:
>
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 8c2d31aa3d31..aa861d2da240 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -6446,7 +6446,7 @@ static int check_stack_slot_within_bounds(int off,
>  {
>         int min_valid_off;
>
> -       if (t == BPF_WRITE)
> +       if (t == BPF_WRITE || env->allow_uninit_stack)
>                 min_valid_off = -MAX_BPF_STACK;
>         else
>                 min_valid_off = -state->allocated_stack;





[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