Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

bpf: Fix insufficient bounds propagation from adjust_scalar_min_max_vals

Kuee reported a corner case where the tnum becomes constant after the call
to __reg_bound_offset(), but the register's bounds are not, that is, its
min bounds are still not equal to the register's max bounds.

This in turn allows to leak pointers through turning a pointer register as
is into an unknown scalar via adjust_ptr_min_max_vals().

Before:

func#0 @0
0: R1=ctx(off=0,imm=0,umax=0,var_off=(0x0; 0x0)) R10=fp(off=0,imm=0,umax=0,var_off=(0x0; 0x0))
0: (b7) r0 = 1 ; R0_w=scalar(imm=1,umin=1,umax=1,var_off=(0x1; 0x0))
1: (b7) r3 = 0 ; R3_w=scalar(imm=0,umax=0,var_off=(0x0; 0x0))
2: (87) r3 = -r3 ; R3_w=scalar()
3: (87) r3 = -r3 ; R3_w=scalar()
4: (47) r3 |= 32767 ; R3_w=scalar(smin=-9223372036854743041,umin=32767,var_off=(0x7fff; 0xffffffffffff8000),s32_min=-2147450881)
5: (75) if r3 s>= 0x0 goto pc+1 ; R3_w=scalar(umin=9223372036854808575,var_off=(0x8000000000007fff; 0x7fffffffffff8000),s32_min=-2147450881,u32_min=32767)
6: (95) exit

from 5 to 7: R0=scalar(imm=1,umin=1,umax=1,var_off=(0x1; 0x0)) R1=ctx(off=0,imm=0,umax=0,var_off=(0x0; 0x0)) R3=scalar(umin=32767,umax=9223372036854775807,var_off=(0x7fff; 0x7fffffffffff8000),s32_min=-2147450881) R10=fp(off=0,imm=0,umax=0,var_off=(0x0; 0x0))
7: (d5) if r3 s<= 0x8000 goto pc+1 ; R3=scalar(umin=32769,umax=9223372036854775807,var_off=(0x7fff; 0x7fffffffffff8000),s32_min=-2147450881,u32_min=32767)
8: (95) exit

from 7 to 9: R0=scalar(imm=1,umin=1,umax=1,var_off=(0x1; 0x0)) R1=ctx(off=0,imm=0,umax=0,var_off=(0x0; 0x0)) R3=scalar(umin=32767,umax=32768,var_off=(0x7fff; 0x8000)) R10=fp(off=0,imm=0,umax=0,var_off=(0x0; 0x0))
9: (07) r3 += -32767 ; R3_w=scalar(imm=0,umax=1,var_off=(0x0; 0x0)) <--- [*]
10: (95) exit

What can be seen here is that R3=scalar(umin=32767,umax=32768,var_off=(0x7fff;
0x8000)) after the operation R3 += -32767 results in a 'malformed' constant, that
is, R3_w=scalar(imm=0,umax=1,var_off=(0x0; 0x0)). Intersecting with var_off has
not been done at that point via __update_reg_bounds(), which would have improved
the umax to be equal to umin.

Refactor the tnum <> min/max bounds information flow into a reg_bounds_sync()
helper and use it consistently everywhere. After the fix, bounds have been
corrected to R3_w=scalar(imm=0,umax=0,var_off=(0x0; 0x0)) and thus the register
is regarded as a 'proper' constant scalar of 0.

After:

func#0 @0
0: R1=ctx(off=0,imm=0,umax=0,var_off=(0x0; 0x0)) R10=fp(off=0,imm=0,umax=0,var_off=(0x0; 0x0))
0: (b7) r0 = 1 ; R0_w=scalar(imm=1,umin=1,umax=1,var_off=(0x1; 0x0))
1: (b7) r3 = 0 ; R3_w=scalar(imm=0,umax=0,var_off=(0x0; 0x0))
2: (87) r3 = -r3 ; R3_w=scalar()
3: (87) r3 = -r3 ; R3_w=scalar()
4: (47) r3 |= 32767 ; R3_w=scalar(smin=-9223372036854743041,umin=32767,var_off=(0x7fff; 0xffffffffffff8000),s32_min=-2147450881)
5: (75) if r3 s>= 0x0 goto pc+1 ; R3_w=scalar(umin=9223372036854808575,var_off=(0x8000000000007fff; 0x7fffffffffff8000),s32_min=-2147450881,u32_min=32767)
6: (95) exit

