All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH bpf-next v2 0/5] bpf: add bpf_memcmp and bpf_strpbrk kfuncs
@ 2026-06-24  9:36 Longjun Tang
  2026-06-24  9:36 ` [PATCH bpf-next v2 1/5] bpf: add bpf_memcmp kfunc Longjun Tang
                   ` (4 more replies)
  0 siblings, 5 replies; 10+ messages in thread
From: Longjun Tang @ 2026-06-24  9:36 UTC (permalink / raw)
  To: ast, andrii; +Cc: yonghong.song, jolsa, bpf, tanglongjun, lange_tang

From: Longjun Tang <tanglongjun@kylinos.cn>

This series adds two new BPF kfuncs to kernel/bpf/helpers.c.

=== bpf_memcmp() ===

BPF programs frequently need to compare raw binary buffers, such as:
- MAC addresses (6 bytes) in XDP/TC for L2 filtering and forwarding;
- UUIDs (16 bytes) and hash digests for allowlist/denylist policies;

Currently there is no safe way to do this — bpf_strncmp (helper) stops
at null bytes which corrupts binary data, and hand-rolled loops are
fragile with the verifier (as evidenced by the local_memcmp() workaround
in selftests/bpf/progs/dynptr_success.c). __builtin_memcmp may work in
some cases but is not verifier-safe for arbitrary kernel memory.

bpf_memcmp() compares two memory regions byte by byte, does not treat
null bytes as terminators, and returns the standard memcmp-style -1/0/1.

=== bpf_strpbrk() ===

BPF programs parsing kernel strings need to locate delimiter characters
to split tokens. Common use cases include:
- Parsing comma-separated mount options (fscrypt,noatime);
- Tokenizing space-separated /proc entries (/proc/mounts);
- Splitting colon-separated xattr values into individual entries.

The existing strspn/strcspn kfuncs can approximate this but require two
separate calls — strcspn to find the span before a delimiter, then
strspn to skip past it. bpf_strpbrk provides the missing primitive:
locate the delimiter position directly in one call. Together, strspn +
strcspn + strpbrk cover the complete set of character-set search
operations.


=== Changelog ===

v1 -> v2:
- Fixed bpf_memcmp to use unsigned char for correct comparison of
  bytes >= 0x80 (sashiko-bot)
- Improved commit messages explaining use cases and motivation
  (Alexei Starovoitov, bot+bpf-ci)
- Removed duplicated kernel-doc block in bpf_strpbrk commit
  (sashiko-bot)
- Fixed test_strpbrk_found_multiple to test with "l" (3 occurrences
  in "hello world") instead of "w" (single occurrence) (sashiko-bot)
- Added -E2BIG too-long-string tests for bpf_strpbrk (sashiko-bot)

Longjun Tang (5):
  bpf: add bpf_memcmp kfunc
  selftests/bpf: rename local bpf_memcmp to avoid conflict
  selftests/bpf: add tests for bpf_memcmp
  bpf: add bpf_strpbrk kfunc
  selftests/bpf: add tests for bpf_strpbrk

 kernel/bpf/helpers.c                          | 88 +++++++++++++++++++
 .../selftests/bpf/prog_tests/string_kfuncs.c  |  2 +
 .../selftests/bpf/progs/dynptr_success.c      | 30 +++----
 .../bpf/progs/string_kfuncs_failure1.c        | 16 ++++
 .../bpf/progs/string_kfuncs_failure2.c        |  2 +
 .../bpf/progs/string_kfuncs_success.c         | 25 ++++++
 6 files changed, 148 insertions(+), 15 deletions(-)

-- 
2.25.1


^ permalink raw reply	[flat|nested] 10+ messages in thread

* [PATCH bpf-next v2 1/5] bpf: add bpf_memcmp kfunc
  2026-06-24  9:36 [PATCH bpf-next v2 0/5] bpf: add bpf_memcmp and bpf_strpbrk kfuncs Longjun Tang
@ 2026-06-24  9:36 ` Longjun Tang
  2026-06-24  9:48   ` sashiko-bot
  2026-06-24  9:36 ` [PATCH bpf-next v2 2/5] selftests/bpf: rename local bpf_memcmp to avoid conflict Longjun Tang
                   ` (3 subsequent siblings)
  4 siblings, 1 reply; 10+ messages in thread
From: Longjun Tang @ 2026-06-24  9:36 UTC (permalink / raw)
  To: ast, andrii; +Cc: yonghong.song, jolsa, bpf, tanglongjun, lange_tang

From: Longjun Tang <tanglongjun@kylinos.cn>

Add bpf_memcmp() for binary memory comparison. Unlike bpf_strcmp,
null bytes do not terminate the comparison, making it suitable for
comparing fixed-size binary data.

BPF programs frequently need to compare raw binary buffers such as:
- MAC addresses (6 bytes) in XDP/TC for L2 filtering and forwarding;
- UUIDs (16 bytes) and hash digests in security policies;

Currently there is no safe way to do this:
- bpf_strncmp (helper) stops at null bytes, corrupting binary data;
- __builtin_memcmp may be used in some cases but is not verifier-safe
  for arbitrary kernel memory;
- hand-rolled loops are fragile and slow, as evidenced by the
  local_memcmp() workaround in selftests/bpf/progs/dynptr_success.c.

Signed-off-by: Longjun Tang <tanglongjun@kylinos.cn>
---
 kernel/bpf/helpers.c | 43 +++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 43 insertions(+)

diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index b5314c9fed3c..aadcc4bfbee6 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -3971,6 +3971,48 @@ __bpf_kfunc int bpf_strcspn(const char *s__ign, const char *reject__ign)
 	return -EFAULT;
 }
 
