* [PATCH bpf-next 0/5] bpf: add bpf_memcmp and bpf_strpbrk kfuncs
@ 2026-06-23 3:07 Longjun Tang
2026-06-23 3:07 ` [PATCH bpf-next 1/5] bpf: add bpf_memcmp kfunc Longjun Tang
` (5 more replies)
0 siblings, 6 replies; 12+ messages in thread
From: Longjun Tang @ 2026-06-23 3:07 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(): Binary memory comparison, unlike bpf_strcmp null
bytes do not terminate the comparison. Returns 0, -1, or 1 with
standard error codes (-EFAULT, -E2BIG, -ERANGE).
- bpf_strpbrk(): Find the first occurrence of any character from an
accept set in a string. Complements the existing strspn/strcspn
kfuncs for string tokenization use cases.
All kfuncs are registered in common_btf_ids, making them available
to all BPF program types. Selftests cover functional correctness
(equality, ordering, edge cases) and negative paths (NULL, user,
and invalid kernel pointers).
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 | 105 ++++++++++++++++++
.../selftests/bpf/progs/dynptr_success.c | 30 ++---
.../bpf/progs/string_kfuncs_failure1.c | 16 +++
.../bpf/progs/string_kfuncs_success.c | 25 +++++
4 files changed, 161 insertions(+), 15 deletions(-)
--
2.25.1
^ permalink raw reply [flat|nested] 12+ messages in thread
* [PATCH bpf-next 1/5] bpf: add bpf_memcmp kfunc
2026-06-23 3:07 [PATCH bpf-next 0/5] bpf: add bpf_memcmp and bpf_strpbrk kfuncs Longjun Tang
@ 2026-06-23 3:07 ` Longjun Tang
2026-06-23 3:22 ` sashiko-bot
2026-06-23 3:54 ` bot+bpf-ci
2026-06-23 3:07 ` [PATCH bpf-next 2/5] selftests/bpf: rename local bpf_memcmp to avoid conflict Longjun Tang
` (4 subsequent siblings)
5 siblings, 2 replies; 12+ messages in thread
From: Longjun Tang @ 2026-06-23 3:07 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. Returns 0, -1, or 1.
Registered in common_btf_ids.
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..872326f0c2b8 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 char *p1 = ptr1__ign, *p2 = ptr2__ign;
+ 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] 12+ messages in thread
* [PATCH bpf-next 2/5] selftests/bpf: rename local bpf_memcmp to avoid conflict
2026-06-23 3:07 [PATCH bpf-next 0/5] bpf: add bpf_memcmp and bpf_strpbrk kfuncs Longjun Tang
2026-06-23 3:07 ` [PATCH bpf-next 1/5] bpf: add bpf_memcmp kfunc Longjun Tang
@ 2026-06-23 3:07 ` Longjun Tang
2026-06-23 3:08 ` [PATCH bpf-next 3/5] selftests/bpf: add tests for bpf_memcmp Longjun Tang
` (3 subsequent siblings)
5 siblings, 0 replies; 12+ messages in thread
From: Longjun Tang @ 2026-06-23 3:07 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] 12+ messages in thread
* [PATCH bpf-next 3/5] selftests/bpf: add tests for bpf_memcmp
2026-06-23 3:07 [PATCH bpf-next 0/5] bpf: add bpf_memcmp and bpf_strpbrk kfuncs Longjun Tang
2026-06-23 3:07 ` [PATCH bpf-next 1/5] bpf: add bpf_memcmp kfunc Longjun Tang
2026-06-23 3:07 ` [PATCH bpf-next 2/5] selftests/bpf: rename local bpf_memcmp to avoid conflict Longjun Tang
@ 2026-06-23 3:08 ` Longjun Tang
2026-06-23 3:26 ` sashiko-bot
2026-06-23 3:08 ` [PATCH bpf-next 4/5] bpf: add bpf_strpbrk kfunc Longjun Tang
` (2 subsequent siblings)
5 siblings, 1 reply; 12+ messages in thread
From: Longjun Tang @ 2026-06-23 3:08 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] 12+ messages in thread
* [PATCH bpf-next 4/5] bpf: add bpf_strpbrk kfunc
2026-06-23 3:07 [PATCH bpf-next 0/5] bpf: add bpf_memcmp and bpf_strpbrk kfuncs Longjun Tang
` (2 preceding siblings ...)
2026-06-23 3:08 ` [PATCH bpf-next 3/5] selftests/bpf: add tests for bpf_memcmp Longjun Tang
@ 2026-06-23 3:08 ` Longjun Tang
2026-06-23 3:20 ` sashiko-bot
2026-06-23 3:08 ` [PATCH bpf-next 5/5] selftests/bpf: add tests for bpf_strpbrk Longjun Tang
2026-06-23 3:19 ` [PATCH bpf-next 0/5] bpf: add bpf_memcmp and bpf_strpbrk kfuncs Alexei Starovoitov
5 siblings, 1 reply; 12+ messages in thread
From: Longjun Tang @ 2026-06-23 3:08 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.
Complements the existing strspn/strcspn kfuncs for string tokenization.
Registered in common_btf_ids.
Signed-off-by: Longjun Tang <tanglongjun@kylinos.cn>
---
kernel/bpf/helpers.c | 62 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 62 insertions(+)
diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index 872326f0c2b8..f24cfdb22635 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -3971,6 +3971,67 @@ __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_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 +4953,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] 12+ messages in thread
* [PATCH bpf-next 5/5] selftests/bpf: add tests for bpf_strpbrk
2026-06-23 3:07 [PATCH bpf-next 0/5] bpf: add bpf_memcmp and bpf_strpbrk kfuncs Longjun Tang
` (3 preceding siblings ...)
2026-06-23 3:08 ` [PATCH bpf-next 4/5] bpf: add bpf_strpbrk kfunc Longjun Tang
@ 2026-06-23 3:08 ` Longjun Tang
2026-06-23 3:19 ` sashiko-bot
2026-06-23 3:19 ` [PATCH bpf-next 0/5] bpf: add bpf_memcmp and bpf_strpbrk kfuncs Alexei Starovoitov
5 siblings, 1 reply; 12+ messages in thread
From: Longjun Tang @ 2026-06-23 3:08 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>
---
.../testing/selftests/bpf/progs/string_kfuncs_failure1.c | 7 +++++++
tools/testing/selftests/bpf/progs/string_kfuncs_success.c | 8 ++++++++
2 files changed, 15 insertions(+)
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_success.c b/tools/testing/selftests/bpf/progs/string_kfuncs_success.c
index c2be9edcd282..e67b80ab3cd2 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(6) int test_strpbrk_found_multiple(void *ctx) { return bpf_strpbrk(str, "w"); }
+__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] 12+ messages in thread
* Re: [PATCH bpf-next 0/5] bpf: add bpf_memcmp and bpf_strpbrk kfuncs
2026-06-23 3:07 [PATCH bpf-next 0/5] bpf: add bpf_memcmp and bpf_strpbrk kfuncs Longjun Tang
` (4 preceding siblings ...)
2026-06-23 3:08 ` [PATCH bpf-next 5/5] selftests/bpf: add tests for bpf_strpbrk Longjun Tang
@ 2026-06-23 3:19 ` Alexei Starovoitov
5 siblings, 0 replies; 12+ messages in thread
From: Alexei Starovoitov @ 2026-06-23 3:19 UTC (permalink / raw)
To: Longjun Tang
Cc: Alexei Starovoitov, Andrii Nakryiko, Yonghong Song, Jiri Olsa,
bpf, tanglongjun
On Mon, Jun 22, 2026 at 8:08 PM Longjun Tang <lange_tang@163.com> wrote:
>
> From: Longjun Tang <tanglongjun@kylinos.cn>
>
> This series adds two new BPF kfuncs to kernel/bpf/helpers.c:
>
> - bpf_memcmp(): Binary memory comparison, unlike bpf_strcmp null
> bytes do not terminate the comparison. Returns 0, -1, or 1 with
> standard error codes (-EFAULT, -E2BIG, -ERANGE).
>
> - bpf_strpbrk(): Find the first occurrence of any character from an
> accept set in a string. Complements the existing strspn/strcspn
> kfuncs for string tokenization use cases.
The patch explains "what", but doesn't answer "why".
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH bpf-next 5/5] selftests/bpf: add tests for bpf_strpbrk
2026-06-23 3:08 ` [PATCH bpf-next 5/5] selftests/bpf: add tests for bpf_strpbrk Longjun Tang
@ 2026-06-23 3:19 ` sashiko-bot
0 siblings, 0 replies; 12+ messages in thread
From: sashiko-bot @ 2026-06-23 3:19 UTC (permalink / raw)
To: Longjun Tang; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
New issues:
- [Low] The selftests for bpf_strpbrk lack negative tests for the -E2BIG (too long string) error paths, leaving this core logic completely untested.
- [Low] The test `test_strpbrk_found_multiple` does not actually test a multiple-match scenario as claimed in the commit message.
Pre-existing issues:
- [High] O(N*M) algorithmic complexity in string kfuncs (bpf_strpbrk, bpf_strspn, bpf_strcspn) allows a malicious BPF program to stall the CPU and cause a Denial of Service.
--
commit a3423f682404351839c3773b03881a48a31cc20c
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_success.c b/tools/testing/selftests/bpf/progs/string_kfuncs_success.c
> index c2be9edcd2821..e67b80ab3cd2c 100644
> --- a/tools/testing/selftests/bpf/progs/string_kfuncs_success.c
> +++ b/tools/testing/selftests/bpf/progs/string_kfuncs_success.c
[ ... ]
> @@ -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(6) int test_strpbrk_found_multiple(void *ctx) { return bpf_strpbrk(str, "w"); }
[Severity: Low]
Does this test actually verify a multiple-match scenario?
The target string "hello world" only contains a single 'w', and the accept
string is just "w". Would it be better to test with a character that appears
multiple times, such as "l" or "o"?
> +__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 */
[Severity: Low]
Are we missing negative tests for the -E2BIG (too long string) error paths?
It appears that the test_cases array in prog_tests/string_kfuncs.c was
not updated to include "strpbrk", and string_kfuncs_failure2.c is missing
the corresponding BPF programs for bpf_strpbrk.
Could this leave the bounds-checking logic untested?
[Severity: High]
This is a pre-existing issue, but does the algorithmic complexity in the
underlying string kfuncs allow for a Denial of Service?
Looking at kernel/bpf/helpers.c, functions like bpf_strpbrk(), bpf_strspn(),
and bpf_strcspn() use nested loops bounded by XATTR_SIZE_MAX (64KB):
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);
...
}
If a BPF program calls one of these kfuncs with two strings up to 64KB in
length that do not share any characters, the loops will execute O(N*M)
times, resulting in up to 4.29 billion memory reads.
Since the verifier is unaware of this internal kfunc complexity, can this
deliberately stall the CPU for tens of seconds and trigger RCU stalls or
watchdog lockup panics?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260623030803.44798-1-lange_tang@163.com?part=5
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH bpf-next 4/5] bpf: add bpf_strpbrk kfunc
2026-06-23 3:08 ` [PATCH bpf-next 4/5] bpf: add bpf_strpbrk kfunc Longjun Tang
@ 2026-06-23 3:20 ` sashiko-bot
0 siblings, 0 replies; 12+ messages in thread
From: sashiko-bot @ 2026-06-23 3:20 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:
- [High] O(N*M) worst-case time complexity in `bpf_strpbrk` allows for unprivileged CPU soft lockup, bypassing BPF verifier limits.
- [High] The validation of `s__ign` and `accept__ign` only checks the first byte, allowing pointers to wrap around the address space boundary.
- [Low] Duplicate `bpf_memcmp` kernel-doc block mistakenly included before `bpf_strpbrk`.
Pre-existing issues:
- [High] O(N^2) worst-case time complexity in `bpf_strcspn` allows for unprivileged CPU soft lockup.
--
commit 6710699312c85ee398aba5d00ff7694b56ae2a3c
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.
Complements the existing strspn/strcspn kfuncs for string tokenization.
Registered in common_btf_ids.
> diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
> index e444c58cad9e9..34b7a9993c0c9 100644
> --- a/kernel/bpf/helpers.c
> +++ b/kernel/bpf/helpers.c
> @@ -4070,6 +4070,67 @@ __bpf_kfunc int bpf_strcspn(const char *s__ign, const char *reject__ign)
[Severity: High]
This is a pre-existing issue, but does bpf_strcspn() have a similar
time complexity issue?
Looking at the existing bpf_strcspn() in kernel/bpf/helpers.c:
__bpf_kfunc int bpf_strcspn(const char *s__ign, const char *reject__ign)
{
...
guard(pagefault)();
for (i = 0; i < XATTR_SIZE_MAX; i++) {
__get_kernel_nofault(&cs, s__ign, char, err_out);
...
for (j = 0; j < XATTR_SIZE_MAX; j++) {
__get_kernel_nofault(&cr, reject__ign + j, char, err_out);
...
}
...
}
...
}
Like the new bpf_strpbrk() function, calling bpf_strcspn() with two long
strings that share no characters could also result in roughly 4 billion memory
reads while page faults are disabled. Could this also cause a multi-second CPU
stall per call?
> 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
> + */
[Severity: Low]
This isn't a bug, but it looks like the kernel-doc block for bpf_memcmp() was
accidentally duplicated here instead of providing documentation for the new
bpf_strpbrk() kfunc.
> +/**
> + * 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;
> + }
[Severity: High]
Does this validation prevent pointers from wrapping around the address space
boundary?
Since only 1 byte is checked here by copy_from_kernel_nofault_allowed(), but
the function reads up to XATTR_SIZE_MAX bytes in the loop below, if a caller
passes a pointer near the end of the address space (such as ULONG_MAX - 10),
could the pointer increment and wrap around to 0x0?
This could allow a BPF program to bypass the kernel memory isolation boundary
and read user-space memory instead of kernel memory.
> +
> + 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++;
> + }
[Severity: High]
Can this nested loop cause a CPU soft lockup?
Because both loops iterate up to XATTR_SIZE_MAX (65536) times, calling this
with two long strings that don't share any characters could result in over
4 billion memory accesses via __get_kernel_nofault().
Since guard(pagefault)() disables page faults and preemption, could this stall
the CPU for several seconds and bypass the BPF verifier's execution time
limits?
> + return -E2BIG;
> +err_out:
> + return -EFAULT;
> +}
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260623030803.44798-1-lange_tang@163.com?part=4
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH bpf-next 1/5] bpf: add bpf_memcmp kfunc
2026-06-23 3:07 ` [PATCH bpf-next 1/5] bpf: add bpf_memcmp kfunc Longjun Tang
@ 2026-06-23 3:22 ` sashiko-bot
2026-06-23 3:54 ` bot+bpf-ci
1 sibling, 0 replies; 12+ messages in thread
From: sashiko-bot @ 2026-06-23 3:22 UTC (permalink / raw)
To: Longjun Tang; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] bpf_memcmp uses signed `char` for memory comparison, violating standard memcmp behavior and causing incorrect sorting for bytes >= 0x80.
- [High] Unbounded O(N^2) loop in bpf_strpbrk and bpf_strcspn can cause a kernel soft lockup.
- [High] Arbitrary kernel memory read oracle via unverified `__ign` arguments in bpf_memcmp.
- [Medium] bpf_memcmp returns -1 to indicate `ptr1 < ptr2`, which collides with the standard error code -EPERM.
--
commit 0b32f895c504b5a659411b32505ad2719b89eec0
Author: Longjun Tang <tanglongjun@kylinos.cn>
bpf: add bpf_memcmp kfunc
Add bpf_memcmp() for binary memory comparison. Unlike bpf_strcmp,
null bytes do not terminate the comparison. Returns 0, -1, or 1.
Registered in common_btf_ids.
> diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
> index 8e196c9b7c50f..e444c58cad9e9 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)
[Severity: High]
Can the nested loops in bpf_strcspn() and bpf_strpbrk() cause a kernel soft
lockup?
If a BPF program passes two distinct 65,535-character strings that do not
share any characters, the inner loop executes 65,535 times and cleanly
breaks at the null byte. Because the inner loop breaks early, the outer
loop continues, resulting in up to 4.29 billion calls to
__get_kernel_nofault(). Can this stall the CPU and trigger a watchdog
timeout?
> return -EFAULT;
> }
>
> +/**
> + * bpf_memcmp - Compare two memory regions byte by byte
[ ... ]
> + */
> +__bpf_kfunc int bpf_memcmp(const void *ptr1__ign, const void *ptr2__ign,
> + size_t size)
[Severity: High]
Does using the __ign suffix for these pointers bypass the BPF verifier's
pointer safety checks?
Since bpf_memcmp() takes an explicit size parameter, using __ign instead
of standard bounds-checked arguments might allow a program to pass an
unverified scalar as a kernel address, potentially creating an oracle to
read arbitrary kernel memory.
> +{
> + const char *p1 = ptr1__ign, *p2 = ptr2__ign;
> + 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;
[Severity: High]
Will using signed char variables for the memory comparison result in
incorrect sorting for bytes with values of 0x80 or higher?
On architectures where char is signed, a byte with the most significant bit
set (like 0xFF) evaluates as a negative number. When compared against 0x00,
-1 < 0 evaluates to true, causing the function to incorrectly return -1.
[Severity: Medium]
Does returning -1 for a valid comparison result collide with the standard
-EPERM error code?
Since this function also returns standard negative error codes (like
-EFAULT or -E2BIG), standard BPF error checking patterns like if (ret < 0)
might mistakenly interpret a valid ptr1 < ptr2 result as an operation
failure.
> + }
> + return 0;
> +err_out:
> + return -EFAULT;
> +}
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260623030803.44798-1-lange_tang@163.com?part=1
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH bpf-next 3/5] selftests/bpf: add tests for bpf_memcmp
2026-06-23 3:08 ` [PATCH bpf-next 3/5] selftests/bpf: add tests for bpf_memcmp Longjun Tang
@ 2026-06-23 3:26 ` sashiko-bot
0 siblings, 0 replies; 12+ messages in thread
From: sashiko-bot @ 2026-06-23 3:26 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:
- [Critical] The `__ign` suffix in `bpf_memcmp` and other string kfuncs bypasses BPF verifier checks, allowing arbitrary kernel memory reads and bypassing CAP_PERFMON capability restrictions.
--
commit 7bc905114c7536361d9f8dec63d9c798b78c1718
Author: Longjun Tang <tanglongjun@kylinos.cn>
selftests/bpf: add tests for bpf_memcmp
Add functional tests for equality, ordering, zero-size, and null-byte
handling. Add negative tests for NULL, user-space, and invalid kernel
pointer arguments.
> diff --git a/tools/testing/selftests/bpf/progs/string_kfuncs_failure1.c b/tools/testing/selftests/bpf/progs/string_kfuncs_failure1.c
> index bddc4e8579d24..a85d64605d0e7 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;
[Severity: Critical]
This is a pre-existing issue, but does using the __ign suffix in the kfunc
declaration of bpf_memcmp allow BPF programs to bypass verifier capability
checks?
Looking at check_kfunc_args() in kernel/bpf/verifier.c, it appears the
verifier completely skips validation for arguments with the __ign suffix:
if (is_kfunc_arg_ignore(btf, &args[i]) || is_kfunc_arg_implicit(meta, i))
continue;
Since bpf_memcmp() is available in common_kfunc_set without requiring
CAP_PERFMON, could an attacker pass an arbitrary kernel address as a
SCALAR_VALUE to ptr1__ign or ptr2__ign?
Because bpf_memcmp() uses copy_from_kernel_nofault_allowed() and safely
handles the memory access without crashing, this could potentially provide an
oracle to leak kernel memory byte-by-byte, acting as an unrestricted bypass
for bpf_probe_read_kernel.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20260623030803.44798-1-lange_tang@163.com?part=3
^ permalink raw reply [flat|nested] 12+ messages in thread
* Re: [PATCH bpf-next 1/5] bpf: add bpf_memcmp kfunc
2026-06-23 3:07 ` [PATCH bpf-next 1/5] bpf: add bpf_memcmp kfunc Longjun Tang
2026-06-23 3:22 ` sashiko-bot
@ 2026-06-23 3:54 ` bot+bpf-ci
1 sibling, 0 replies; 12+ messages in thread
From: bot+bpf-ci @ 2026-06-23 3:54 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: 962 bytes --]
> commit ed9698292695c1294690b0b42df1c4b26e8df6a7
> Author: Longjun Tang <tanglongjun@kylinos.cn>
> Date: Tue Jun 23 11:07:58 2026 +0800
>
> bpf: add bpf_memcmp kfunc
>
> Add bpf_memcmp() for binary memory comparison. Unlike bpf_strcmp,
> null bytes do not terminate the comparison. Returns 0, -1, or 1.
> Registered in common_btf_ids.
>
> Signed-off-by: Longjun Tang <tanglongjun@kylinos.cn>
The commit message explains what bpf_memcmp() does but doesn't explain
why it's needed. What use case or BPF program requires binary memory
comparison that can't be achieved with existing helpers?
Reference: https://lore.kernel.org/bpf/CAADnVQJTXdNUsLxEHZPg9V356XfuqrSTwSE3W9EaOuMjMznMEA@mail.gmail.com/
---
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/28000096945
^ permalink raw reply [flat|nested] 12+ messages in thread
end of thread, other threads:[~2026-06-23 3:54 UTC | newest]
Thread overview: 12+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-23 3:07 [PATCH bpf-next 0/5] bpf: add bpf_memcmp and bpf_strpbrk kfuncs Longjun Tang
2026-06-23 3:07 ` [PATCH bpf-next 1/5] bpf: add bpf_memcmp kfunc Longjun Tang
2026-06-23 3:22 ` sashiko-bot
2026-06-23 3:54 ` bot+bpf-ci
2026-06-23 3:07 ` [PATCH bpf-next 2/5] selftests/bpf: rename local bpf_memcmp to avoid conflict Longjun Tang
2026-06-23 3:08 ` [PATCH bpf-next 3/5] selftests/bpf: add tests for bpf_memcmp Longjun Tang
2026-06-23 3:26 ` sashiko-bot
2026-06-23 3:08 ` [PATCH bpf-next 4/5] bpf: add bpf_strpbrk kfunc Longjun Tang
2026-06-23 3:20 ` sashiko-bot
2026-06-23 3:08 ` [PATCH bpf-next 5/5] selftests/bpf: add tests for bpf_strpbrk Longjun Tang
2026-06-23 3:19 ` sashiko-bot
2026-06-23 3:19 ` [PATCH bpf-next 0/5] bpf: add bpf_memcmp and bpf_strpbrk kfuncs Alexei Starovoitov
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox