• Daniel Borkmann's avatar
    bpf: Fix incorrect delta propagation between linked registers · 3878ae04
    Daniel Borkmann authored
    Nathaniel reported a bug in the linked scalar delta tracking, which can lead
    to accepting a program with OOB access. The specific code is related to the
    sync_linked_regs() function and the BPF_ADD_CONST flag, which signifies a
    constant offset between two scalar registers tracked by the same register id.
    
    The verifier attempts to track "similar" scalars in order to propagate bounds
    information learned about one scalar to others. For instance, if r1 and r2
    are known to contain the same value, then upon encountering 'if (r1 != 0x1234)
    goto xyz', not only does it know that r1 is equal to 0x1234 on the path where
    that conditional jump is not taken, it also knows that r2 is.
    
    Additionally, with env->bpf_capable set, the verifier will track scalars
    which should be a constant delta apart (if r1 is known to be one greater than
    r2, then if r1 is known to be equal to 0x1234, r2 must be equal to 0x1233.)
    The code path for the latter in adjust_reg_min_max_vals() is reached when
    processing both 32 and 64-bit addition operations. While adjust_reg_min_max_vals()
    knows whether dst_reg was produced by a 32 or a 64-bit addition (based on the
    alu32 bool), the only information saved in dst_reg is the id of the source
    register (reg->id, or'ed by BPF_ADD_CONST) and the value of the constant
    offset (reg->off).
    
    Later, the function sync_linked_regs() will attempt to use this information
    to propagate bounds information from one register (known_reg) to others,
    meaning, for all R in linked_regs, it copies known_reg range (and possibly
    adjusting delta) into R for the case of R->id == known_reg->id.
    
    For the delta adjustment, meaning, matching reg->id with BPF_ADD_CONST, the
    verifier adjusts the register as reg = known_reg; reg += delta where delta
    is computed as (s32)reg->off - (s32)known_reg->off and placed as a scalar
    into a fake_reg to then simulate the addition of reg += fake_reg. This is
    only correct, however, if the value in reg was created by a 64-bit addition.
    When reg contains the result of a 32-bit addition operation, its upper 32
    bits will always be zero. sync_linked_regs() on the other hand, may cause
    the verifier to believe that the addition between fake_reg and reg overflows
    into those upper bits. For example, if reg was generated by adding the
    constant 1 to known_reg using a 32-bit alu operation, then reg->off is 1
    and known_reg->off is 0. If known_reg is known to be the constant 0xFFFFFFFF,
    sync_linked_regs() will tell the verifier that reg is equal to the constant
    0x100000000. This is incorrect as the actual value of reg will be 0, as the
    32-bit addition will wrap around.
    
    Example:
    
      0: (b7) r0 = 0;             R0_w=0
      1: (18) r1 = 0x80000001;    R1_w=0x80000001
      3: (37) r1 /= 1;            R1_w=scalar()
      4: (bf) r2 = r1;            R1_w=scalar(id=1) R2_w=scalar(id=1)
      5: (bf) r4 = r1;            R1_w=scalar(id=1) R4_w=scalar(id=1)
      6: (04) w2 += 2147483647;   R2_w=scalar(id=1+2147483647,smin=0,smax=umax=0xffffffff,var_off=(0x0; 0xffffffff))
      7: (04) w4 += 0 ;           R4_w=scalar(id=1+0,smin=0,smax=umax=0xffffffff,var_off=(0x0; 0xffffffff))
      8: (15) if r2 == 0x0 goto pc+1
     10: R0=0 R1=0xffffffff80000001 R2=0x7fffffff R4=0xffffffff80000001 R10=fp0
    
    What can be seen here is that r1 is copied to r2 and r4, such that {r1,r2,r4}.id
    are all the same which later lets sync_linked_regs() to be invoked. Then, in
    a next step constants are added with alu32 to r2 and r4, setting their ->off,
    as well as id |= BPF_ADD_CONST. Next, the conditional will bind r2 and
    propagate ranges to its linked registers. The verifier now believes the upper
    32 bits of r4 are r4=0xffffffff80000001, while actually r4=r1=0x80000001.
    
    One approach for a simple fix suitable also for stable is to limit the constant
    delta tracking to only 64-bit alu addition. If necessary at some later point,
    BPF_ADD_CONST could be split into BPF_ADD_CONST64 and BPF_ADD_CONST32 to avoid
    mixing the two under the tradeoff to further complicate sync_linked_regs().
    However, none of the added tests from dedf56d7 ("selftests/bpf: Add tests
    for add_const") make this necessary at this point, meaning, BPF CI also passes
    with just limiting tracking to 64-bit alu addition.
    
    Fixes: 98d7ca37 ("bpf: Track delta between "linked" registers.")
    Reported-by: default avatarNathaniel Theis <nathaniel.theis@nccgroup.com>
    Signed-off-by: default avatarDaniel Borkmann <daniel@iogearbox.net>
    Signed-off-by: default avatarAndrii Nakryiko <andrii@kernel.org>
    Reviewed-by: default avatarEduard Zingerman <eddyz87@gmail.com>
    Link: https://lore.kernel.org/bpf/20241016134913.32249-1-daniel@iogearbox.net
    3878ae04
verifier.c 676 KB