+/**
+ * bpf_memcmp - Compare two memory regions byte by byte
+ * @ptr1__ign: First memory region
+ * @ptr2__ign: Second memory region
+ * @size: Number of bytes to compare
+ *
+ * Compares the first @size bytes of the memory areas @ptr1__ign and
+ * @ptr2__ign. Unlike bpf_strcmp, null bytes do not terminate the comparison.
+ *
+ * Return:
+ * * %0       - The memory regions are equal
+ * * %-1      - @ptr1__ign is less than @ptr2__ign
+ * * %1       - @ptr1__ign is greater than @ptr2__ign
+ * * %-EFAULT - Cannot read one of the memory regions
+ * * %-E2BIG  - @size exceeds the maximum allowed size
+ * * %-ERANGE - One of the memory regions is outside of kernel address space
+ */
+__bpf_kfunc int bpf_memcmp(const void *ptr1__ign, const void *ptr2__ign,
+			   size_t size)
+{
+	const unsigned char *p1 = ptr1__ign, *p2 = ptr2__ign;
+	unsigned char c1, c2;
+	size_t i;
+
+	if (size > XATTR_SIZE_MAX)
+		return -E2BIG;
+	if (!copy_from_kernel_nofault_allowed(p1, size) ||
+	    !copy_from_kernel_nofault_allowed(p2, size))
+		return -ERANGE;
+
+	guard(pagefault)();
+	for (i = 0; i < size; i++) {
+		__get_kernel_nofault(&c1, p1 + i, char, err_out);
+		__get_kernel_nofault(&c2, p2 + i, char, err_out);
+		if (c1 != c2)
+			return c1 < c2 ? -1 : 1;
+	}
+	return 0;
+err_out:
+	return -EFAULT;
+}
+
 static int __bpf_strnstr(const char *s1, const char *s2, size_t len,
 			 bool ignore_case)
 {
@@ -4850,6 +4892,7 @@ BTF_ID_FLAGS(func, bpf_strlen);
 BTF_ID_FLAGS(func, bpf_strnlen);
 BTF_ID_FLAGS(func, bpf_strspn);
 BTF_ID_FLAGS(func, bpf_strcspn);
+BTF_ID_FLAGS(func, bpf_memcmp);
 BTF_ID_FLAGS(func, bpf_strstr);
 BTF_ID_FLAGS(func, bpf_strcasestr);
 BTF_ID_FLAGS(func, bpf_strnstr);
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH bpf-next v2 2/5] selftests/bpf: rename local bpf_memcmp to avoid conflict
  2026-06-24  9:36 [PATCH bpf-next v2 0/5] bpf: add bpf_memcmp and bpf_strpbrk kfuncs Longjun Tang
  2026-06-24  9:36 ` [PATCH bpf-next v2 1/5] bpf: add bpf_memcmp kfunc Longjun Tang
@ 2026-06-24  9:36 ` Longjun Tang
  2026-06-24  9:36 ` [PATCH bpf-next v2 3/5] selftests/bpf: add tests for bpf_memcmp Longjun Tang
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 10+ messages in thread
From: Longjun Tang @ 2026-06-24  9:36 UTC (permalink / raw)
  To: ast, andrii; +Cc: yonghong.song, jolsa, bpf, tanglongjun, lange_tang

From: Longjun Tang <tanglongjun@kylinos.cn>

The dynptr_success test defines a local inline bpf_memcmp() which
conflicts with the newly added bpf_memcmp kfunc when vmlinux.h
includes the kfunc declaration. Rename it to local_memcmp.

Signed-off-by: Longjun Tang <tanglongjun@kylinos.cn>
---
 .../selftests/bpf/progs/dynptr_success.c      | 30 +++++++++----------
 1 file changed, 15 insertions(+), 15 deletions(-)

diff --git a/tools/testing/selftests/bpf/progs/dynptr_success.c b/tools/testing/selftests/bpf/progs/dynptr_success.c
index e0d672d93adf..071581ff0833 100644
--- a/tools/testing/selftests/bpf/progs/dynptr_success.c
+++ b/tools/testing/selftests/bpf/progs/dynptr_success.c
@@ -624,7 +624,7 @@ int BPF_PROG(test_dynptr_skb_tp_btf, void *skb, void *location)
 	return 1;
 }
 
-static inline int bpf_memcmp(const char *a, const char *b, u32 size)
+static inline int local_memcmp(const char *a, const char *b, u32 size)
 {
 	int i;
 
@@ -650,12 +650,12 @@ int test_dynptr_copy(void *ctx)
 	err = bpf_dynptr_write(&src, 0, data, sz, 0);
 	err = err ?: bpf_dynptr_copy(&dst, 0, &src, 0, sz);
 	err = err ?: bpf_dynptr_read(buf, sz, &dst, 0, 0);
-	err = err ?: bpf_memcmp(data, buf, sz);
+	err = err ?: local_memcmp(data, buf, sz);
 
 	/* Test that offsets are handled correctly */
 	err = err ?: bpf_dynptr_copy(&dst, 3, &src, 5, sz - 5);
 	err = err ?: bpf_dynptr_read(buf, sz - 5, &dst, 3, 0);
-	err = err ?: bpf_memcmp(data + 5, buf, sz - 5);
+	err = err ?: local_memcmp(data + 5, buf, sz - 5);
 
 	bpf_ringbuf_discard_dynptr(&src, 0);
 	bpf_ringbuf_discard_dynptr(&dst, 0);
@@ -692,7 +692,7 @@ int test_dynptr_copy_xdp(struct xdp_md *xdp)
 		err = bpf_dynptr_read(&buf, len, &ptr_xdp, i * len, 0);
 		if (err)
 			goto out;
-		if (bpf_memcmp(data, buf, len) != 0)
+		if (local_memcmp(data, buf, len) != 0)
 			goto out;
 	}
 
@@ -713,7 +713,7 @@ int test_dynptr_copy_xdp(struct xdp_md *xdp)
 		err = bpf_dynptr_read(&buf, len, &ptr_buf, i * len, 0);
 		if (err)
 			goto out;
-		if (bpf_memcmp(data, buf, len) != 0)
+		if (local_memcmp(data, buf, len) != 0)
 			goto out;
 	}
 
@@ -727,7 +727,7 @@ int test_dynptr_copy_xdp(struct xdp_md *xdp)
 		err = bpf_dynptr_read(&buf, len, &ptr_xdp, 2 + i * len, 0);
 		if (err)
 			goto out;
-		if (bpf_memcmp(data, buf, len) != 0)
+		if (local_memcmp(data, buf, len) != 0)
 			goto out;
 	}
 
@@ -750,7 +750,7 @@ int test_dynptr_memset_zero(void *ctx)
 
 	err = bpf_dynptr_from_mem(memset_zero_data, data_sz, 0, &ptr);
 	err = err ?: bpf_dynptr_memset(&ptr, 0, data_sz, 0);
-	err = err ?: bpf_memcmp(zeroes, memset_zero_data, data_sz);
+	err = err ?: local_memcmp(zeroes, memset_zero_data, data_sz);
 
 	return 0;
 }
@@ -770,7 +770,7 @@ int test_dynptr_memset_notzero(void *ctx)
 
 	err = bpf_dynptr_from_mem(memset_notzero_data, data_sz, 0, &ptr);
 	err = err ?: bpf_dynptr_memset(&ptr, 0, data_sz, DYNPTR_MEMSET_VAL);
-	err = err ?: bpf_memcmp(expected, memset_notzero_data, data_sz);
+	err = err ?: local_memcmp(expected, memset_notzero_data, data_sz);
 
 	return 0;
 }
@@ -786,7 +786,7 @@ int test_dynptr_memset_zero_offset(void *ctx)
 
 	err = bpf_dynptr_from_mem(memset_zero_offset_data, data_sz, 0, &ptr);
 	err = err ?: bpf_dynptr_memset(&ptr, 8, 4, 0);
-	err = err ?: bpf_memcmp(expected, memset_zero_offset_data, data_sz);
+	err = err ?: local_memcmp(expected, memset_zero_offset_data, data_sz);
 
 	return 0;
 }
@@ -803,7 +803,7 @@ int test_dynptr_memset_zero_adjusted(void *ctx)
 	err = bpf_dynptr_from_mem(memset_zero_adjusted_data, data_sz, 0, &ptr);
 	err = err ?: bpf_dynptr_adjust(&ptr, 4, 8);
 	err = err ?: bpf_dynptr_memset(&ptr, 0, bpf_dynptr_size(&ptr), 0);
-	err = err ?: bpf_memcmp(expected, memset_zero_adjusted_data, data_sz);
+	err = err ?: local_memcmp(expected, memset_zero_adjusted_data, data_sz);
 
 	return 0;
 }
@@ -898,7 +898,7 @@ int test_dynptr_memset_xdp_chunks(struct xdp_md *xdp)
 		err = bpf_dynptr_read(&buf, chunk_sz, &ptr_xdp, offset, 0);
 		if (err)
 			goto out;
-		err = bpf_memcmp(buf, expected_buf, sizeof(buf));
+		err = local_memcmp(buf, expected_buf, sizeof(buf));
 		if (err)
 			goto out;
 	}
@@ -952,7 +952,7 @@ static __always_inline void test_dynptr_probe(void *ptr, bpf_read_dynptr_fn_t bp
 			break;
 		err = err ?: bpf_dynptr_read(&buf, len, &ptr_buf, 0, 0);
 
-		if (err || bpf_memcmp(expected_str, buf, len))
+		if (err || local_memcmp(expected_str, buf, len))
 			err = 1;
 
 		/* Reset buffer and dynptr */
@@ -986,7 +986,7 @@ static __always_inline void test_dynptr_probe_str(void *ptr,
 		err = err ?: bpf_dynptr_read(&buf, len, &ptr_buf, 0, 0);
 		if (!len)
 			continue;
-		if (err || bpf_memcmp(expected_str, buf, len - 1) || buf[len - 1] != '\0')
+		if (err || local_memcmp(expected_str, buf, len - 1) || buf[len - 1] != '\0')
 			err = 1;
 	}
 	bpf_ringbuf_discard_dynptr(&ptr_buf, 0);
@@ -1012,7 +1012,7 @@ static __always_inline void test_dynptr_probe_xdp(struct xdp_md *xdp, void *ptr,
 		if (len > sizeof(buf))
 			continue;
 		err = err ?: bpf_dynptr_read(&buf, len, &ptr_xdp, off, 0);
-		if (err || bpf_memcmp(expected_str, buf, len))
+		if (err || local_memcmp(expected_str, buf, len))
 			err = 1;
 		/* Reset buffer and dynptr */
 		__builtin_memset(buf, 0, sizeof(buf));
@@ -1048,7 +1048,7 @@ static __always_inline void test_dynptr_probe_str_xdp(struct xdp_md *xdp, void *
 
 		if (!len)
 			continue;
-		if (err || bpf_memcmp(expected_str, buf, len - 1) || buf[len - 1] != '\0')
+		if (err || local_memcmp(expected_str, buf, len - 1) || buf[len - 1] != '\0')
 			err = 1;
 
 		__builtin_memset(buf, 0, sizeof(buf));
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH bpf-next v2 3/5] selftests/bpf: add tests for bpf_memcmp
  2026-06-24  9:36 [PATCH bpf-next v2 0/5] bpf: add bpf_memcmp and bpf_strpbrk kfuncs Longjun Tang
  2026-06-24  9:36 ` [PATCH bpf-next v2 1/5] bpf: add bpf_memcmp kfunc Longjun Tang
  2026-06-24  9:36 ` [PATCH bpf-next v2 2/5] selftests/bpf: rename local bpf_memcmp to avoid conflict Longjun Tang
@ 2026-06-24  9:36 ` Longjun Tang
  2026-06-24  9:36 ` [PATCH bpf-next v2 4/5] bpf: add bpf_strpbrk kfunc Longjun Tang
  2026-06-24  9:36 ` [PATCH bpf-next v2 5/5] selftests/bpf: add tests for bpf_strpbrk Longjun Tang
  4 siblings, 0 replies; 10+ messages in thread
