In message: [linux-yocto][v5.15/standard/base][patch] bpf: Fix accesses to 
uninit stack slots [ Upstream commit 6b4a64bafd107e521c01eec3453ce94a3fb38529 ]
on 08/08/2024 libo.chen...@windriver.com wrote:

> From: Andrei Matei <andreimat...@gmail.com>
> 
> Privileged programs are supposed to be able to read uninitialized stack
> memory (ever since 6715df8d5) but, before this patch, these accesses
> were permitted inconsistently. In particular, accesses were permitted
> above state->allocated_stack, but not below it. In other words, if the
> stack was already "large enough", the access was permitted, but
> otherwise the access was rejected instead of being allowed to "grow the
> stack". This undesired rejection was happening in two places:
> - in check_stack_slot_within_bounds()
> - in check_stack_range_initialized()
> This patch arranges for these accesses to be permitted. A bunch of tests
> that were relying on the old rejection had to change; all of them were
> changed to add also run unprivileged, in which case the old behavior
> persists. One tests couldn't be updated - global_func16 - because it
> can't run unprivileged for other reasons.
> 
> This patch also fixes the tracking of the stack size for variable-offset
> reads. This second fix is bundled in the same commit as the first one
> because they're inter-related. Before this patch, writes to the stack
> using registers containing a variable offset (as opposed to registers
> with fixed, known values) were not properly contributing to the
> function's needed stack size. As a result, it was possible for a program
> to verify, but then to attempt to read out-of-bounds data at runtime
> because a too small stack had been allocated for it.
> 
> Each function tracks the size of the stack it needs in
> bpf_subprog_info.stack_depth, which is maintained by
> update_stack_depth(). For regular memory accesses, check_mem_access()
> was calling update_state_depth() but it was passing in only the fixed
> part of the offset register, ignoring the variable offset. This was
> incorrect; the minimum possible value of that register should be used
> instead.
> 
> This tracking is now fixed by centralizing the tracking of stack size in
> grow_stack_state(), and by lifting the calls to grow_stack_state() to
> check_stack_access_within_bounds() as suggested by Andrii. The code is
> now simpler and more convincingly tracks the correct maximum stack size.
> check_stack_range_initialized() can now rely on enough stack having been
> allocated for the access; this helps with the fix for the first issue.
> 
> A few tests were changed to also check the stack depth computation. The
> one that fails without this patch is 
> verifier_var_off:stack_write_priv_vs_unpriv.
> 
> Fixes: 01f810ace9ed3 ("bpf: Allow variable-offset stack access")
> Reported-by: Hao Sun <sunhao...@gmail.com>
> Signed-off-by: Andrei Matei <andreimat...@gmail.com>
> Signed-off-by: Andrii Nakryiko <and...@kernel.org>
> Acked-by: Andrii Nakryiko <and...@kernel.org>
> Link: 
> https://lore.kernel.org/bpf/20231208032519.260451-3-andreimat...@gmail.com
> 
> Closes: 
> https://lore.kernel.org/bpf/CABWLsev9g8UP_c3a=1qbuzui20tgouxou07fpf-5flvhoko...@mail.gmail.com/
> Signed-off-by: Sasha Levin <sas...@kernel.org>
> 
> CVE-2023-52452
> The following tests are not backported since they don't exist in this version.
> tools/testing/selftests/bpf/progs/verifier_basic_stack.c
> tools/testing/selftests/bpf/progs/verifier_int_ptr.c
> tools/testing/selftests/bpf/progs/verifier_raw_stack.c
> tools/testing/selftests/bpf/progs/verifier_var_off.c

merged.

Bruce

