Skip to content

Commit 13e5019

Browse files
harishankarvKernel Patches Daemon
authored andcommitted
bpf, verifier: Improve precision for BPF_ADD and BPF_SUB
This patch improves the precison of the scalar(32)_min_max_add and scalar(32)_min_max_sub functions, which update the u(32)min/u(32)_max ranges for the BPF_ADD and BPF_SUB instructions. We discovered this more precise operator using a technique we are developing for automatically synthesizing functions for updating tnums and ranges. According to the BPF ISA [1], "Underflow and overflow are allowed during arithmetic operations, meaning the 64-bit or 32-bit value will wrap". Our patch leverages the wrap-around semantics of unsigned overflow and underflow to improve precision. Below is an example of our patch for scalar_min_max_add; the idea is analogous for all four functions. There are three cases to consider when adding two u64 ranges [dst_umin, dst_umax] and [src_umin, src_umax]. Consider a value x in the range [dst_umin, dst_umax] and another value y in the range [src_umin, src_umax]. (a) No overflow: No addition x + y overflows. This occurs when even the largest possible sum, i.e., dst_umax + src_umax <= U64_MAX. (b) Partial overflow: Some additions x + y overflow. This occurs when the largest possible sum overflows (dst_umax + src_umax > U64_MAX), but the smallest possible sum does not overflow (dst_umin + src_umin <= U64_MAX). (c) Full overflow: All additions x + y overflow. This occurs when both the smallest possible sum and the largest possible sum overflow, i.e., both (dst_umin + src_umin) and (dst_umax + src_umax) are > U64_MAX. The current implementation conservatively sets the output bounds to unbounded, i.e, [umin=0, umax=U64_MAX], whenever there is *any* possibility of overflow, i.e, in cases (b) and (c). Otherwise it computes tight bounds as [dst_umin + src_umin, dst_umax + src_umax]: if (check_add_overflow(*dst_umin, src_reg->umin_value, dst_umin) || check_add_overflow(*dst_umax, src_reg->umax_value, dst_umax)) { *dst_umin = 0; *dst_umax = U64_MAX; } Our synthesis-based technique discovered a more precise operator. Particularly, in case (c), all possible additions x + y overflow and wrap around according to eBPF semantics, and the computation of the output range as [dst_umin + src_umin, dst_umax + src_umax] continues to work. Only in case (b), do we need to set the output bounds to unbounded, i.e., [0, U64_MAX]. Case (b) can be checked by seeing if the minimum possible sum does *not* overflow and the maximum possible sum *does* overflow, and when that happens, we set the output to unbounded: min_overflow = check_add_overflow(*dst_umin, src_reg->umin_value, dst_umin); max_overflow = check_add_overflow(*dst_umax, src_reg->umax_value, dst_umax); if (!min_overflow && max_overflow) { *dst_umin = 0; *dst_umax = U64_MAX; } Below is an example eBPF program and the corresponding log from the verifier. The current implementation of scalar_min_max_add() sets r3's bounds to [0, U64_MAX] at instruction 5: (0f) r3 += r3, due to conservative overflow handling. 0: R1=ctx() R10=fp0 0: (b7) r4 = 0 ; R4_w=0 1: (87) r4 = -r4 ; R4_w=scalar() 2: (18) r3 = 0xa000000000000000 ; R3_w=0xa000000000000000 4: (4f) r3 |= r4 ; R3_w=scalar(smin=0xa000000000000000,smax=-1,umin=0xa000000000000000,var_off=(0xa000000000000000; 0x5fffffffffffffff)) R4_w=scalar() 5: (0f) r3 += r3 ; R3_w=scalar() 6: (b7) r0 = 1 ; R0_w=1 7: (95) exit With our patch, r3's bounds after instruction 5 are set to a much more precise [0x4000000000000000,0xfffffffffffffffe]. ... 5: (0f) r3 += r3 ; R3_w=scalar(umin=0x4000000000000000,umax=0xfffffffffffffffe) 6: (b7) r0 = 1 ; R0_w=1 7: (95) exit The logic for scalar32_min_max_add is analogous. For the scalar(32)_min_max_sub functions, the reasoning is similar but applied to detecting underflow instead of overflow. We verified the correctness of the new implementations using Agni [3,4]. We since also discovered that a similar technique has been used to calculate output ranges for unsigned interval addition and subtraction in Hacker's Delight [2]. [1] https://docs.kernel.org/bpf/standardization/instruction-set.html [2] Hacker's Delight Ch.4-2, Propagating Bounds through Add’s and Subtract’s [3] https://github.com/bpfverif/agni [4] https://people.cs.rutgers.edu/~sn349/papers/sas24-preprint.pdf Co-developed-by: Matan Shachnai <[email protected]> Signed-off-by: Matan Shachnai <[email protected]> Co-developed-by: Srinivas Narayana <[email protected]> Signed-off-by: Srinivas Narayana <[email protected]> Co-developed-by: Santosh Nagarakatte <[email protected]> Signed-off-by: Santosh Nagarakatte <[email protected]> Signed-off-by: Harishankar Vishwanathan <[email protected]>
1 parent 1c12ad1 commit 13e5019

