diff mbox series

[v5,bpf-next,06/23] bpf: add special smin32/smax32 derivation from 64-bit bounds

Message ID 20231027181346.4019398-7-andrii@kernel.org (mailing list archive)
State Superseded
Delegated to: BPF
Headers show
Series BPF register bounds logic and testing improvements | expand

Checks

Context Check Description
bpf/vmtest-bpf-next-VM_Test-30 success Logs for x86_64-llvm-16 / test (test_progs_no_alu32_parallel, true, 30) / test_progs_no_alu32_parallel on x86_64 with llvm-16
bpf/vmtest-bpf-next-VM_Test-31 success Logs for x86_64-llvm-16 / test (test_progs_parallel, true, 30) / test_progs_parallel on x86_64 with llvm-16
bpf/vmtest-bpf-next-VM_Test-32 success Logs for x86_64-llvm-16 / test (test_verifier, false, 360) / test_verifier on x86_64 with llvm-16
bpf/vmtest-bpf-next-VM_Test-33 success Logs for x86_64-llvm-16 / veristat
netdev/series_format fail Series longer than 15 patches (and no cover letter)
netdev/tree_selection success Clearly marked for bpf-next, async
netdev/fixes_present success Fixes tag not required for -next series
netdev/header_inline success No static functions without inline keyword in header files
netdev/build_32bit success Errors and warnings before: 1374 this patch: 1374
netdev/cc_maintainers warning 8 maintainers not CCed: john.fastabend@gmail.com kpsingh@kernel.org song@kernel.org sdf@google.com jolsa@kernel.org martin.lau@linux.dev yonghong.song@linux.dev haoluo@google.com
netdev/build_clang fail Errors and warnings before: 15 this patch: 15
netdev/verify_signedoff success Signed-off-by tag matches author and committer
netdev/deprecated_api success None detected
netdev/check_selftest success No net selftest shell script
netdev/verify_fixes success No Fixes tag
netdev/build_allmodconfig_warn success Errors and warnings before: 1399 this patch: 1399
netdev/checkpatch warning WARNING: line length of 90 exceeds 80 columns
netdev/build_clang_rust success Link
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0
bpf/vmtest-bpf-next-VM_Test-0 success Logs for Lint
bpf/vmtest-bpf-next-VM_Test-1 success Logs for ShellCheck
bpf/vmtest-bpf-next-VM_Test-2 success Logs for Validate matrix.py
bpf/vmtest-bpf-next-VM_Test-3 success Logs for aarch64-gcc / build / build for aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-8 success Logs for aarch64-gcc / veristat
bpf/vmtest-bpf-next-VM_Test-4 success Logs for aarch64-gcc / test (test_maps, false, 360) / test_maps on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-5 success Logs for aarch64-gcc / test (test_progs, false, 360) / test_progs on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-6 success Logs for aarch64-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-7 success Logs for aarch64-gcc / test (test_verifier, false, 360) / test_verifier on aarch64 with gcc
bpf/vmtest-bpf-next-VM_Test-9 success Logs for s390x-gcc / build / build for s390x with gcc
bpf/vmtest-bpf-next-VM_Test-14 success Logs for s390x-gcc / veristat
bpf/vmtest-bpf-next-VM_Test-15 success Logs for set-matrix
bpf/vmtest-bpf-next-VM_Test-16 success Logs for x86_64-gcc / build / build for x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-17 success Logs for x86_64-gcc / test (test_maps, false, 360) / test_maps on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-18 success Logs for x86_64-gcc / test (test_progs, false, 360) / test_progs on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-19 success Logs for x86_64-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-20 success Logs for x86_64-gcc / test (test_progs_no_alu32_parallel, true, 30) / test_progs_no_alu32_parallel on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-21 success Logs for x86_64-gcc / test (test_progs_parallel, true, 30) / test_progs_parallel on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-22 success Logs for x86_64-gcc / test (test_verifier, false, 360) / test_verifier on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-23 success Logs for x86_64-gcc / veristat / veristat on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-24 success Logs for x86_64-llvm-16 / build / build for x86_64 with llvm-16
bpf/vmtest-bpf-next-VM_Test-25 success Logs for x86_64-llvm-16 / test (test_maps, false, 360) / test_maps on x86_64 with llvm-16
bpf/vmtest-bpf-next-VM_Test-26 success Logs for x86_64-llvm-16 / test (test_progs, false, 360) / test_progs on x86_64 with llvm-16
bpf/vmtest-bpf-next-VM_Test-27 success Logs for x86_64-llvm-16 / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on x86_64 with llvm-16
bpf/vmtest-bpf-next-VM_Test-28 success Logs for x86_64-llvm-16 / test (test_verifier, false, 360) / test_verifier on x86_64 with llvm-16
bpf/vmtest-bpf-next-VM_Test-29 success Logs for x86_64-llvm-16 / veristat
bpf/vmtest-bpf-next-VM_Test-13 success Logs for s390x-gcc / test (test_verifier, false, 360) / test_verifier on s390x with gcc
bpf/vmtest-bpf-next-VM_Test-12 success Logs for s390x-gcc / test (test_progs_no_alu32, false, 360) / test_progs_no_alu32 on s390x with gcc
bpf/vmtest-bpf-next-PR success PR summary
bpf/vmtest-bpf-next-VM_Test-10 success Logs for s390x-gcc / test (test_maps, false, 360) / test_maps on s390x with gcc
bpf/vmtest-bpf-next-VM_Test-11 success Logs for s390x-gcc / test (test_progs, false, 360) / test_progs on s390x with gcc