> Signed-off-by: Libo Chen <libo.chen...@windriver.com>
> ---
>  kernel/bpf/verifier.c                         | 61 +++++++------------
>  .../selftests/bpf/progs/test_global_func16.c  |  1 +
>  .../selftests/bpf/verifier/atomic_cmpxchg.c   | 11 ----
>  tools/testing/selftests/bpf/verifier/calls.c  |  4 +-
>  4 files changed, 27 insertions(+), 50 deletions(-)
> 
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 88b38db5f626..df30d7be19aa 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -846,7 +846,10 @@ static int resize_reference_state(struct bpf_func_state 
> *state, size_t n)
>       return 0;
>  }
>  
> -static int grow_stack_state(struct bpf_func_state *state, int size)
> +/* Possibly update state->allocated_stack to be at least size bytes. Also
> + * possibly update the function's high-water mark in its bpf_subprog_info.
> + */
> +static int grow_stack_state(struct bpf_verifier_env *env, struct 
> bpf_func_state *state, int size)
>  {
>       size_t old_n = state->allocated_stack / BPF_REG_SIZE, n = size / 
> BPF_REG_SIZE;
>  
> @@ -858,6 +861,11 @@ static int grow_stack_state(struct bpf_func_state 
> *state, int size)
>               return -ENOMEM;
>  
>       state->allocated_stack = size;
> +
> +    /* update known max for given subprogram */
> +    if (env->subprog_info[state->subprogno].stack_depth < size)
> +        env->subprog_info[state->subprogno].stack_depth = size;
> +
>       return 0;
>  }
>  
> @@ -2839,9 +2847,6 @@ static int check_stack_write_fixed_off(struct 
> bpf_verifier_env *env,
>       struct bpf_reg_state *reg = NULL;
>       u32 dst_reg = insn->dst_reg;
>  
> -     err = grow_stack_state(state, round_up(slot + 1, BPF_REG_SIZE));
> -     if (err)
> -             return err;
>       /* caller checked that off % size == 0 and -MAX_BPF_STACK <= off < 0,
>        * so it's aligned access and [off, off + size) are within stack limits
>        */
> @@ -2990,11 +2995,6 @@ static int check_stack_write_var_off(struct 
> bpf_verifier_env *env,
>       if (value_reg && register_is_null(value_reg))
>               writing_zero = true;
>  
> -     err = grow_stack_state(state, round_up(-min_off, BPF_REG_SIZE));
> -     if (err)
> -             return err;
> -
> -
>       /* Variable offset writes destroy any spilled pointers in range. */
>       for (i = min_off; i < max_off; i++) {
>               u8 new_type, *stype;
> @@ -3840,20 +3840,6 @@ static int check_ptr_alignment(struct bpf_verifier_env 
> *env,
>                                          strict);
>  }
>  
> -static int update_stack_depth(struct bpf_verifier_env *env,
> -                           const struct bpf_func_state *func,
> -                           int off)
> -{
> -     u16 stack = env->subprog_info[func->subprogno].stack_depth;
> -
> -     if (stack >= -off)
> -             return 0;
> -
> -     /* update known max for given subprogram */
> -     env->subprog_info[func->subprogno].stack_depth = -off;
> -     return 0;
> -}
> -
>  /* starting from main bpf function walk all instructions of the function
>   * and recursively walk all callees that given function can call.
>   * Ignore jump and exit insns.
> @@ -4276,13 +4262,14 @@ static int check_ptr_to_map_access(struct 
> bpf_verifier_env *env,
>   * The minimum valid offset is -MAX_BPF_STACK for writes, and
>   * -state->allocated_stack for reads.
>   */
> -static int check_stack_slot_within_bounds(int off,
> +static int check_stack_slot_within_bounds(struct bpf_verifier_env *env,
> +                      int off,
>                                         struct bpf_func_state *state,
>                                         enum bpf_access_type t)
>  {
>       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;
> @@ -4331,7 +4318,7 @@ static int check_stack_access_within_bounds(
>               max_off = reg->smax_value + off + access_size;
>       }
>  
> -     err = check_stack_slot_within_bounds(min_off, state, type);
> +     err = check_stack_slot_within_bounds(env, min_off, state, type);
>       if (!err && max_off > 0)
>               err = -EINVAL; /* out of stack access into non-negative offsets 
> */
>       if (!err && access_size < 0)
> @@ -4351,8 +4338,9 @@ static int check_stack_access_within_bounds(
>                       verbose(env, "invalid variable-offset%s stack R%d 
> var_off=%s size=%d\n",
>                               err_extra, regno, tn_buf, access_size);
>               }
> +             return err;
>       }
> -     return err;
> +     return grow_stack_state(env, state, round_up(-min_off, BPF_REG_SIZE));
>  }
>  
>  /* check whether memory at (regno + off) is accessible for t = (read | write)
> @@ -4367,7 +4355,6 @@ static int check_mem_access(struct bpf_verifier_env 
> *env, int insn_idx, u32 regn
>  {
>       struct bpf_reg_state *regs = cur_regs(env);
>       struct bpf_reg_state *reg = regs + regno;
> -     struct bpf_func_state *state;
>       int size, err = 0;
>  
>       size = bpf_size_to_bytes(bpf_size);
> @@ -4500,11 +4487,6 @@ static int check_mem_access(struct bpf_verifier_env 
> *env, int insn_idx, u32 regn
>               if (err)
>                       return err;
>  
> -             state = func(env, reg);
> -             err = update_stack_depth(env, state, off);
> -             if (err)
> -                     return err;
> -
>               if (t == BPF_READ)
>                       err = check_stack_read(env, regno, off, size,
>                                              value_regno);
> @@ -4698,7 +4680,8 @@ static int check_atomic(struct bpf_verifier_env *env, 
> int insn_idx, struct bpf_i
>  
>  /* When register 'regno' is used to read the stack (either directly or 
> through
>   * a helper function) make sure that it's within stack boundary and, 
> depending
> - * on the access type, that all elements of the stack are initialized.
> + * on the access type and privileges, that all elements of the stack are
> + * initialized.
>   *
>   * 'off' includes 'regno->off', but not its dynamic part (if any).
>   *
> @@ -4781,8 +4764,11 @@ static int check_stack_range_initialized(
>  
>               slot = -i - 1;
>               spi = slot / BPF_REG_SIZE;
> -             if (state->allocated_stack <= slot)
> -                     goto err;
> +             if (state->allocated_stack <= slot) {
> +                     verbose(env, "verifier bug: allocated_stack too small");
> +                     return -EFAULT;
> +             }
> +
>               stype = &state->stack[spi].slot_type[slot % BPF_REG_SIZE];
>               if (*stype == STACK_MISC)
>                       goto mark;
> @@ -4810,7 +4796,6 @@ static int check_stack_range_initialized(
>                       goto mark;
>               }
>  
> -err:
>               if (tnum_is_const(reg->var_off)) {
>                       verbose(env, "invalid%s read from stack R%d off %d+%d 
> size %d\n",
>                               err_extra, regno, min_off, i - min_off, 
> access_size);
> @@ -4830,7 +4815,7 @@ static int check_stack_range_initialized(
>                             state->stack[spi].spilled_ptr.parent,
>                             REG_LIVE_READ64);
>       }
> -     return update_stack_depth(env, state, min_off);
> +     return 0;
>  }
>  
>  static int check_helper_mem_access(struct bpf_verifier_env *env, int regno,
> diff --git a/tools/testing/selftests/bpf/progs/test_global_func16.c 
> b/tools/testing/selftests/bpf/progs/test_global_func16.c
> index 0312d1e8d8c0..870bf2f148fc 100644
> --- a/tools/testing/selftests/bpf/progs/test_global_func16.c
> +++ b/tools/testing/selftests/bpf/progs/test_global_func16.c
> @@ -12,6 +12,7 @@ __noinline int foo(int (*arr)[10])
>  }
>  
>  SEC("cgroup_skb/ingress")
> +__success
>  int test_cls(struct __sk_buff *skb)
>  {
>       int array[10];
> diff --git a/tools/testing/selftests/bpf/verifier/atomic_cmpxchg.c 
> b/tools/testing/selftests/bpf/verifier/atomic_cmpxchg.c
> index 6fb52d8cfd88..e04116b94d77 100644
> --- a/tools/testing/selftests/bpf/verifier/atomic_cmpxchg.c
> +++ b/tools/testing/selftests/bpf/verifier/atomic_cmpxchg.c
> @@ -85,17 +85,6 @@
>       .result = REJECT,
>       .errstr = "!read_ok",
>  },
> -{
> -     "Can't use cmpxchg on uninit memory",
> -     .insns = {
> -             BPF_MOV64_IMM(BPF_REG_0, 3),
> -             BPF_MOV64_IMM(BPF_REG_2, 4),
> -             BPF_ATOMIC_OP(BPF_DW, BPF_CMPXCHG, BPF_REG_10, BPF_REG_2, -8),
> -             BPF_EXIT_INSN(),
> -     },
> -     .result = REJECT,
> -     .errstr = "invalid read from stack",
> -},
>  {
>       "BPF_W cmpxchg should zero top 32 bits",
>       .insns = {
> diff --git a/tools/testing/selftests/bpf/verifier/calls.c 
> b/tools/testing/selftests/bpf/verifier/calls.c
> index 5d1e01d54a82..a8e05628b7a4 100644
> --- a/tools/testing/selftests/bpf/verifier/calls.c
> +++ b/tools/testing/selftests/bpf/verifier/calls.c
> @@ -1247,7 +1247,9 @@
>       .prog_type = BPF_PROG_TYPE_XDP,
>       .fixup_map_hash_8b = { 23 },
>       .result = REJECT,
> -     .errstr = "invalid read from stack R7 off=-16 size=8",
> +     .errstr = "R0 invalid mem access 'scalar'",
> +     .result_unpriv = REJECT,
> +     .errstr_unpriv = "invalid read from stack R7 off=-16 size=8",
>  },
>  {
>       "calls: two calls that receive map_value via arg=ptr_stack_of_caller. 
> test1",
> -- 
> 2.25.1
> 
-=-=-=-=-=-=-=-=-=-=-=-
Links: You receive all messages sent to this group.
View/Reply Online (#14228): 
https://lists.yoctoproject.org/g/linux-yocto/message/14228
Mute This Topic: https://lists.yoctoproject.org/mt/107786212/21656
Group Owner: linux-yocto+ow...@lists.yoctoproject.org
Unsubscribe: https://lists.yoctoproject.org/g/linux-yocto/unsub 
[arch...@mail-archive.com]
-=-=-=-=-=-=-=-=-=-=-=-

Reply via email to