from 5 to 7: R0=scalar(imm=1,umin=1,umax=1,var_off=(0x1; 0x0)) R1=ctx(off=0,imm=0,umax=0,var_off=(0x0; 0x0)) R3=scalar(umin=32767,umax=9223372036854775807,var_off=(0x7fff; 0x7fffffffffff8000),s32_min=-2147450881) R10=fp(off=0,imm=0,umax=0,var_off=(0x0; 0x0))
7: (d5) if r3 s<= 0x8000 goto pc+1 ; R3=scalar(umin=32769,umax=9223372036854775807,var_off=(0x7fff; 0x7fffffffffff8000),s32_min=-2147450881,u32_min=32767)
8: (95) exit

from 7 to 9: R0=scalar(imm=1,umin=1,umax=1,var_off=(0x1; 0x0)) R1=ctx(off=0,imm=0,umax=0,var_off=(0x0; 0x0)) R3=scalar(umin=32767,umax=32768,var_off=(0x7fff; 0x8000)) R10=fp(off=0,imm=0,umax=0,var_off=(0x0; 0x0))
9: (07) r3 += -32767 ; R3_w=scalar(imm=0,umax=0,var_off=(0x0; 0x0)) <--- [*]
10: (95) exit

Fixes: b03c9f9fdc37 ("bpf/verifier: track signed and unsigned min/max values")
Reported-by: Kuee K1r0a <liulin063@gmail.com>
Signed-off-by: Daniel Borkmann <daniel@iogearbox.net>
Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
Acked-by: John Fastabend <john.fastabend@gmail.com>
Link: https://lore.kernel.org/bpf/20220701124727.11153-2-daniel@iogearbox.net

authored by

Daniel Borkmann and committed by
Andrii Nakryiko
3844d153 a12ca627