From: Longjun Tang @ 2026-06-24  9:36 UTC (permalink / raw)
  To: ast, andrii; +Cc: yonghong.song, jolsa, bpf, tanglongjun, lange_tang

From: Longjun Tang <tanglongjun@kylinos.cn>

Add functional tests for equality, ordering, zero-size, and null-byte
handling. Add negative tests for NULL, user-space, and invalid kernel
pointer arguments.

Signed-off-by: Longjun Tang <tanglongjun@kylinos.cn>
---
 .../bpf/progs/string_kfuncs_failure1.c          |  9 +++++++++
 .../selftests/bpf/progs/string_kfuncs_success.c | 17 +++++++++++++++++
 2 files changed, 26 insertions(+)

diff --git a/tools/testing/selftests/bpf/progs/string_kfuncs_failure1.c b/tools/testing/selftests/bpf/progs/string_kfuncs_failure1.c
index bddc4e8579d2..a85d64605d0e 100644
--- a/tools/testing/selftests/bpf/progs/string_kfuncs_failure1.c
+++ b/tools/testing/selftests/bpf/progs/string_kfuncs_failure1.c
@@ -9,6 +9,9 @@
 char *user_ptr = (char *)1;
 char *invalid_kern_ptr = (char *)-1;
 
+extern int bpf_memcmp(const void *ptr1__ign, const void *ptr2__ign,
+		      size_t size) __ksym;
+
 /*
  * When passing userspace pointers, the error code differs based on arch:
  *   -ERANGE on arches with non-overlapping address spaces
@@ -53,6 +56,8 @@ SEC("syscall")  __retval(USER_PTR_ERR)int test_strnstr_null1(void *ctx) { return
 SEC("syscall")  __retval(USER_PTR_ERR)int test_strnstr_null2(void *ctx) { return bpf_strnstr("hello", NULL, 1); }
 SEC("syscall")  __retval(USER_PTR_ERR)int test_strncasestr_null1(void *ctx) { return bpf_strncasestr(NULL, "hello", 1); }
 SEC("syscall")  __retval(USER_PTR_ERR)int test_strncasestr_null2(void *ctx) { return bpf_strncasestr("hello", NULL, 1); }
+SEC("syscall")  __retval(USER_PTR_ERR)int test_memcmp_null1(void *ctx) { return bpf_memcmp(NULL, "x", 1); }
+SEC("syscall")  __retval(USER_PTR_ERR)int test_memcmp_null2(void *ctx) { return bpf_memcmp("x", NULL, 1); }
 
 /* Passing userspace ptr to string kfuncs */
 SEC("syscall") __retval(USER_PTR_ERR) int test_strcmp_user_ptr1(void *ctx) { return bpf_strcmp(user_ptr, "hello"); }
