bpf: add special smin32/smax32 derivation from 64-bit bounds
authorAndrii Nakryiko <andrii@kernel.org>
Thu, 2 Nov 2023 03:37:48 +0000 (20:37 -0700)
committerAlexei Starovoitov <ast@kernel.org>
Fri, 10 Nov 2023 02:58:39 +0000 (18:58 -0800)
Add a special case where we can derive valid s32 bounds from umin/umax
or smin/smax by stitching together negative s32 subrange and
non-negative s32 subrange. That requires upper 32 bits to form a [N, N+1]
range in u32 domain (taking into account wrap around, so 0xffffffff
to 0x00000000 is a valid [N, N+1] range in this sense). See code comment
for concrete examples.

Eduard Zingerman also provided an alternative explanation ([0]) for more
mathematically inclined readers:

Suppose:
. there are numbers a, b, c
. 2**31 <= b < 2**32
. 0 <= c < 2**31
. umin = 2**32 * a + b
. umax = 2**32 * (a + 1) + c

The number of values in the range represented by [umin; umax] is:
. N = umax - umin + 1 = 2**32 + c - b + 1
. min(N) = 2**32 + 0 - (2**32-1) + 1 = 2, with b = 2**32-1, c = 0
. max(N) = 2**32 + (2**31 - 1) - 2**31 + 1 = 2**32, with b = 2**31, c = 2**31-1

Hence [(s32)b; (s32)c] forms a valid range.

  [0] https://lore.kernel.org/bpf/d7af631802f0cfae20df77fe70068702d24bbd31.camel@gmail.com/

Acked-by: Eduard Zingerman <eddyz87@gmail.com>
Acked-by: Shung-Hsi Yu <shung-hsi.yu@suse.com>
Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
Link: https://lore.kernel.org/r/20231102033759.2541186-7-andrii@kernel.org
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
kernel/bpf/verifier.c

index 9e39f12538f793dac3554f0992510d51c6c30e4d..0fffbf01328e1e077582f1722f9c546373eeb2dd 100644 (file)
@@ -2369,6 +2369,29 @@ static void __reg32_deduce_bounds(struct bpf_reg_state *reg)
                        reg->s32_max_value = min_t(s32, reg->s32_max_value, (s32)reg->smax_value);
                }
        }
+       /* Special case where upper bits form a small sequence of two
+        * sequential numbers (in 32-bit unsigned space, so 0xffffffff to
+        * 0x00000000 is also valid), while lower bits form a proper s32 range
+        * going from negative numbers to positive numbers. E.g., let's say we
+        * have s64 range [-1, 1] ([0xffffffffffffffff, 0x0000000000000001]).
+        * Possible s64 values are {-1, 0, 1} ({0xffffffffffffffff,
+        * 0x0000000000000000, 0x00000000000001}). Ignoring upper 32 bits,
+        * we still get a valid s32 range [-1, 1] ([0xffffffff, 0x00000001]).
+        * Note that it doesn't have to be 0xffffffff going to 0x00000000 in
+        * upper 32 bits. As a random example, s64 range
+        * [0xfffffff0fffffff0; 0xfffffff100000010], forms a valid s32 range
+        * [-16, 16] ([0xfffffff0; 0x00000010]) in its 32 bit subregister.
+        */
+       if ((u32)(reg->umin_value >> 32) + 1 == (u32)(reg->umax_value >> 32) &&
+           (s32)reg->umin_value < 0 && (s32)reg->umax_value >= 0) {
+               reg->s32_min_value = max_t(s32, reg->s32_min_value, (s32)reg->umin_value);
+               reg->s32_max_value = min_t(s32, reg->s32_max_value, (s32)reg->umax_value);
+       }
+       if ((u32)(reg->smin_value >> 32) + 1 == (u32)(reg->smax_value >> 32) &&
+           (s32)reg->smin_value < 0 && (s32)reg->smax_value >= 0) {
+               reg->s32_min_value = max_t(s32, reg->s32_min_value, (s32)reg->smin_value);
+               reg->s32_max_value = min_t(s32, reg->s32_max_value, (s32)reg->smax_value);
+       }
        /* if u32 range forms a valid s32 range (due to matching sign bit),
         * try to learn from that
         */