+23 -49
+23 -49
kernel/bpf/verifier.c
··· 1562 1562 reg->var_off = tnum_or(tnum_clear_subreg(var64_off), var32_off); 1563 1563 } 1564 1564 1565 + static void reg_bounds_sync(struct bpf_reg_state *reg) 1566 + { 1567 + /* We might have learned new bounds from the var_off. */ 1568 + __update_reg_bounds(reg); 1569 + /* We might have learned something about the sign bit. */ 1570 + __reg_deduce_bounds(reg); 1571 + /* We might have learned some bits from the bounds. */ 1572 + __reg_bound_offset(reg); 1573 + /* Intersecting with the old var_off might have improved our bounds 1574 + * slightly, e.g. if umax was 0x7f...f and var_off was (0; 0xf...fc), 1575 + * then new var_off is (0; 0x7f...fc) which improves our umax. 1576 + */ 1577 + __update_reg_bounds(reg); 1578 + } 1579 + 1565 1580 static bool __reg32_bound_s64(s32 a) 1566 1581 { 1567 1582 return a >= 0 && a <= S32_MAX; ··· 1618 1603 * so they do not impact tnum bounds calculation. 1619 1604 */ 1620 1605 __mark_reg64_unbounded(reg); 1621 - __update_reg_bounds(reg); 1622 1606 } 1623 - 1624 - /* Intersecting with the old var_off might have improved our bounds 1625 - * slightly. e.g. if umax was 0x7f...f and var_off was (0; 0xf...fc), 1626 - * then new var_off is (0; 0x7f...fc) which improves our umax. 1627 - */ 1628 - __reg_deduce_bounds(reg); 1629 - __reg_bound_offset(reg); 1630 - __update_reg_bounds(reg); 1607 + reg_bounds_sync(reg); 1631 1608 } 1632 1609 1633 1610 static bool __reg64_bound_s32(s64 a) ··· 1635 1628 static void __reg_combine_64_into_32(struct bpf_reg_state *reg) 1636 1629 { 1637 1630 __mark_reg32_unbounded(reg); 1638 - 1639 1631 if (__reg64_bound_s32(reg->smin_value) && __reg64_bound_s32(reg->smax_value)) { 1640 1632 reg->s32_min_value = (s32)reg->smin_value; 1641 1633 reg->s32_max_value = (s32)reg->smax_value; ··· 1643 1637 reg->u32_min_value = (u32)reg->umin_value; 1644 1638 reg->u32_max_value = (u32)reg->umax_value; 1645 1639 } 1646 - 1647 - /* Intersecting with the old var_off might have improved our bounds 1648 - * slightly. e.g. if umax was 0x7f...f and var_off was (0; 0xf...fc), 1649 - * then new var_off is (0; 0x7f...fc) which improves our umax. 1650 - */ 1651 - __reg_deduce_bounds(reg); 1652 - __reg_bound_offset(reg); 1653 - __update_reg_bounds(reg); 1640 + reg_bounds_sync(reg); 1654 1641 } 1655 1642 1656 1643 /* Mark a register as having a completely unknown (scalar) value. */ ··· 6942 6943 ret_reg->s32_max_value = meta->msize_max_value; 6943 6944 ret_reg->smin_value = -MAX_ERRNO; 6944 6945 ret_reg->s32_min_value = -MAX_ERRNO; 6945 - __reg_deduce_bounds(ret_reg); 6946 - __reg_bound_offset(ret_reg); 6947 - __update_reg_bounds(ret_reg); 6946 + reg_bounds_sync(ret_reg); 6948 6947 } 6949 6948 6950 6949 static int ··· 8199 8202 8200 8203 if (!check_reg_sane_offset(env, dst_reg, ptr_reg->type)) 8201 8204 return -EINVAL; 8202 - 8203 - __update_reg_bounds(dst_reg); 8204 - __reg_deduce_bounds(dst_reg); 8205 - __reg_bound_offset(dst_reg); 8206 - 8205 + reg_bounds_sync(dst_reg); 8207 8206 if (sanitize_check_bounds(env, insn, dst_reg) < 0) 8208 8207 return -EACCES; 8209 8208 if (sanitize_needed(opcode)) { ··· 8937 8944 /* ALU32 ops are zero extended into 64bit register */ 8938 8945 if (alu32) 8939 8946 zext_32_to_64(dst_reg); 8940 - 8941 - __update_reg_bounds(dst_reg); 8942 - __reg_deduce_bounds(dst_reg); 8943 - __reg_bound_offset(dst_reg); 8947 + reg_bounds_sync(dst_reg); 8944 8948 return 0; 8945 8949 } 8946 8950 ··· 9126 9136 insn->dst_reg); 9127 9137 } 9128 9138 zext_32_to_64(dst_reg); 9129 - 9130 - __update_reg_bounds(dst_reg); 9131 - __reg_deduce_bounds(dst_reg); 9132 - __reg_bound_offset(dst_reg); 9139 + reg_bounds_sync(dst_reg); 9133 9140 } 9134 9141 } else { 9135 9142 /* case: R = imm ··· 9729 9742 dst_reg->smax_value); 9730 9743 src_reg->var_off = dst_reg->var_off = tnum_intersect(src_reg->var_off, 9731 9744 dst_reg->var_off); 9732 - /* We might have learned new bounds from the var_off. */ 9733 - __update_reg_bounds(src_reg); 9734 - __update_reg_bounds(dst_reg); 9735 - /* We might have learned something about the sign bit. */ 9736 - __reg_deduce_bounds(src_reg); 9737 - __reg_deduce_bounds(dst_reg); 9738 - /* We might have learned some bits from the bounds. */ 9739 - __reg_bound_offset(src_reg); 9740 - __reg_bound_offset(dst_reg); 9741 - /* Intersecting with the old var_off might have improved our bounds 9742 - * slightly. e.g. if umax was 0x7f...f and var_off was (0; 0xf...fc), 9743 - * then new var_off is (0; 0x7f...fc) which improves our umax. 9744 - */ 9745 - __update_reg_bounds(src_reg); 9746 - __update_reg_bounds(dst_reg); 9745 + reg_bounds_sync(src_reg); 9746 + reg_bounds_sync(dst_reg); 9747 9747 } 9748 9748 9749 9749 static void reg_combine_min_max(struct bpf_reg_state *true_src,