diff mbox series

[RFC,bpf-next,v0,4/7] Implement wrange32_add()

Message ID 20231108054611.19531-5-shung-hsi.yu@suse.com (mailing list archive)
State RFC
Delegated to: BPF
Headers show
Series Unifying signed and unsigned min/max tracking | expand

Checks

Context Check Description
bpf/vmtest-bpf-next-PR success PR summary
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-4 success Logs for aarch64-gcc / test (test_maps, false, 360) / test_maps 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-8 success Logs for aarch64-gcc / veristat
bpf/vmtest-bpf-next-VM_Test-9 pending Logs for s390x-gcc / build / build for s390x with gcc
bpf/vmtest-bpf-next-VM_Test-10 success Logs for set-matrix
bpf/vmtest-bpf-next-VM_Test-11 success Logs for x86_64-gcc / build / build for x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-12 success Logs for x86_64-gcc / test (test_maps, false, 360) / test_maps on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-13 success Logs for x86_64-gcc / test (test_progs, false, 360) / test_progs on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-16 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-14 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-15 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-18 success Logs for x86_64-gcc / veristat / veristat on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-20 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-19 success Logs for x86_64-llvm-16 / build / build for x86_64 with llvm-16
bpf/vmtest-bpf-next-VM_Test-17 success Logs for x86_64-gcc / test (test_verifier, false, 360) / test_verifier on x86_64 with gcc
bpf/vmtest-bpf-next-VM_Test-21 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-22 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-24 success Logs for x86_64-llvm-16 / veristat
bpf/vmtest-bpf-next-VM_Test-23 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-5 fail Logs for aarch64-gcc / test (test_progs, false, 360) / test_progs on aarch64 with gcc
netdev/series_format success Posting correctly formatted
netdev/tree_selection success Clearly marked for bpf-next
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: 1352 this patch: 1352
netdev/cc_maintainers warning 9 maintainers not CCed: haoluo@google.com song@kernel.org linux-kselftest@vger.kernel.org shuah@kernel.org martin.lau@linux.dev kpsingh@kernel.org jolsa@kernel.org sdf@google.com mykolal@fb.com
netdev/build_clang success Errors and warnings before: 1379 this patch: 1379
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: 1380 this patch: 1380
netdev/checkpatch warning WARNING: Improper SPDX comment style for 'kernel/bpf/wrange.c', please use '//' instead WARNING: Missing or malformed SPDX-License-Identifier tag in line 1 WARNING: Missing or malformed SPDX-License-Identifier tag in line 2 WARNING: added, moved or deleted file(s), does MAINTAINERS need updating?
netdev/build_clang_rust success No Rust files in patch. Skipping build
netdev/kdoc success Errors and warnings before: 0 this patch: 0
netdev/source_inline success Was 0 now: 0

Commit Message

Shung-Hsi Yu Nov. 8, 2023, 5:46 a.m. UTC
Implement wrange32_add() that takes two wrange32 and compute a new wrange32
that contains all possible combinations of sums produced by adding values in
the two wrange32.

This is done by adding start and end of both wrange32 for the majority of
cases, and works even when the addition overflows. However, there still exist
cases where the addition of two wrange32 result in a range that is too large to
track, this happens when the combined length is too large. When this happens we
fallback to start=U32_MIN and end=U32_MAX.
(Calling end-minus-start as "length" because one can visual wrange32 as
a number line, and thus end point minus starting point would naturally
be the length of such line)

Additionally, make sure wrange.c gets compilation checked, and add
wrange_add.py that models and check wrange32_add().

Signed-off-by: Shung-Hsi Yu <shung-hsi.yu@suse.com>
---
 include/linux/wrange.h                        |  2 +
 kernel/bpf/Makefile                           |  3 +-
 kernel/bpf/wrange.c                           | 17 +++++
 tools/testing/selftests/bpf/formal/wrange.py  |  4 +
 .../selftests/bpf/formal/wrange_add.py        | 73 +++++++++++++++++++
 5 files changed, 98 insertions(+), 1 deletion(-)
 create mode 100644 kernel/bpf/wrange.c
 create mode 100755 tools/testing/selftests/bpf/formal/wrange_add.py
diff mbox series

Patch

diff --git a/include/linux/wrange.h b/include/linux/wrange.h
index 876e260017fe..0c4a8affd877 100644
--- a/include/linux/wrange.h
+++ b/include/linux/wrange.h
@@ -11,6 +11,8 @@  struct wrange32 {
 	u32 end;
 };
 
+struct wrange32 wrange32_add(struct wrange32 a, struct wrange32 b);
+
 static inline bool wrange32_uwrapping(struct wrange32 a) {
 	return a.end < a.start;
 }
diff --git a/kernel/bpf/Makefile b/kernel/bpf/Makefile
index f526b7573e97..f0a4ce21e2ff 100644
--- a/kernel/bpf/Makefile
+++ b/kernel/bpf/Makefile
@@ -6,7 +6,8 @@  cflags-nogcse-$(CONFIG_X86)$(CONFIG_CC_IS_GCC) := -fno-gcse
 endif
 CFLAGS_core.o += $(call cc-disable-warning, override-init) $(cflags-nogcse-yy)
 