File tree

1 file changed

+56
-20
lines changed

1 file changed

+56
-20
lines changed

kernel/bpf/verifier.c

Lines changed: 56 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14605,14 +14605,25 @@ static void scalar32_min_max_add(struct bpf_reg_state *dst_reg,
1460514605
s32 *dst_smax = &dst_reg->s32_max_value;
1460614606
u32 *dst_umin = &dst_reg->u32_min_value;
1460714607
u32 *dst_umax = &dst_reg->u32_max_value;
14608+
u32 umin_val = src_reg->u32_min_value;
14609+
u32 umax_val = src_reg->u32_max_value;
14610+
bool min_overflow, max_overflow;
1460814611

1460914612
if (check_add_overflow(*dst_smin, src_reg->s32_min_value, dst_smin) ||
1461014613
check_add_overflow(*dst_smax, src_reg->s32_max_value, dst_smax)) {
1461114614
*dst_smin = S32_MIN;
1461214615
*dst_smax = S32_MAX;
1461314616
}
14614-
if (check_add_overflow(*dst_umin, src_reg->u32_min_value, dst_umin) ||
14615-
check_add_overflow(*dst_umax, src_reg->u32_max_value, dst_umax)) {
14617+
14618+
/* If either all additions overflow or no additions overflow, then
14619+
* it is okay to set: dst_umin = dst_umin + src_umin, dst_umax =
14620+
* dst_umax + src_umax. Otherwise (some additions overflow), set
14621+
* the output bounds to unbounded.
14622+
*/
14623+
min_overflow = check_add_overflow(*dst_umin, umin_val, dst_umin);
14624+
max_overflow = check_add_overflow(*dst_umax, umax_val, dst_umax);
14625+
14626+
if (!min_overflow && max_overflow) {
1461614627
*dst_umin = 0;
1461714628
*dst_umax = U32_MAX;
1461814629
}
@@ -14625,14 +14636,25 @@ static void scalar_min_max_add(struct bpf_reg_state *dst_reg,
1462514636
s64 *dst_smax = &dst_reg->smax_value;
1462614637
u64 *dst_umin = &dst_reg->umin_value;
1462714638
u64 *dst_umax = &dst_reg->umax_value;
14639+
u64 umin_val = src_reg->umin_value;
14640+
u64 umax_val = src_reg->umax_value;
14641+
bool min_overflow, max_overflow;
1462814642

1462914643
if (check_add_overflow(*dst_smin, src_reg->smin_value, dst_smin) ||
1463014644
check_add_overflow(*dst_smax, src_reg->smax_value, dst_smax)) {
1463114645
*dst_smin = S64_MIN;
1463214646
*dst_smax = S64_MAX;
1463314647
}
14634-
if (check_add_overflow(*dst_umin, src_reg->umin_value, dst_umin) ||
14635-
check_add_overflow(*dst_umax, src_reg->umax_value, dst_umax)) {
14648+
14649+
/* If either all additions overflow or no additions overflow, then
14650+
* it is okay to set: dst_umin = dst_umin + src_umin, dst_umax =
14651+
* dst_umax + src_umax. Otherwise (some additions overflow), set
14652+
* the output bounds to unbounded.
14653+
*/
14654+
min_overflow = check_add_overflow(*dst_umin, umin_val, dst_umin);
14655+
max_overflow = check_add_overflow(*dst_umax, umax_val, dst_umax);
14656+
14657+
if (!min_overflow && max_overflow) {
1463614658
*dst_umin = 0;
1463714659
*dst_umax = U64_MAX;
1463814660
}
@@ -14643,23 +14665,30 @@ static void scalar32_min_max_sub(struct bpf_reg_state *dst_reg,
1464314665
{
1464414666
s32 *dst_smin = &dst_reg->s32_min_value;
1464514667
s32 *dst_smax = &dst_reg->s32_max_value;
14668+
u32 *dst_umin = &dst_reg->u32_min_value;
14669+
u32 *dst_umax = &dst_reg->u32_max_value;
1464614670
u32 umin_val = src_reg->u32_min_value;
1464714671
u32 umax_val = src_reg->u32_max_value;
14672+
bool min_underflow, max_underflow;
1464814673

1464914674
if (check_sub_overflow(*dst_smin, src_reg->s32_max_value, dst_smin) ||
1465014675
check_sub_overflow(*dst_smax, src_reg->s32_min_value, dst_smax)) {
1465114676
/* Overflow possible, we know nothing */
1465214677
*dst_smin = S32_MIN;
1465314678
*dst_smax = S32_MAX;
1465414679
}
14655-
if (dst_reg->u32_min_value < umax_val) {
14656-
/* Overflow possible, we know nothing */
14657-
dst_reg->u32_min_value = 0;
14658-
dst_reg->u32_max_value = U32_MAX;
14659-
} else {
14660-
/* Cannot overflow (as long as bounds are consistent) */
14661-
dst_reg->u32_min_value -= umax_val;
14662-
dst_reg->u32_max_value -= umin_val;
14680+
14681+
/* If either all subtractions underflow or no subtractions
14682+
* underflow, it is okay to set: dst_umin = dst_umin - src_umax,
14683+
* dst_umax = dst_umax - src_umin. Otherwise (some subtractions
14684+
* underflow), set the output bounds to unbounded.
14685+
*/
14686+
min_underflow = check_sub_overflow(*dst_umin, umax_val, dst_umin);
14687+
max_underflow = check_sub_overflow(*dst_umax, umin_val, dst_umax);
14688+
14689+
if (min_underflow && !max_underflow) {
14690+
*dst_umin = 0;
14691+
*dst_umax = U32_MAX;
1466314692
}
1466414693
}
1466514694

@@ -14668,23 +14697,30 @@ static void scalar_min_max_sub(struct bpf_reg_state *dst_reg,
1466814697
{
1466914698
s64 *dst_smin = &dst_reg->smin_value;
1467014699
s64 *dst_smax = &dst_reg->smax_value;
14700+
u64 *dst_umin = &dst_reg->umin_value;
14701+
u64 *dst_umax = &dst_reg->umax_value;
1467114702
u64 umin_val = src_reg->umin_value;
1467214703
u64 umax_val = src_reg->umax_value;
14704+
bool min_underflow, max_underflow;
1467314705

1467414706
if (check_sub_overflow(*dst_smin, src_reg->smax_value, dst_smin) ||
1467514707
check_sub_overflow(*dst_smax, src_reg->smin_value, dst_smax)) {
1467614708
/* Overflow possible, we know nothing */
1467714709
*dst_smin = S64_MIN;
1467814710
*dst_smax = S64_MAX;
1467914711
}
14680-
if (dst_reg->umin_value < umax_val) {
14681-
/* Overflow possible, we know nothing */
14682-
dst_reg->umin_value = 0;
14683-
dst_reg->umax_value = U64_MAX;
14684-
} else {
14685-
/* Cannot overflow (as long as bounds are consistent) */
14686-
dst_reg->umin_value -= umax_val;
14687-
dst_reg->umax_value -= umin_val;
14712+
14713+
/* If either all subtractions underflow or no subtractions
14714+
* underflow, it is okay to set: dst_umin = dst_umin - src_umax,
14715+
* dst_umax = dst_umax - src_umin. Otherwise (some subtractions
14716+
* underflow), set the output bounds to unbounded.
14717+
*/
14718+
min_underflow = check_sub_overflow(*dst_umin, umax_val, dst_umin);
14719+
max_underflow = check_sub_overflow(*dst_umax, umin_val, dst_umax);
14720+
14721+
if (min_underflow && !max_underflow) {
14722+
*dst_umin = 0;
14723+
*dst_umax = U64_MAX;
1468814724
}
1468914725
}
1469014726

0 commit comments

Comments
 (0)