• Daniel Borkmann's avatar
    bpf: Fix leakage under speculation on mispredicted branches · 9183671a
    Daniel Borkmann authored
    The verifier only enumerates valid control-flow paths and skips paths that
    are unreachable in the non-speculative domain. And so it can miss issues
    under speculative execution on mispredicted branches.
    
    For example, a type confusion has been demonstrated with the following
    crafted program:
    
      // r0 = pointer to a map array entry
      // r6 = pointer to readable stack slot
      // r9 = scalar controlled by attacker
      1: r0 = *(u64 *)(r0) // cache miss
      2: if r0 != 0x0 goto line 4
      3: r6 = r9
      4: if r0 != 0x1 goto line 6
      5: r9 = *(u8 *)(r6)
      6: // leak r9
    
    Since line 3 runs iff r0 == 0 and line 5 runs iff r0 == 1, the verifier
    concludes that the pointer dereference on line 5 is safe. But: if the
    attacker trains both the branches to fall-through, such that the following
    is speculatively executed ...
    
      r6 = r9
      r9 = *(u8 *)(r6)
      // leak r9
    
    ... then the program will dereference an attacker-controlled value and could
    leak its content under speculative execution via side-channel. This requires
    to mistrain the branch predictor, which can be rather tricky, because the
    branches are mutually exclusive. However such training can be done at
    congruent addresses in user space using different branches that are not
    mutually exclusive. That is, by training branches in user space ...
    
      A:  if r0 != 0x0 goto line C
      B:  ...
      C:  if r0 != 0x0 goto line D
      D:  ...
    
    ... such that addresses A and C collide to the same CPU branch prediction
    entries in the PHT (pattern history table) as those of the BPF program's
    lines 2 and 4, respectively. A non-privileged attacker could simply brute
    force such collisions in the PHT until observing the attack succeeding.
    
    Alternative methods to mistrain the branch predictor are also possible that
    avoid brute forcing the collisions in the PHT. A reliable attack has been
    demonstrated, for example, using the following crafted program:
    
      // r0 = pointer to a [control] map array entry
      // r7 = *(u64 *)(r0 + 0), training/attack phase
      // r8 = *(u64 *)(r0 + 8), oob address
      // [...]
      // r0 = pointer to a [data] map array entry
      1: if r7 == 0x3 goto line 3
      2: r8 = r0
      // crafted sequence of conditional jumps to separate the conditional
      // branch in line 193 from the current execution flow
      3: if r0 != 0x0 goto line 5
      4: if r0 == 0x0 goto exit
      5: if r0 != 0x0 goto line 7
      6: if r0 == 0x0 goto exit
      [...]
      187: if r0 != 0x0 goto line 189
      188: if r0 == 0x0 goto exit
      // load any slowly-loaded value (due to cache miss in phase 3) ...
      189: r3 = *(u64 *)(r0 + 0x1200)
      // ... and turn it into known zero for verifier, while preserving slowly-
      // loaded dependency when executing:
      190: r3 &= 1
      191: r3 &= 2
      // speculatively bypassed phase dependency
      192: r7 += r3
      193: if r7 == 0x3 goto exit
      194: r4 = *(u8 *)(r8 + 0)
      // leak r4
    
    As can be seen, in training phase (phase != 0x3), the condition in line 1
    turns into false and therefore r8 with the oob address is overridden with
    the valid map value address, which in line 194 we can read out without
    issues. However, in attack phase, line 2 is skipped, and due to the cache
    miss in line 189 where the map value is (zeroed and later) added to the
    phase register, the condition in line 193 takes the fall-through path due
    to prior branch predictor training, where under speculation, it'll load the
    byte at oob address r8 (unknown scalar type at that point) which could then
    be leaked via side-channel.
    
    One way to mitigate these is to 'branch off' an unreachable path, meaning,
    the current verification path keeps following the is_branch_taken() path
    and we push the other branch to the verification stack. Given this is
    unreachable from the non-speculative domain, this branch's vstate is
    explicitly marked as speculative. This is needed for two reasons: i) if
    this path is solely seen from speculative execution, then we later on still
    want the dead code elimination to kick in in order to sanitize these
    instructions with jmp-1s, and ii) to ensure that paths walked in the
    non-speculative domain are not pruned from earlier walks of paths walked in
    the speculative domain. Additionally, for robustness, we mark the registers
    which have been part of the conditional as unknown in the speculative path
    given there should be no assumptions made on their content.
    
    The fix in here mitigates type confusion attacks described earlier due to
    i) all code paths in the BPF program being explored and ii) existing
    verifier logic already ensuring that given memory access instruction
    references one specific data structure.
    
    An alternative to this fix that has also been looked at in this scope was to
    mark aux->alu_state at the jump instruction with a BPF_JMP_TAKEN state as
    well as direction encoding (always-goto, always-fallthrough, unknown), such
    that mixing of different always-* directions themselves as well as mixing of
    always-* with unknown directions would cause a program rejection by the
    verifier, e.g. programs with constructs like 'if ([...]) { x = 0; } else
    { x = 1; }' with subsequent 'if (x == 1) { [...] }'. For unprivileged, this
    would result in only single direction always-* taken paths, and unknown taken
    paths being allowed, such that the former could be patched from a conditional
    jump to an unconditional jump (ja). Compared to this approach here, it would
    have two downsides: i) valid programs that otherwise are not performing any
    pointer arithmetic, etc, would potentially be rejected/broken, and ii) we are
    required to turn off path pruning for unprivileged, where both can be avoided
    in this work through pushing the invalid branch to the verification stack.
    
    The issue was originally discovered by Adam and Ofek, and later independently
    discovered and reported as a result of Benedict and Piotr's research work.
    
    Fixes: b2157399 ("bpf: prevent out-of-bounds speculation")
    Reported-by: default avatarAdam Morrison <mad@cs.tau.ac.il>
    Reported-by: default avatarOfek Kirzner <ofekkir@gmail.com>
    Reported-by: default avatarBenedict Schlueter <benedict.schlueter@rub.de>
    Reported-by: default avatarPiotr Krysiuk <piotras@gmail.com>
    Signed-off-by: default avatarDaniel Borkmann <daniel@iogearbox.net>
    Reviewed-by: default avatarJohn Fastabend <john.fastabend@gmail.com>
    Reviewed-by: default avatarBenedict Schlueter <benedict.schlueter@rub.de>
    Reviewed-by: default avatarPiotr Krysiuk <piotras@gmail.com>
    Acked-by: default avatarAlexei Starovoitov <ast@kernel.org>
    9183671a
verifier.c 386 KB