1. 21 Jan, 2023 1 commit
    • Kumar Kartikeya Dwivedi's avatar
      bpf: Fix state pruning for STACK_DYNPTR stack slots · d6fefa11
      Kumar Kartikeya Dwivedi authored
      The root of the problem is missing liveness marking for STACK_DYNPTR
      slots. This leads to all kinds of problems inside stacksafe.
      
      The verifier by default inside stacksafe ignores spilled_ptr in stack
      slots which do not have REG_LIVE_READ marks. Since this is being checked
      in the 'old' explored state, it must have already done clean_live_states
      for this old bpf_func_state. Hence, it won't be receiving any more
      liveness marks from to be explored insns (it has received REG_LIVE_DONE
      marking from liveness point of view).
      
      What this means is that verifier considers that it's safe to not compare
      the stack slot if was never read by children states. While liveness
      marks are usually propagated correctly following the parentage chain for
      spilled registers (SCALAR_VALUE and PTR_* types), the same is not the
      case for STACK_DYNPTR.
      
      clean_live_states hence simply rewrites these stack slots to the type
      STACK_INVALID since it sees no REG_LIVE_READ marks.
      
      The end result is that we will never see STACK_DYNPTR slots in explored
      state. Even if verifier was conservatively matching !REG_LIVE_READ
      slots, very next check continuing the stacksafe loop on seeing
      STACK_INVALID would again prevent further checks.
      
      Now as long as verifier stores an explored state which we can compare to
      when reaching a pruning point, we can abuse this bug to make verifier
      prune search for obviously unsafe paths using STACK_DYNPTR slots
      thinking they are never used hence safe.
      
      Doing this in unprivileged mode is a bit challenging. add_new_state is
      only set when seeing BPF_F_TEST_STATE_FREQ (which requires privileges)
      or when jmps_processed difference is >= 2 and insn_processed difference
      is >= 8. So coming up with the unprivileged case requires a little more
      work, but it is still totally possible. The test case being discussed
      below triggers the heuristic even in unprivileged mode.
      
      However, it no longer works since commit
      8addbfc7 ("bpf: Gate dynptr API behind CAP_BPF").
      
      Let's try to study the test step by step.
      
      Consider the following program (C style BPF ASM):
      
      0  r0 = 0;
      1  r6 = &ringbuf_map;
      3  r1 = r6;
      4  r2 = 8;
      5  r3 = 0;
      6  r4 = r10;
      7  r4 -= -16;
      8  call bpf_ringbuf_reserve_dynptr;
      9  if r0 == 0 goto pc+1;
      10 goto pc+1;
      11 *(r10 - 16) = 0xeB9F;
      12 r1 = r10;
      13 r1 -= -16;
      14 r2 = 0;
      15 call bpf_ringbuf_discard_dynptr;
      16 r0 = 0;
      17 exit;
      
      We know that insn 12 will be a pruning point, hence if we force
      add_new_state for it, it will first verify the following path as
      safe in straight line exploration:
      0 1 3 4 5 6 7 8 9 -> 10 -> (12) 13 14 15 16 17
      
      Then, when we arrive at insn 12 from the following path:
      0 1 3 4 5 6 7 8 9 -> 11 (12)
      
      We will find a state that has been verified as safe already at insn 12.
      Since register state is same at this point, regsafe will pass. Next, in
      stacksafe, for spi = 0 and spi = 1 (location of our dynptr) is skipped
      seeing !REG_LIVE_READ. The rest matches, so stacksafe returns true.
      Next, refsafe is also true as reference state is unchanged in both
      states.
      
      The states are considered equivalent and search is pruned.
      
      Hence, we are able to construct a dynptr with arbitrary contents and use
      the dynptr API to operate on this arbitrary pointer and arbitrary size +
      offset.
      
      To fix this, first define a mark_dynptr_read function that propagates
      liveness marks whenever a valid initialized dynptr is accessed by dynptr
      helpers. REG_LIVE_WRITTEN is marked whenever we initialize an
      uninitialized dynptr. This is done in mark_stack_slots_dynptr. It allows
      screening off mark_reg_read and not propagating marks upwards from that
      point.
      
      This ensures that we either set REG_LIVE_READ64 on both dynptr slots, or
      none, so clean_live_states either sets both slots to STACK_INVALID or
      none of them. This is the invariant the checks inside stacksafe rely on.
      
      Next, do a complete comparison of both stack slots whenever they have
      STACK_DYNPTR. Compare the dynptr type stored in the spilled_ptr, and
      also whether both form the same first_slot. Only then is the later path
      safe.
      
      Fixes: 97e03f52 ("bpf: Add verifier support for dynptrs")
      Acked-by: default avatarEduard Zingerman <eddyz87@gmail.com>
      Signed-off-by: default avatarKumar Kartikeya Dwivedi <memxor@gmail.com>
      Link: https://lore.kernel.org/r/20230121002241.2113993-2-memxor@gmail.comSigned-off-by: default avatarAlexei Starovoitov <ast@kernel.org>
      d6fefa11
  2. 20 Jan, 2023 4 commits
  3. 18 Jan, 2023 3 commits
  4. 17 Jan, 2023 1 commit
  5. 15 Jan, 2023 14 commits
  6. 13 Jan, 2023 3 commits
  7. 12 Jan, 2023 14 commits