Commit Message

Andrii Nakryiko Oct. 27, 2023, 6:13 p.m. UTC
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.

Acked-by: Shung-Hsi Yu <shung-hsi.yu@suse.com>
Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
---
 kernel/bpf/verifier.c | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)

Comments

Eduard Zingerman Oct. 31, 2023, 3:37 p.m. UTC | #1
On Fri, 2023-10-27 at 11:13 -0700, Andrii Nakryiko wrote:
> 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.
> 
> Acked-by: Shung-Hsi Yu <shung-hsi.yu@suse.com>
> Signed-off-by: Andrii Nakryiko <andrii@kernel.org>

Acked-by: Eduard Zingerman <eddyz87@gmail.com>

fwiw, an alternative explanation might be arithmetic based.
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
. max(N) = 2**32 + (2**31 - 1) - 2**31 + 1 = 2**32
Hence [(s32)b; (s32)c] form a valid range.

At-least that's how I convinced myself.

> ---
>  kernel/bpf/verifier.c | 23 +++++++++++++++++++++++
>  1 file changed, 23 insertions(+)
> 
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> index 5082ca1ea5dc..38d21d0e46bd 100644
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
> @@ -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
> +	 * [0xfffffff0ffffff00; 0xfffffff100000010], forms a valid s32 range
> +	 * [-16, 16] ([0xffffff00; 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
>  	 */
Andrii Nakryiko Oct. 31, 2023, 5:39 p.m. UTC | #2
On Tue, Oct 31, 2023 at 8:37 AM Eduard Zingerman <eddyz87@gmail.com> wrote:
>
> On Fri, 2023-10-27 at 11:13 -0700, Andrii Nakryiko wrote:
> > 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.
> >
> > Acked-by: Shung-Hsi Yu <shung-hsi.yu@suse.com>
> > Signed-off-by: Andrii Nakryiko <andrii@kernel.org>
>
> Acked-by: Eduard Zingerman <eddyz87@gmail.com>
>
> fwiw, an alternative explanation might be arithmetic based.
> 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
> . max(N) = 2**32 + (2**31 - 1) - 2**31 + 1 = 2**32
> Hence [(s32)b; (s32)c] form a valid range.
>
> At-least that's how I convinced myself.

So the logic here follows the (visual) intuition how s64 and u64 (and
also u32 and s32) correlate. That's how I saw it. TBH, the above
mathematical way seems scary and not so straightforward to follow, so
I'm hesitant to add it to comments to not scare anyone away :)

I did try to visually represent it, but I'm not creative enough ASCII
artist to pull this off, apparently. I'll just leave it as it is for
now.

>
> > ---
> >  kernel/bpf/verifier.c | 23 +++++++++++++++++++++++
> >  1 file changed, 23 insertions(+)
> >
> > diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> > index 5082ca1ea5dc..38d21d0e46bd 100644
> > --- a/kernel/bpf/verifier.c
> > +++ b/kernel/bpf/verifier.c
> > @@ -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
> > +      * [0xfffffff0ffffff00; 0xfffffff100000010], forms a valid s32 range
> > +      * [-16, 16] ([0xffffff00; 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
> >        */
>
>
>
Alexei Starovoitov Oct. 31, 2023, 6:41 p.m. UTC | #3
On Tue, Oct 31, 2023 at 10:39 AM Andrii Nakryiko
<andrii.nakryiko@gmail.com> wrote:
> > fwiw, an alternative explanation might be arithmetic based.
> > 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
> > . max(N) = 2**32 + (2**31 - 1) - 2**31 + 1 = 2**32
> > Hence [(s32)b; (s32)c] form a valid range.
> >
> > At-least that's how I convinced myself.
>
> So the logic here follows the (visual) intuition how s64 and u64 (and
> also u32 and s32) correlate. That's how I saw it. TBH, the above
> mathematical way seems scary and not so straightforward to follow, so
> I'm hesitant to add it to comments to not scare anyone away :)

Actually Ed's math carried me across the line.
Could you add it to the commit log at least?

> I did try to visually represent it, but I'm not creative enough ASCII
> artist to pull this off, apparently. I'll just leave it as it is for
> now.

Your comment is also good, keep it as-is,
but it's harder to see that it's correct without the math part.

> > > +      * upper 32 bits. As a random example, s64 range
> > > +      * [0xfffffff0ffffff00; 0xfffffff100000010], forms a valid s32 range
> > > +      * [-16, 16] ([0xffffff00; 0x00000010]) in its 32 bit subregister.
> > > +      */

typo. It's [-256, 16]
Andrii Nakryiko Oct. 31, 2023, 6:49 p.m. UTC | #4
On Tue, Oct 31, 2023 at 11:41 AM Alexei Starovoitov
<alexei.starovoitov@gmail.com> wrote:
>
> On Tue, Oct 31, 2023 at 10:39 AM Andrii Nakryiko
> <andrii.nakryiko@gmail.com> wrote:
> > > fwiw, an alternative explanation might be arithmetic based.
> > > 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
> > > . max(N) = 2**32 + (2**31 - 1) - 2**31 + 1 = 2**32
> > > Hence [(s32)b; (s32)c] form a valid range.
> > >
> > > At-least that's how I convinced myself.
> >
> > So the logic here follows the (visual) intuition how s64 and u64 (and
> > also u32 and s32) correlate. That's how I saw it. TBH, the above
> > mathematical way seems scary and not so straightforward to follow, so
> > I'm hesitant to add it to comments to not scare anyone away :)
>
> Actually Ed's math carried me across the line.
> Could you add it to the commit log at least?

Sure, whatever it takes :) Will add to the commit message.

>
> > I did try to visually represent it, but I'm not creative enough ASCII
> > artist to pull this off, apparently. I'll just leave it as it is for
> > now.
>
> Your comment is also good, keep it as-is,
> but it's harder to see that it's correct without the math part.
>
> > > > +      * upper 32 bits. As a random example, s64 range
> > > > +      * [0xfffffff0ffffff00; 0xfffffff100000010], forms a valid s32 range
> > > > +      * [-16, 16] ([0xffffff00; 0x00000010]) in its 32 bit subregister.
> > > > +      */
>
> typo. It's [-256, 16]

I think I wanted to have 0xfffffff0 (one more f) for [-16, 16], but I
can also leave asymmetrical bounds [-256, 16]
diff mbox series

Patch

diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 5082ca1ea5dc..38d21d0e46bd 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -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
+	 * [0xfffffff0ffffff00; 0xfffffff100000010], forms a valid s32 range
+	 * [-16, 16] ([0xffffff00; 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
 	 */