@@ -79,6 +84,8 @@ SEC("syscall") __retval(USER_PTR_ERR) int test_strnstr_user_ptr1(void *ctx) { re
 SEC("syscall") __retval(USER_PTR_ERR) int test_strnstr_user_ptr2(void *ctx) { return bpf_strnstr("hello", user_ptr, 1); }
 SEC("syscall") __retval(USER_PTR_ERR) int test_strncasestr_user_ptr1(void *ctx) { return bpf_strncasestr(user_ptr, "hello", 1); }
 SEC("syscall") __retval(USER_PTR_ERR) int test_strncasestr_user_ptr2(void *ctx) { return bpf_strncasestr("hello", user_ptr, 1); }
+SEC("syscall") __retval(USER_PTR_ERR) int test_memcmp_user_ptr1(void *ctx) { return bpf_memcmp(user_ptr, "x", 1); }
+SEC("syscall") __retval(USER_PTR_ERR) int test_memcmp_user_ptr2(void *ctx) { return bpf_memcmp("x", user_ptr, 1); }
 
 #endif /* __TARGET_ARCH_s390 */
 
@@ -107,5 +114,7 @@ SEC("syscall") __retval(-EFAULT) int test_strnstr_pagefault1(void *ctx) { return
 SEC("syscall") __retval(-EFAULT) int test_strnstr_pagefault2(void *ctx) { return bpf_strnstr("hello", invalid_kern_ptr, 1); }
 SEC("syscall") __retval(-EFAULT) int test_strncasestr_pagefault1(void *ctx) { return bpf_strncasestr(invalid_kern_ptr, "hello", 1); }
 SEC("syscall") __retval(-EFAULT) int test_strncasestr_pagefault2(void *ctx) { return bpf_strncasestr("hello", invalid_kern_ptr, 1); }
+SEC("syscall") __retval(-EFAULT) int test_memcmp_pagefault1(void *ctx) { return bpf_memcmp(invalid_kern_ptr, "x", 1); }
+SEC("syscall") __retval(-EFAULT) int test_memcmp_pagefault2(void *ctx) { return bpf_memcmp("x", invalid_kern_ptr, 1); }
 
 char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/string_kfuncs_success.c b/tools/testing/selftests/bpf/progs/string_kfuncs_success.c
index f65b1226a81a..c2be9edcd282 100644
--- a/tools/testing/selftests/bpf/progs/string_kfuncs_success.c
+++ b/tools/testing/selftests/bpf/progs/string_kfuncs_success.c
@@ -7,6 +7,9 @@
 
 char str[] = "hello world";
 
+extern int bpf_memcmp(const void *ptr1__ign, const void *ptr2__ign,
+		      size_t size) __ksym;
+
 #define __test(retval) SEC("syscall") __success __retval(retval)
 
 /* Functional tests */
@@ -60,4 +63,18 @@ __test(-ENOENT) int test_strncasestr_notfound2(void *ctx) { return bpf_strncases
 __test(-ENOENT) int test_strncasestr_notfound3(void *ctx) { return bpf_strncasestr("", "a", 0); }
 __test(0) int test_strncasestr_empty(void *ctx) { return bpf_strncasestr(str, "", 1); }
 
+/* bpf_memcmp - functional tests */
+char data1[] = "hello world";
+char data2[] = "hello world";
+char data3[] = "hello worle";
+__test(0) int test_memcmp_eq(void *ctx) { return bpf_memcmp(data1, data2, sizeof(data1)); }
+__test(-1) int test_memcmp_neq_less(void *ctx) { return bpf_memcmp(data1, data3, sizeof(data1)); }
+__test(1) int test_memcmp_neq_greater(void *ctx) { return bpf_memcmp(data3, data1, sizeof(data1)); }
+__test(0) int test_memcmp_size_zero(void *ctx) { return bpf_memcmp(data1, data3, 0); }
+/* memcmp should NOT stop at null byte - compare past '\0' */
+char bin1[] = { 'a', '\0', 'c' };
+char bin2[] = { 'a', '\0', 'd' };
+__test(0) int test_memcmp_null_byte_eq(void *ctx) { return bpf_memcmp(bin1, bin1, sizeof(bin1)); }
+__test(-1) int test_memcmp_null_byte_neq(void *ctx) { return bpf_memcmp(bin1, bin2, sizeof(bin1)); }
+
 char _license[] SEC("license") = "GPL";
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH bpf-next v2 4/5] bpf: add bpf_strpbrk kfunc
  2026-06-24  9:36 [PATCH bpf-next v2 0/5] bpf: add bpf_memcmp and bpf_strpbrk kfuncs Longjun Tang
                   ` (2 preceding siblings ...)
  2026-06-24  9:36 ` [PATCH bpf-next v2 3/5] selftests/bpf: add tests for bpf_memcmp Longjun Tang
@ 2026-06-24  9:36 ` Longjun Tang
  2026-06-24  9:49   ` sashiko-bot
  2026-06-24  9:36 ` [PATCH bpf-next v2 5/5] selftests/bpf: add tests for bpf_strpbrk Longjun Tang
  4 siblings, 1 reply; 10+ messages in thread
From: Longjun Tang @ 2026-06-24  9:36 UTC (permalink / raw)
  To: ast, andrii; +Cc: yonghong.song, jolsa, bpf, tanglongjun, lange_tang

From: Longjun Tang <tanglongjun@kylinos.cn>

Add bpf_strpbrk() that returns the index of the first character in a
string matching any character from the accept set, or -ENOENT if no
match is found.

BPF programs parsing kernel strings often need to locate delimiter
characters to split tokens. Common use cases include:
- Parsing comma-separated mount options (e.g., fscrypt,noatime);
- Tokenizing space-separated /proc entries (e.g., /proc/mounts);
- Splitting colon-separated xattr values into individual entries.

The existing strspn/strcspn kfuncs can be used for this but require
two separate calls: strcspn to find the span before a delimiter,
then strspn to skip past it. bpf_strpbrk provides the missing
primitive — find the delimiter position directly in one call.
Together, strspn + strcspn + strpbrk cover the complete set of
C standard library character-set search operations.

Signed-off-by: Longjun Tang <tanglongjun@kylinos.cn>
---
 kernel/bpf/helpers.c | 45 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 45 insertions(+)

diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index aadcc4bfbee6..ead00ec314f5 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -3971,6 +3971,50 @@ __bpf_kfunc int bpf_strcspn(const char *s__ign, const char *reject__ign)
 	return -EFAULT;
 }
 
+/**
+ * bpf_strpbrk - Find the first occurrence of a set of characters in a string
+ * @s__ign: The string to be searched
+ * @accept__ign: The string containing the characters to search for
+ *
+ * Return:
+ * * >=0      - Index of the first character in @s__ign that matches any
+ *              character in @accept__ign
+ * * %-ENOENT - No character of @accept__ign found in @s__ign
+ * * %-EFAULT - Cannot read one of the strings
+ * * %-E2BIG  - One of the strings is too large
+ * * %-ERANGE - One of the strings is outside of kernel address space
+ */
+__bpf_kfunc int bpf_strpbrk(const char *s__ign, const char *accept__ign)
+{
+	char cs, ca;
+	int i, j;
+
+	if (!copy_from_kernel_nofault_allowed(s__ign, 1) ||
+	    !copy_from_kernel_nofault_allowed(accept__ign, 1)) {
+		return -ERANGE;
+	}
+
+	guard(pagefault)();
+	for (i = 0; i < XATTR_SIZE_MAX; i++) {
+		__get_kernel_nofault(&cs, s__ign, char, err_out);
+		if (cs == '\0')
+			return -ENOENT;
+		for (j = 0; j < XATTR_SIZE_MAX; j++) {
+			__get_kernel_nofault(&ca, accept__ign + j, char, err_out);
+			if (ca == '\0')
+				break;
+			if (cs == ca)
+				return i;
+		}
+		if (j == XATTR_SIZE_MAX)
+			return -E2BIG;
+		s__ign++;
+	}
+	return -E2BIG;
+err_out:
+	return -EFAULT;
+}
+
 /**
  * bpf_memcmp - Compare two memory regions byte by byte
  * @ptr1__ign: First memory region
@@ -4892,6 +4936,7 @@ BTF_ID_FLAGS(func, bpf_strlen);
 BTF_ID_FLAGS(func, bpf_strnlen);
 BTF_ID_FLAGS(func, bpf_strspn);
 BTF_ID_FLAGS(func, bpf_strcspn);
+BTF_ID_FLAGS(func, bpf_strpbrk);
 BTF_ID_FLAGS(func, bpf_memcmp);
 BTF_ID_FLAGS(func, bpf_strstr);
 BTF_ID_FLAGS(func, bpf_strcasestr);
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* [PATCH bpf-next v2 5/5] selftests/bpf: add tests for bpf_strpbrk
  2026-06-24  9:36 [PATCH bpf-next v2 0/5] bpf: add bpf_memcmp and bpf_strpbrk kfuncs Longjun Tang
                   ` (3 preceding siblings ...)
  2026-06-24  9:36 ` [PATCH bpf-next v2 4/5] bpf: add bpf_strpbrk kfunc Longjun Tang
@ 2026-06-24  9:36 ` Longjun Tang
  2026-06-24  9:48   ` sashiko-bot
  2026-06-24 12:08   ` bot+bpf-ci
  4 siblings, 2 replies; 10+ messages in thread
From: Longjun Tang @ 2026-06-24  9:36 UTC (permalink / raw)
  To: ast, andrii; +Cc: yonghong.song, jolsa, bpf, tanglongjun, lange_tang

From: Longjun Tang <tanglongjun@kylinos.cn>

Add functional tests for first-match, multiple-match, not-found, and
empty-accept cases. Add negative tests for NULL, user-space, and
invalid kernel pointer arguments.

Signed-off-by: Longjun Tang <tanglongjun@kylinos.cn>
---
 tools/testing/selftests/bpf/prog_tests/string_kfuncs.c    | 2 ++
 .../testing/selftests/bpf/progs/string_kfuncs_failure1.c  | 7 +++++++
 .../testing/selftests/bpf/progs/string_kfuncs_failure2.c  | 2 ++
 tools/testing/selftests/bpf/progs/string_kfuncs_success.c | 8 ++++++++
 4 files changed, 19 insertions(+)

diff --git a/tools/testing/selftests/bpf/prog_tests/string_kfuncs.c b/tools/testing/selftests/bpf/prog_tests/string_kfuncs.c
index 300032a19445..959ff37110a1 100644
--- a/tools/testing/selftests/bpf/prog_tests/string_kfuncs.c
+++ b/tools/testing/selftests/bpf/prog_tests/string_kfuncs.c
@@ -20,6 +20,8 @@ static const char * const test_cases[] = {
 	"strspn_accept",
 	"strcspn_str",
 	"strcspn_reject",
+		"strpbrk_str",
+		"strpbrk_accept",
 	"strstr",
 	"strcasestr",
 	"strnstr",
diff --git a/tools/testing/selftests/bpf/progs/string_kfuncs_failure1.c b/tools/testing/selftests/bpf/progs/string_kfuncs_failure1.c
index a85d64605d0e..e67fe9700e59 100644
--- a/tools/testing/selftests/bpf/progs/string_kfuncs_failure1.c
+++ b/tools/testing/selftests/bpf/progs/string_kfuncs_failure1.c
@@ -9,6 +9,7 @@
 char *user_ptr = (char *)1;
 char *invalid_kern_ptr = (char *)-1;
 
+extern int bpf_strpbrk(const char *s__ign, const char *accept__ign) __ksym;
 extern int bpf_memcmp(const void *ptr1__ign, const void *ptr2__ign,
 		      size_t size) __ksym;
 
@@ -58,6 +59,8 @@ SEC("syscall")  __retval(USER_PTR_ERR)int test_strncasestr_null1(void *ctx) { re
 SEC("syscall")  __retval(USER_PTR_ERR)int test_strncasestr_null2(void *ctx) { return bpf_strncasestr("hello", NULL, 1); }
 SEC("syscall")  __retval(USER_PTR_ERR)int test_memcmp_null1(void *ctx) { return bpf_memcmp(NULL, "x", 1); }
 SEC("syscall")  __retval(USER_PTR_ERR)int test_memcmp_null2(void *ctx) { return bpf_memcmp("x", NULL, 1); }
+SEC("syscall")  __retval(USER_PTR_ERR)int test_strpbrk_null1(void *ctx) { return bpf_strpbrk(NULL, "x"); }
+SEC("syscall")  __retval(USER_PTR_ERR)int test_strpbrk_null2(void *ctx) { return bpf_strpbrk("x", NULL); }
 
 /* Passing userspace ptr to string kfuncs */
 SEC("syscall") __retval(USER_PTR_ERR) int test_strcmp_user_ptr1(void *ctx) { return bpf_strcmp(user_ptr, "hello"); }
@@ -86,6 +89,8 @@ SEC("syscall") __retval(USER_PTR_ERR) int test_strncasestr_user_ptr1(void *ctx)
 SEC("syscall") __retval(USER_PTR_ERR) int test_strncasestr_user_ptr2(void *ctx) { return bpf_strncasestr("hello", user_ptr, 1); }
 SEC("syscall") __retval(USER_PTR_ERR) int test_memcmp_user_ptr1(void *ctx) { return bpf_memcmp(user_ptr, "x", 1); }
 SEC("syscall") __retval(USER_PTR_ERR) int test_memcmp_user_ptr2(void *ctx) { return bpf_memcmp("x", user_ptr, 1); }
+SEC("syscall") __retval(USER_PTR_ERR) int test_strpbrk_user_ptr1(void *ctx) { return bpf_strpbrk(user_ptr, "x"); }
+SEC("syscall") __retval(USER_PTR_ERR) int test_strpbrk_user_ptr2(void *ctx) { return bpf_strpbrk("x", user_ptr); }
 
 #endif /* __TARGET_ARCH_s390 */
 
@@ -116,5 +121,7 @@ SEC("syscall") __retval(-EFAULT) int test_strncasestr_pagefault1(void *ctx) { re
 SEC("syscall") __retval(-EFAULT) int test_strncasestr_pagefault2(void *ctx) { return bpf_strncasestr("hello", invalid_kern_ptr, 1); }
 SEC("syscall") __retval(-EFAULT) int test_memcmp_pagefault1(void *ctx) { return bpf_memcmp(invalid_kern_ptr, "x", 1); }
 SEC("syscall") __retval(-EFAULT) int test_memcmp_pagefault2(void *ctx) { return bpf_memcmp("x", invalid_kern_ptr, 1); }
+SEC("syscall") __retval(-EFAULT) int test_strpbrk_pagefault1(void *ctx) { return bpf_strpbrk(invalid_kern_ptr, "x"); }
+SEC("syscall") __retval(-EFAULT) int test_strpbrk_pagefault2(void *ctx) { return bpf_strpbrk("x", invalid_kern_ptr); }
 
 char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/string_kfuncs_failure2.c b/tools/testing/selftests/bpf/progs/string_kfuncs_failure2.c
index 412c53b87b18..d3b978e453bd 100644
--- a/tools/testing/selftests/bpf/progs/string_kfuncs_failure2.c
+++ b/tools/testing/selftests/bpf/progs/string_kfuncs_failure2.c
@@ -19,6 +19,8 @@ SEC("syscall") int test_strspn_str_too_long(void *ctx) { return bpf_strspn(long_
 SEC("syscall") int test_strspn_accept_too_long(void *ctx) { return bpf_strspn("b", long_str); }
 SEC("syscall") int test_strcspn_str_too_long(void *ctx) { return bpf_strcspn(long_str, "b"); }
 SEC("syscall") int test_strcspn_reject_too_long(void *ctx) { return bpf_strcspn("b", long_str); }
+SEC("syscall") int test_strpbrk_str_too_long(void *ctx) { return bpf_strpbrk(long_str, "z"); }
+SEC("syscall") int test_strpbrk_accept_too_long(void *ctx) { return bpf_strpbrk("b", long_str); }
 SEC("syscall") int test_strstr_too_long(void *ctx) { return bpf_strstr(long_str, "hello"); }
 SEC("syscall") int test_strcasestr_too_long(void *ctx) { return bpf_strcasestr(long_str, "hello"); }
 SEC("syscall") int test_strnstr_too_long(void *ctx) { return bpf_strnstr(long_str, "hello", sizeof(long_str)); }
diff --git a/tools/testing/selftests/bpf/progs/string_kfuncs_success.c b/tools/testing/selftests/bpf/progs/string_kfuncs_success.c
index c2be9edcd282..25b8c897da54 100644
--- a/tools/testing/selftests/bpf/progs/string_kfuncs_success.c
+++ b/tools/testing/selftests/bpf/progs/string_kfuncs_success.c
@@ -7,6 +7,7 @@
 
 char str[] = "hello world";
 
+extern int bpf_strpbrk(const char *s__ign, const char *accept__ign) __ksym;
 extern int bpf_memcmp(const void *ptr1__ign, const void *ptr2__ign,
 		      size_t size) __ksym;
 
@@ -63,6 +64,13 @@ __test(-ENOENT) int test_strncasestr_notfound2(void *ctx) { return bpf_strncases
 __test(-ENOENT) int test_strncasestr_notfound3(void *ctx) { return bpf_strncasestr("", "a", 0); }
 __test(0) int test_strncasestr_empty(void *ctx) { return bpf_strncasestr(str, "", 1); }
 
+/* bpf_strpbrk - functional tests */
+__test(0) int test_strpbrk_found_first(void *ctx) { return bpf_strpbrk(str, "h"); }
+__test(4) int test_strpbrk_found_middle(void *ctx) { return bpf_strpbrk(str, "ow"); }
+__test(2) int test_strpbrk_found_multiple(void *ctx) { return bpf_strpbrk(str, "l"); }
+__test(-ENOENT) int test_strpbrk_notfound(void *ctx) { return bpf_strpbrk(str, "xyz"); }
+__test(-ENOENT) int test_strpbrk_empty_accept(void *ctx) { return bpf_strpbrk(str, ""); }
+
 /* bpf_memcmp - functional tests */
 char data1[] = "hello world";
 char data2[] = "hello world";
-- 
2.25.1


^ permalink raw reply related	[flat|nested] 10+ messages in thread

* Re: [PATCH bpf-next v2 5/5] selftests/bpf: add tests for bpf_strpbrk
  2026-06-24  9:36 ` [PATCH bpf-next v2 5/5] selftests/bpf: add tests for bpf_strpbrk Longjun Tang
@ 2026-06-24  9:48   ` sashiko-bot
  2026-06-24 12:08   ` bot+bpf-ci
  1 sibling, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-06-24  9:48 UTC (permalink / raw)
  To: Longjun Tang; +Cc: bpf

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:

Pre-existing issues:
- [High] The string search kfuncs (bpf_strpbrk, bpf_strspn, bpf_strcspn) have an O(N*M) algorithmic complexity that can trigger up to 4.29 billion memory reads in the kernel, stalling the CPU. The newly added tests explicitly mask this vulnerability.
--

commit 8efd73628e6ad8d84061c17ed578ece973a232c2
Author: Longjun Tang <tanglongjun@kylinos.cn>

selftests/bpf: add tests for bpf_strpbrk
    
Add functional tests for first-match, multiple-match, not-found, and
empty-accept cases. Add negative tests for NULL, user-space, and
invalid kernel pointer arguments.

> diff --git a/tools/testing/selftests/bpf/progs/string_kfuncs_failure2.c b/tools/testing/selftests/bpf/progs/string_kfuncs_failure2.c
> index 412c53b87b182..d3b978e453bd7 100644
> --- a/tools/testing/selftests/bpf/progs/string_kfuncs_failure2.c
> +++ b/tools/testing/selftests/bpf/progs/string_kfuncs_failure2.c
> @@ -19,6 +19,8 @@ SEC("syscall") int test_strspn_str_too_long(void *ctx) { return bpf_strspn(long_
>  SEC("syscall") int test_strspn_accept_too_long(void *ctx) { return bpf_strspn("b", long_str); }
>  SEC("syscall") int test_strcspn_str_too_long(void *ctx) { return bpf_strcspn(long_str, "b"); }
>  SEC("syscall") int test_strcspn_reject_too_long(void *ctx) { return bpf_strcspn("b", long_str); }
> +SEC("syscall") int test_strpbrk_str_too_long(void *ctx) { return bpf_strpbrk(long_str, "z"); }
> +SEC("syscall") int test_strpbrk_accept_too_long(void *ctx) { return bpf_strpbrk("b", long_str); }

[Severity: High]
This is a pre-existing issue, but by only testing a single large string
against a 1-byte string, do these tests mask an O(N*M) algorithmic
complexity in the underlying implementation?

Looking at the nested loops in bpf_strpbrk():

kernel/bpf/helpers.c:bpf_strpbrk() {
	...
	for (i = 0; i < XATTR_SIZE_MAX; i++) {
		__get_kernel_nofault(&cs, s__ign, char, err_out);
		if (cs == '\0')
			return -ENOENT;
		for (j = 0; j < XATTR_SIZE_MAX; j++) {
			__get_kernel_nofault(&ca, accept__ign + j, char, err_out);
	...
}

Since XATTR_SIZE_MAX is 65536, if a BPF program passes two 64KB buffers
constructed to avoid early loop termination (e.g., no matching characters
and a null-terminator only at the very end of the inner string), couldn't
the function perform up to 4.29 billion calls to __get_kernel_nofault()?

Could this stall the CPU for several seconds, bypassing BPF execution time
limits and potentially triggering a watchdog soft-lockup?

Would it be worth adding a test case that passes long_str for both arguments
to expose how the kernel handles this worst-case scenario?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260624093638.117062-1-lange_tang@163.com?part=5

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH bpf-next v2 1/5] bpf: add bpf_memcmp kfunc
  2026-06-24  9:36 ` [PATCH bpf-next v2 1/5] bpf: add bpf_memcmp kfunc Longjun Tang
@ 2026-06-24  9:48   ` sashiko-bot
  0 siblings, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-06-24  9:48 UTC (permalink / raw)
  To: Longjun Tang; +Cc: bpf

Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Critical] bpf_memcmp bypasses verifier memory bounds checking by using __ign arguments, creating an arbitrary kernel memory read oracle.
--

commit 29136173f7ef78c9e1fa53cea458359e9ef94899
Author: Longjun Tang <tanglongjun@kylinos.cn>

bpf: add bpf_memcmp kfunc

This commit adds a new bpf_memcmp kfunc for binary memory comparison in BPF
programs. Unlike bpf_strcmp, null bytes do not terminate the comparison,
making it suitable for comparing fixed-size binary data like MAC addresses
and UUIDs.

> diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
> index 8e196c9b7c50f..897e8d71eae45 100644
> --- a/kernel/bpf/helpers.c
> +++ b/kernel/bpf/helpers.c

[ ... ]

> @@ -4070,6 +4070,48 @@ __bpf_kfunc int bpf_strcspn(const char *s__ign, const char *reject__ign)
>  	return -EFAULT;
>  }
>  
> +/**
> + * bpf_memcmp - Compare two memory regions byte by byte
> + * @ptr1__ign: First memory region
> + * @ptr2__ign: Second memory region
> + * @size: Number of bytes to compare
> + *
> + * Compares the first @size bytes of the memory areas @ptr1__ign and
> + * @ptr2__ign. Unlike bpf_strcmp, null bytes do not terminate the comparison.
> + *
> + * Return:
> + * * %0       - The memory regions are equal
> + * * %-1      - @ptr1__ign is less than @ptr2__ign
> + * * %1       - @ptr1__ign is greater than @ptr2__ign
> + * * %-EFAULT - Cannot read one of the memory regions
> + * * %-E2BIG  - @size exceeds the maximum allowed size
> + * * %-ERANGE - One of the memory regions is outside of kernel address space
> + */
> +__bpf_kfunc int bpf_memcmp(const void *ptr1__ign, const void *ptr2__ign,
> +			   size_t size)

[Severity: Critical]
Does the __ign suffix on the pointer arguments bypass the verifier's memory
bounds checking?

If the verifier skips type and bounds checking for ptr1__ign and ptr2__ign,
could this regression allow passing arbitrary scalars as pointers?

Since the function reads from these addresses using __get_kernel_nofault
and returns a byte-by-byte comparison result, can this be used as an
arbitrary kernel memory read oracle via binary search?

Would it be safer to use KF_ARG_PTR_TO_MEM (for example, by using a __sz
suffix) to enforce static verifier bounds checking instead of relying on
runtime nofault reads?

> +{
> +	const unsigned char *p1 = ptr1__ign, *p2 = ptr2__ign;
> +	unsigned char c1, c2;

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260624093638.117062-1-lange_tang@163.com?part=1

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH bpf-next v2 4/5] bpf: add bpf_strpbrk kfunc
  2026-06-24  9:36 ` [PATCH bpf-next v2 4/5] bpf: add bpf_strpbrk kfunc Longjun Tang
@ 2026-06-24  9:49   ` sashiko-bot
  0 siblings, 0 replies; 10+ messages in thread
From: sashiko-bot @ 2026-06-24  9:49 UTC (permalink / raw)
  To: Longjun Tang; +Cc: bpf

Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:

New issues:
- [Critical] Incomplete bounds check in `bpf_strpbrk` allows pointer wraparound and out-of-bounds reads.
- [High] Unoptimized O(N*M) nested loop in `bpf_strpbrk` causes CPU exhaustion and Denial of Service.

Pre-existing issues:
- [Critical] Incomplete bounds checks in pre-existing string kfuncs allow pointer wraparound and arbitrary memory reads.
- [High] Pre-existing string kfuncs (`bpf_strcspn`, `bpf_strspn`, `bpf_strnstr`, etc.) contain O(N*M) nested loops vulnerable to CPU exhaustion.
--

commit eccea6a6a708ac6a6e30a1e094434ed92118f8f0
Author: Longjun Tang <tanglongjun@kylinos.cn>

bpf: add bpf_strpbrk kfunc

Add bpf_strpbrk() that returns the index of the first character in a
string matching any character from the accept set, or -ENOENT if no
match is found.

> diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
> --- a/kernel/bpf/helpers.c
> +++ b/kernel/bpf/helpers.c

[ ... ]

> +__bpf_kfunc int bpf_strpbrk(const char *s__ign, const char *accept__ign)
> +{
> +	char cs, ca;
> +	int i, j;
> +
> +	if (!copy_from_kernel_nofault_allowed(s__ign, 1) ||
> +	    !copy_from_kernel_nofault_allowed(accept__ign, 1)) {

[Severity: Critical]
Since the __ign suffix allows arbitrary scalar values as pointers, could
checking only the first byte allow a pointer near ULONG_MAX
(e.g. ULONG_MAX - 5) to pass this validation?

Because the loop below increments the pointer up to XATTR_SIZE_MAX times,
it appears the pointer could wrap around to user space (0). Since
__get_kernel_nofault() uses __get_user(), could this allow reading
arbitrary user-space memory, bypassing security boundaries?

[Severity: Critical]
This is a pre-existing issue, but other string functions like bpf_strcspn()
and __bpf_strnstr() use this same 1-byte validation pattern.

In __bpf_strnstr(), accessing s1 + i + j can reach up to 131,070 bytes past
the start pointer, bypassing mmap_min_addr and allowing direct user space
memory reads from within BPF. Could this be updated across all string kfuncs
to validate the entire memory range being accessed?

> +		return -ERANGE;
> +	}
> +
> +	guard(pagefault)();
> +	for (i = 0; i < XATTR_SIZE_MAX; i++) {
> +		__get_kernel_nofault(&cs, s__ign, char, err_out);
> +		if (cs == '\0')
> +			return -ENOENT;
> +		for (j = 0; j < XATTR_SIZE_MAX; j++) {

[Severity: High]
Does this nested loop introduce a potential CPU stall or soft lockup?

If a BPF program passes 64KB strings for both s__ign and accept__ign where
no characters match and the null terminator is placed near the end of
accept__ign, the outer loop could execute 65,536 times with the inner loop
executing ~65,535 times per iteration.

With page faults disabled via guard(pagefault)(), could this lead to
~4.29 billion memory accesses, potentially causing a Denial of Service?

[Severity: High]
This is a pre-existing issue, but other kfuncs like bpf_strcspn() and
bpf_strspn() also contain these XATTR_SIZE_MAX nested loops.

Could crafted strings force those inner loops to run fully for every
iteration of the outer loop, causing the same CPU exhaustion? Is there a
way to optimize these searches or limit the execution time?

> +			__get_kernel_nofault(&ca, accept__ign + j, char, err_out);
> +			if (ca == '\0')
> +				break;
> +			if (cs == ca)
> +				return i;
> +		}

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260624093638.117062-1-lange_tang@163.com?part=4

^ permalink raw reply	[flat|nested] 10+ messages in thread

* Re: [PATCH bpf-next v2 5/5] selftests/bpf: add tests for bpf_strpbrk
  2026-06-24  9:36 ` [PATCH bpf-next v2 5/5] selftests/bpf: add tests for bpf_strpbrk Longjun Tang
  2026-06-24  9:48   ` sashiko-bot
@ 2026-06-24 12:08   ` bot+bpf-ci
  1 sibling, 0 replies; 10+ messages in thread
From: bot+bpf-ci @ 2026-06-24 12:08 UTC (permalink / raw)
  To: lange_tang, ast, andrii
  Cc: yonghong.song, jolsa, bpf, tanglongjun, lange_tang, ast, andrii,
	daniel, martin.lau, eddyz87, yonghong.song, clm, ihor.solodrai

[-- Attachment #1: Type: text/plain, Size: 869 bytes --]

> diff --git a/tools/testing/selftests/bpf/prog_tests/string_kfuncs.c b/tools/testing/selftests/bpf/prog_tests/string_kfuncs.c
> index 300032a19445..959ff37110a1 100644
> --- a/tools/testing/selftests/bpf/prog_tests/string_kfuncs.c
> +++ b/tools/testing/selftests/bpf/prog_tests/string_kfuncs.c
> @@ -20,6 +20,8 @@ static const char * const test_cases[] = {
>  	"strspn_accept",
>  	"strcspn_str",
>  	"strcspn_reject",
> +		"strpbrk_str",
> +		"strpbrk_accept",

This isn't a bug, but should the two new "strpbrk_str" and
"strpbrk_accept" entries use a single tab to match the indentation of
the other entries in test_cases[]?


---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md

CI run summary: https://github.com/kernel-patches/bpf/actions/runs/28095722357

^ permalink raw reply	[flat|nested] 10+ messages in thread

end of thread, other threads:[~2026-06-24 12:08 UTC | newest]

Thread overview: 10+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-24  9:36 [PATCH bpf-next v2 0/5] bpf: add bpf_memcmp and bpf_strpbrk kfuncs Longjun Tang
2026-06-24  9:36 ` [PATCH bpf-next v2 1/5] bpf: add bpf_memcmp kfunc Longjun Tang
2026-06-24  9:48   ` sashiko-bot
2026-06-24  9:36 ` [PATCH bpf-next v2 2/5] selftests/bpf: rename local bpf_memcmp to avoid conflict Longjun Tang
2026-06-24  9:36 ` [PATCH bpf-next v2 3/5] selftests/bpf: add tests for bpf_memcmp Longjun Tang
2026-06-24  9:36 ` [PATCH bpf-next v2 4/5] bpf: add bpf_strpbrk kfunc Longjun Tang
2026-06-24  9:49   ` sashiko-bot
2026-06-24  9:36 ` [PATCH bpf-next v2 5/5] selftests/bpf: add tests for bpf_strpbrk Longjun Tang
2026-06-24  9:48   ` sashiko-bot
2026-06-24 12:08   ` bot+bpf-ci

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.