-obj-$(CONFIG_BPF_SYSCALL) += syscall.o verifier.o inode.o helpers.o tnum.o log.o
+# At least make sure wrange.c compiles
+obj-$(CONFIG_BPF_SYSCALL) += syscall.o verifier.o inode.o helpers.o tnum.o log.o wrange.o
 obj-$(CONFIG_BPF_SYSCALL) += bpf_iter.o map_iter.o task_iter.o prog_iter.o link_iter.o
 obj-$(CONFIG_BPF_SYSCALL) += hashtab.o arraymap.o percpu_freelist.o bpf_lru_list.o lpm_trie.o map_in_map.o bloom_filter.o
 obj-$(CONFIG_BPF_SYSCALL) += local_storage.o queue_stack_maps.o ringbuf.o
diff --git a/kernel/bpf/wrange.c b/kernel/bpf/wrange.c
new file mode 100644
index 000000000000..8cdbc21a51f2
--- /dev/null
+++ b/kernel/bpf/wrange.c
@@ -0,0 +1,17 @@ 
+/* SPDX-License-Identifier: GPL-2.0-only */
+#include <linux/wrange.h>
+
+#define WRANGE32(_s, _e) ((struct wrange32) {.start = _s, .end = _e})
+
+struct wrange32 wrange32_add(struct wrange32 a, struct wrange32 b)
+{
+	u32 a_len = a.end - a.start;
+	u32 b_len = b.end - b.start;
+	u32 new_len = a_len + b_len;
+
+	/* the new start/end pair goes full circle, so any value is possible */
+	if (new_len < a_len || new_len < b_len)
+		return WRANGE32(U32_MIN, U32_MAX);
+	else
+		return WRANGE32(a.start + b.start, a.end + b.end);
+}
diff --git a/tools/testing/selftests/bpf/formal/wrange.py b/tools/testing/selftests/bpf/formal/wrange.py
index 825d79c6570f..c659cfd3a52c 100755
--- a/tools/testing/selftests/bpf/formal/wrange.py
+++ b/tools/testing/selftests/bpf/formal/wrange.py
@@ -24,6 +24,10 @@  class Wrange(abc.ABC):
         # allow end < start, so any start/end combination is valid
         return BoolVal(True)
 
+    @property
+    def length(self):
+        return self.end - self.start
+
     @property
     def uwrapping(self):
         # unsigned comparison, (u32)end < (u32)start
diff --git a/tools/testing/selftests/bpf/formal/wrange_add.py b/tools/testing/selftests/bpf/formal/wrange_add.py
new file mode 100755
index 000000000000..43d035383fe4
--- /dev/null
+++ b/tools/testing/selftests/bpf/formal/wrange_add.py
@@ -0,0 +1,73 @@ 
+#!/usr/bin/env python3
+from z3 import *
+from wrange import *
+
+
+def wrange_add(a: Wrange, b: Wrange):
+    wrange_class = type(a)
+    assert(a.SIZE == b.SIZE)
+
+    new_length = a.length + b.length
+    too_wide = Or(ULT(new_length, a.length), ULT(new_length, b.length))
+    new_start = If(too_wide, BitVecVal(0, a.SIZE), a.start + b.start)
+    new_end = If(too_wide, BitVecVal(2**a.SIZE-1, a.SIZE), a.end + b.end)
+    return wrange_class(f'{a.name} + {b.name}', new_start, new_end)
+
+
+def main():
+    x = BitVec32('x')
+    w = wrange_add(
+        # {1, 2, 3}
+        Wrange32('w1', start=BitVecVal32(1), end=BitVecVal32(3)),
+        # + {0}
+        Wrange32('w2', start=BitVecVal32(0), end=BitVecVal32(0)),
+    )   # = {1, 2, 3}
+    print('Checking {1, 2, 3} + {0} = {1, 2, 3}')
+    prove(               # 1 <= x <= 3
+        w.contains(x) == And(BitVecVal32(1) <= x, x <= BitVecVal32(3)),
+    )
+
+    w = wrange_add(
+        # {-1}
+        Wrange32('w1', start=BitVecVal32(-1), end=BitVecVal32(-1)),
+        # + {0, 1, 2}
+        Wrange32('w2', start=BitVecVal32(0), end=BitVecVal32(2)),
+    )   # = {-1, 0, 1}
+    print('\nChecking {-1} + {0, 1, 2} = {-1, 0, 1}')
+    prove(               # -1 <= x <= 1
+        w.contains(x) == And(BitVecVal32(-1) <= x, x <= BitVecVal32(1)),
+    )
+
+    # A general check to make sure wrange_add() is sound
+    x = BitVec32('x')
+    y = BitVec32('y')
+    w1 = Wrange32('w1')
+    w2 = Wrange32('w2')
+    w = wrange_add(w1, w2)
+    premise = And(
+        w1.wellformed(),
+        w2.wellformed(),
+        w1.contains(x),
+        w2.contains(y),
+    )
+    # Suppose we have a wrange32 called w1 that contains the 32-bit integer x
+    # (where x can be any possible value contained inside w1), and another
+    # wrange32 called w2 that similarly contains 32-bit integer y.
+    #
+    # The sum of w1 and w2 calculated from wrange_add(w1, w2), called w, should
+    # _always_ contains the sum of x and y, no matter what.
+    print('\nChecking that if w1.contains(x) and w2.contains(y), then wrange32_add(w1, w2).contains(x+y)')
+    print('(note: this may take awhile)')
+    prove(
+        Implies(
+            premise,
+            And(
+                w.contains(x + y),
+                w.wellformed(),
+            ),
+        )
+    )
+
+
+if __name__ == '__main__':
+    main()