Skip to content

Commit f41345f

Browse files
pchaignoborkmann
authored andcommitted
bpf: Use tnums for JEQ/JNE is_branch_taken logic
In the following toy program (reg states minimized for readability), R0 and R1 always have different values at instruction 6. This is obvious when reading the program but cannot be guessed from ranges alone as they overlap (R0 in [0; 0xc0000000], R1 in [1024; 0xc0000400]). 0: call bpf_get_prandom_u32#7 ; R0_w=scalar() 1: w0 = w0 ; R0_w=scalar(var_off=(0x0; 0xffffffff)) 2: r0 >>= 30 ; R0_w=scalar(var_off=(0x0; 0x3)) 3: r0 <<= 30 ; R0_w=scalar(var_off=(0x0; 0xc0000000)) 4: r1 = r0 ; R1_w=scalar(var_off=(0x0; 0xc0000000)) 5: r1 += 1024 ; R1_w=scalar(var_off=(0x400; 0xc0000000)) 6: if r1 != r0 goto pc+1 Looking at tnums however, we can deduce that R1 is always different from R0 because their tnums don't agree on known bits. This patch uses this logic to improve is_scalar_branch_taken in case of BPF_JEQ and BPF_JNE. This change has a tiny impact on complexity, which was measured with the Cilium complexity CI test. That test covers 72 programs with various build and load time configurations for a total of 970 test cases. For 80% of test cases, the patch has no impact. On the other test cases, the patch decreases complexity by only 0.08% on average. In the best case, the verifier needs to walk 3% less instructions and, in the worst case, 1.5% more. Overall, the patch has a small positive impact, especially for our largest programs. Signed-off-by: Paul Chaignon <[email protected]> Signed-off-by: Daniel Borkmann <[email protected]> Acked-by: Eduard Zingerman <[email protected]> Acked-by: Shung-Hsi Yu <[email protected]> Acked-by: Daniel Borkmann <[email protected]> Link: https://lore.kernel.org/bpf/be3ee70b6e489c49881cb1646114b1d861b5c334.1755694147.git.paul.chaignon@gmail.com
1 parent 21aeabb commit f41345f

File tree

3 files changed

+15
-0
lines changed

3 files changed

+15
-0
lines changed

include/linux/tnum.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ struct tnum tnum_xor(struct tnum a, struct tnum b);
5151
/* Multiply two tnums, return @a * @b */
5252
struct tnum tnum_mul(struct tnum a, struct tnum b);
5353

54+
/* Return true if the known bits of both tnums have the same value */
55+
bool tnum_overlap(struct tnum a, struct tnum b);
56+
5457
/* Return a tnum representing numbers satisfying both @a and @b */
5558
struct tnum tnum_intersect(struct tnum a, struct tnum b);
5659

kernel/bpf/tnum.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,14 @@ struct tnum tnum_mul(struct tnum a, struct tnum b)
143143
return tnum_add(TNUM(acc_v, 0), acc_m);
144144
}
145145

146+
bool tnum_overlap(struct tnum a, struct tnum b)
147+
{
148+
u64 mu;
149+
150+
mu = ~a.mask & ~b.mask;
151+
return (a.value & mu) == (b.value & mu);
152+
}
153+
146154
/* Note that if a and b disagree - i.e. one has a 'known 1' where the other has
147155
* a 'known 0' - this will return a 'known 1' for that bit.
148156
*/

kernel/bpf/verifier.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15897,6 +15897,8 @@ static int is_scalar_branch_taken(struct bpf_reg_state *reg1, struct bpf_reg_sta
1589715897
*/
1589815898
if (tnum_is_const(t1) && tnum_is_const(t2))
1589915899
return t1.value == t2.value;
15900+
if (!tnum_overlap(t1, t2))
15901+
return 0;
1590015902
/* non-overlapping ranges */
1590115903
if (umin1 > umax2 || umax1 < umin2)
1590215904
return 0;
@@ -15921,6 +15923,8 @@ static int is_scalar_branch_taken(struct bpf_reg_state *reg1, struct bpf_reg_sta
1592115923
*/
1592215924
if (tnum_is_const(t1) && tnum_is_const(t2))
1592315925
return t1.value != t2.value;
15926+
if (!tnum_overlap(t1, t2))
15927+
return 1;
1592415928
/* non-overlapping ranges */
1592515929
if (umin1 > umax2 || umax1 < umin2)
1592615930
return 1;

0 commit comments

Comments
 (0)