* [PATCH bpf-next v3 0/2] bpf: Add kfunc bpf_strncasecmp()
@ 2026-01-20 7:03 Yuzuki Ishiyama
2026-01-20 7:03 ` [PATCH bpf-next v3 1/2] bpf: add bpf_strncasecmp kfunc Yuzuki Ishiyama
2026-01-20 7:03 ` [PATCH bpf-next v3 2/2] selftests/bpf: Test kfunc bpf_strncasecmp Yuzuki Ishiyama
0 siblings, 2 replies; 9+ messages in thread
From: Yuzuki Ishiyama @ 2026-01-20 7:03 UTC (permalink / raw)
To: bpf
Cc: mykyta.yatsenko5, vmalik, andrii, ast, daniel, martin.lau,
Yuzuki Ishiyama
This patchset introduces bpf_strncasecmp to allow case-insensitive and
limited-length string comparison. This is useful for parsing protocol
headers like HTTP.
---
Changes in v3:
- Use ternary operator to maintain style consistency
- Reverted unnecessary doc comment about XATTR_SIZE_MAX
Changes in v2:
- Compute max_sz upfront and remove len check from the loop body
- Document that @len is limited by XATTR_SIZE_MAX
Yuzuki Ishiyama (2):
bpf: add bpf_strncasecmp kfunc
selftests/bpf: Test kfunc bpf_strncasecmp
kernel/bpf/helpers.c | 34 +++++++++++++++----
.../selftests/bpf/prog_tests/string_kfuncs.c | 1 +
.../bpf/progs/string_kfuncs_failure1.c | 6 ++++
.../bpf/progs/string_kfuncs_failure2.c | 1 +
.../bpf/progs/string_kfuncs_success.c | 7 ++++
5 files changed, 43 insertions(+), 6 deletions(-)
--
2.52.0
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH bpf-next v3 1/2] bpf: add bpf_strncasecmp kfunc
2026-01-20 7:03 [PATCH bpf-next v3 0/2] bpf: Add kfunc bpf_strncasecmp() Yuzuki Ishiyama
@ 2026-01-20 7:03 ` Yuzuki Ishiyama
2026-01-20 7:21 ` bot+bpf-ci
2026-01-20 7:03 ` [PATCH bpf-next v3 2/2] selftests/bpf: Test kfunc bpf_strncasecmp Yuzuki Ishiyama
1 sibling, 1 reply; 9+ messages in thread
From: Yuzuki Ishiyama @ 2026-01-20 7:03 UTC (permalink / raw)
To: bpf
Cc: mykyta.yatsenko5, vmalik, andrii, ast, daniel, martin.lau,
Yuzuki Ishiyama
bpf_strncasecmp() function performs same like bpf_strcasecmp() except
limiting the comparison to a specific length.
Signed-off-by: Yuzuki Ishiyama <ishiyama@hpc.is.uec.ac.jp>
---
kernel/bpf/helpers.c | 34 ++++++++++++++++++++++++++++------
1 file changed, 28 insertions(+), 6 deletions(-)
diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index 9eaa4185e0a7..753753f039ff 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -3406,18 +3406,20 @@ __bpf_kfunc void __bpf_trap(void)
* __get_kernel_nofault instead of plain dereference to make them safe.
*/
-static int __bpf_strcasecmp(const char *s1, const char *s2, bool ignore_case)
+static int __bpf_strncasecmp(const char *s1, const char *s2, bool ignore_case, size_t len)
{
char c1, c2;
- int i;
+ int i, max_sz;
if (!copy_from_kernel_nofault_allowed(s1, 1) ||
!copy_from_kernel_nofault_allowed(s2, 1)) {
return -ERANGE;
}
+ max_sz = min_t(int, len, XATTR_SIZE_MAX);
+
guard(pagefault)();
- for (i = 0; i < XATTR_SIZE_MAX; i++) {
+ for (i = 0; i < max_sz; i++) {
__get_kernel_nofault(&c1, s1, char, err_out);
__get_kernel_nofault(&c2, s2, char, err_out);
if (ignore_case) {
@@ -3431,7 +3433,7 @@ static int __bpf_strcasecmp(const char *s1, const char *s2, bool ignore_case)
s1++;
s2++;
}
- return -E2BIG;
+ return i == XATTR_SIZE_MAX ? -E2BIG : 0;
err_out:
return -EFAULT;
}
@@ -3451,7 +3453,7 @@ static int __bpf_strcasecmp(const char *s1, const char *s2, bool ignore_case)
*/
__bpf_kfunc int bpf_strcmp(const char *s1__ign, const char *s2__ign)
{
- return __bpf_strcasecmp(s1__ign, s2__ign, false);
+ return __bpf_strncasecmp(s1__ign, s2__ign, false, XATTR_SIZE_MAX);
}
/**
@@ -3469,7 +3471,26 @@ __bpf_kfunc int bpf_strcmp(const char *s1__ign, const char *s2__ign)
*/
__bpf_kfunc int bpf_strcasecmp(const char *s1__ign, const char *s2__ign)
{
- return __bpf_strcasecmp(s1__ign, s2__ign, true);
+ return __bpf_strncasecmp(s1__ign, s2__ign, true, XATTR_SIZE_MAX);
+}
+
+/*
+ * bpf_strncasecmp - Compare two length-limited strings, ignoring case
+ * @s1__ign: One string
+ * @s2__ign: Another string
+ * @len: The maximum number of characters to compare
+ *
+ * Return:
+ * * %0 - Strings are equal
+ * * %-1 - @s1__ign is smaller
+ * * %1 - @s2__ign is smaller
+ * * %-EFAULT - Cannot read one of the strings
+ * * %-E2BIG - One of strings is too large
+ * * %-ERANGE - One of strings is outside of kernel address space
+ */
+__bpf_kfunc int bpf_strncasecmp(const char *s1__ign, const char *s2__ign, size_t len)
+{
+ return __bpf_strncasecmp(s1__ign, s2__ign, true, len);
}
/**
@@ -4521,6 +4542,7 @@ BTF_ID_FLAGS(func, bpf_iter_dmabuf_destroy, KF_ITER_DESTROY | KF_SLEEPABLE)
BTF_ID_FLAGS(func, __bpf_trap)
BTF_ID_FLAGS(func, bpf_strcmp);
BTF_ID_FLAGS(func, bpf_strcasecmp);
+BTF_ID_FLAGS(func, bpf_strncasecmp);
BTF_ID_FLAGS(func, bpf_strchr);
BTF_ID_FLAGS(func, bpf_strchrnul);
BTF_ID_FLAGS(func, bpf_strnchr);
--
2.52.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* [PATCH bpf-next v3 2/2] selftests/bpf: Test kfunc bpf_strncasecmp
2026-01-20 7:03 [PATCH bpf-next v3 0/2] bpf: Add kfunc bpf_strncasecmp() Yuzuki Ishiyama
2026-01-20 7:03 ` [PATCH bpf-next v3 1/2] bpf: add bpf_strncasecmp kfunc Yuzuki Ishiyama
@ 2026-01-20 7:03 ` Yuzuki Ishiyama
2026-01-20 9:30 ` Viktor Malik
1 sibling, 1 reply; 9+ messages in thread
From: Yuzuki Ishiyama @ 2026-01-20 7:03 UTC (permalink / raw)
To: bpf
Cc: mykyta.yatsenko5, vmalik, andrii, ast, daniel, martin.lau,
Yuzuki Ishiyama
Add testsuites for kfunc bpf_strncasecmp.
Signed-off-by: Yuzuki Ishiyama <ishiyama@hpc.is.uec.ac.jp>
---
tools/testing/selftests/bpf/prog_tests/string_kfuncs.c | 1 +
tools/testing/selftests/bpf/progs/string_kfuncs_failure1.c | 6 ++++++
tools/testing/selftests/bpf/progs/string_kfuncs_failure2.c | 1 +
tools/testing/selftests/bpf/progs/string_kfuncs_success.c | 7 +++++++
4 files changed, 15 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/string_kfuncs.c b/tools/testing/selftests/bpf/prog_tests/string_kfuncs.c
index 0f3bf594e7a5..300032a19445 100644
--- a/tools/testing/selftests/bpf/prog_tests/string_kfuncs.c
+++ b/tools/testing/selftests/bpf/prog_tests/string_kfuncs.c
@@ -9,6 +9,7 @@
static const char * const test_cases[] = {
"strcmp",
"strcasecmp",
+ "strncasecmp",
"strchr",
"strchrnul",
"strnchr",
diff --git a/tools/testing/selftests/bpf/progs/string_kfuncs_failure1.c b/tools/testing/selftests/bpf/progs/string_kfuncs_failure1.c
index 826e6b6aff7e..bddc4e8579d2 100644
--- a/tools/testing/selftests/bpf/progs/string_kfuncs_failure1.c
+++ b/tools/testing/selftests/bpf/progs/string_kfuncs_failure1.c
@@ -33,6 +33,8 @@ SEC("syscall") __retval(USER_PTR_ERR) int test_strcmp_null1(void *ctx) { return
SEC("syscall") __retval(USER_PTR_ERR)int test_strcmp_null2(void *ctx) { return bpf_strcmp("hello", NULL); }
SEC("syscall") __retval(USER_PTR_ERR) int test_strcasecmp_null1(void *ctx) { return bpf_strcasecmp(NULL, "HELLO"); }
SEC("syscall") __retval(USER_PTR_ERR)int test_strcasecmp_null2(void *ctx) { return bpf_strcasecmp("HELLO", NULL); }
+SEC("syscall") __retval(USER_PTR_ERR)int test_strncasecmp_null1(void *ctx) { return bpf_strncasecmp(NULL, "HELLO", 5); }
+SEC("syscall") __retval(USER_PTR_ERR)int test_strncasecmp_null2(void *ctx) { return bpf_strncasecmp("HELLO", NULL, 5); }
SEC("syscall") __retval(USER_PTR_ERR)int test_strchr_null(void *ctx) { return bpf_strchr(NULL, 'a'); }
SEC("syscall") __retval(USER_PTR_ERR)int test_strchrnul_null(void *ctx) { return bpf_strchrnul(NULL, 'a'); }
SEC("syscall") __retval(USER_PTR_ERR)int test_strnchr_null(void *ctx) { return bpf_strnchr(NULL, 1, 'a'); }
@@ -57,6 +59,8 @@ SEC("syscall") __retval(USER_PTR_ERR) int test_strcmp_user_ptr1(void *ctx) { ret
SEC("syscall") __retval(USER_PTR_ERR) int test_strcmp_user_ptr2(void *ctx) { return bpf_strcmp("hello", user_ptr); }
SEC("syscall") __retval(USER_PTR_ERR) int test_strcasecmp_user_ptr1(void *ctx) { return bpf_strcasecmp(user_ptr, "HELLO"); }
SEC("syscall") __retval(USER_PTR_ERR) int test_strcasecmp_user_ptr2(void *ctx) { return bpf_strcasecmp("HELLO", user_ptr); }
+SEC("syscall") __retval(USER_PTR_ERR) int test_strncasecmp_user_ptr1(void *ctx) { return bpf_strncasecmp(user_ptr, "HELLO", 5); }
+SEC("syscall") __retval(USER_PTR_ERR) int test_strncasecmp_user_ptr2(void *ctx) { return bpf_strncasecmp("HELLO", user_ptr, 5); }
SEC("syscall") __retval(USER_PTR_ERR) int test_strchr_user_ptr(void *ctx) { return bpf_strchr(user_ptr, 'a'); }
SEC("syscall") __retval(USER_PTR_ERR) int test_strchrnul_user_ptr(void *ctx) { return bpf_strchrnul(user_ptr, 'a'); }
SEC("syscall") __retval(USER_PTR_ERR) int test_strnchr_user_ptr(void *ctx) { return bpf_strnchr(user_ptr, 1, 'a'); }
@@ -83,6 +87,8 @@ SEC("syscall") __retval(-EFAULT) int test_strcmp_pagefault1(void *ctx) { return
SEC("syscall") __retval(-EFAULT) int test_strcmp_pagefault2(void *ctx) { return bpf_strcmp("hello", invalid_kern_ptr); }
SEC("syscall") __retval(-EFAULT) int test_strcasecmp_pagefault1(void *ctx) { return bpf_strcasecmp(invalid_kern_ptr, "HELLO"); }
SEC("syscall") __retval(-EFAULT) int test_strcasecmp_pagefault2(void *ctx) { return bpf_strcasecmp("HELLO", invalid_kern_ptr); }
+SEC("syscall") __retval(-EFAULT) int test_strncasecmp_pagefault1(void *ctx) { return bpf_strncasecmp(invalid_kern_ptr, "HELLO", 5); }
+SEC("syscall") __retval(-EFAULT) int test_strncasecmp_pagefault2(void *ctx) { return bpf_strncasecmp("HELLO", invalid_kern_ptr, 5); }
SEC("syscall") __retval(-EFAULT) int test_strchr_pagefault(void *ctx) { return bpf_strchr(invalid_kern_ptr, 'a'); }
SEC("syscall") __retval(-EFAULT) int test_strchrnul_pagefault(void *ctx) { return bpf_strchrnul(invalid_kern_ptr, 'a'); }
SEC("syscall") __retval(-EFAULT) int test_strnchr_pagefault(void *ctx) { return bpf_strnchr(invalid_kern_ptr, 1, 'a'); }
diff --git a/tools/testing/selftests/bpf/progs/string_kfuncs_failure2.c b/tools/testing/selftests/bpf/progs/string_kfuncs_failure2.c
index 05e1da1f250f..412c53b87b18 100644
--- a/tools/testing/selftests/bpf/progs/string_kfuncs_failure2.c
+++ b/tools/testing/selftests/bpf/progs/string_kfuncs_failure2.c
@@ -8,6 +8,7 @@ char long_str[XATTR_SIZE_MAX + 1];
SEC("syscall") int test_strcmp_too_long(void *ctx) { return bpf_strcmp(long_str, long_str); }
SEC("syscall") int test_strcasecmp_too_long(void *ctx) { return bpf_strcasecmp(long_str, long_str); }
+SEC("syscall") int test_strncasecmp_too_long(void *ctx) { return bpf_strncasecmp(long_str, long_str, sizeof(long_str)); }
SEC("syscall") int test_strchr_too_long(void *ctx) { return bpf_strchr(long_str, 'b'); }
SEC("syscall") int test_strchrnul_too_long(void *ctx) { return bpf_strchrnul(long_str, 'b'); }
SEC("syscall") int test_strnchr_too_long(void *ctx) { return bpf_strnchr(long_str, sizeof(long_str), 'b'); }
diff --git a/tools/testing/selftests/bpf/progs/string_kfuncs_success.c b/tools/testing/selftests/bpf/progs/string_kfuncs_success.c
index a8513964516b..3ccfae4d27d3 100644
--- a/tools/testing/selftests/bpf/progs/string_kfuncs_success.c
+++ b/tools/testing/selftests/bpf/progs/string_kfuncs_success.c
@@ -17,6 +17,13 @@ __test(0) int test_strcasecmp_eq2(void *ctx) { return bpf_strcasecmp(str, "HELLO
__test(0) int test_strcasecmp_eq3(void *ctx) { return bpf_strcasecmp(str, "HELLO world"); }
__test(1) int test_strcasecmp_neq1(void *ctx) { return bpf_strcasecmp(str, "hello"); }
__test(1) int test_strcasecmp_neq2(void *ctx) { return bpf_strcasecmp(str, "HELLO"); }
+__test(0) int test_strncasecmp_eq1(void *ctx) { return bpf_strncasecmp(str, "hello world", 11); }
+__test(0) int test_strncasecmp_eq2(void *ctx) { return bpf_strncasecmp(str, "HELLO WORLD", 11); }
+__test(0) int test_strncasecmp_eq3(void *ctx) { return bpf_strncasecmp(str, "HELLO world", 11); }
+__test(0) int test_strncasecmp_eq4(void *ctx) { return bpf_strncasecmp(str, "hello", 5); }
+__test(0) int test_strncasecmp_eq6(void *ctx) { return bpf_strncasecmp(str, "hello world!", 11); }
+__test(-1) int test_strncasecmp_neq1(void *ctx) { return bpf_strncasecmp(str, "hello!", 6); }
+__test(1) int test_strncasecmp_neq2(void *ctx) { return bpf_strncasecmp(str, "abc", 3); }
__test(1) int test_strchr_found(void *ctx) { return bpf_strchr(str, 'e'); }
__test(11) int test_strchr_null(void *ctx) { return bpf_strchr(str, '\0'); }
__test(-ENOENT) int test_strchr_notfound(void *ctx) { return bpf_strchr(str, 'x'); }
--
2.52.0
^ permalink raw reply related [flat|nested] 9+ messages in thread
* Re: [PATCH bpf-next v3 1/2] bpf: add bpf_strncasecmp kfunc
2026-01-20 7:03 ` [PATCH bpf-next v3 1/2] bpf: add bpf_strncasecmp kfunc Yuzuki Ishiyama
@ 2026-01-20 7:21 ` bot+bpf-ci
2026-01-20 9:29 ` Viktor Malik
0 siblings, 1 reply; 9+ messages in thread
From: bot+bpf-ci @ 2026-01-20 7:21 UTC (permalink / raw)
To: ishiyama, bpf
Cc: mykyta.yatsenko5, vmalik, andrii, ast, daniel, martin.lau,
ishiyama, ast, andrii, daniel, martin.lau, eddyz87, yonghong.song,
clm, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 2065 bytes --]
> diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
> index 9eaa4185e0a7..753753f039ff 100644
> --- a/kernel/bpf/helpers.c
> +++ b/kernel/bpf/helpers.c
[ ... ]
> -static int __bpf_strcasecmp(const char *s1, const char *s2, bool ignore_case)
> +static int __bpf_strncasecmp(const char *s1, const char *s2, bool ignore_case, size_t len)
> {
> char c1, c2;
> - int i;
> + int i, max_sz;
>
> if (!copy_from_kernel_nofault_allowed(s1, 1) ||
> !copy_from_kernel_nofault_allowed(s2, 1)) {
> return -ERANGE;
> }
>
> + max_sz = min_t(int, len, XATTR_SIZE_MAX);
^^^
Can this truncation cause incorrect results when len > INT_MAX? The len
parameter is size_t, but min_t(int, ...) casts it to signed int before the
comparison. When len is larger than INT_MAX, the cast produces a negative
value, and min_t returns that negative value instead of XATTR_SIZE_MAX.
For example, if a BPF program calls bpf_strncasecmp(s1, s2, SIZE_MAX):
- min_t(int, SIZE_MAX, 65536) evaluates to min_t(int, -1, 65536) = -1
- max_sz becomes -1
- The loop condition (i < max_sz) is (0 < -1), which is false
- The loop never executes, and the function returns 0 without comparing
This would make bpf_strncasecmp("foo", "bar", SIZE_MAX) return 0 (equal)
when the strings are clearly different.
Would using min_t(size_t, len, XATTR_SIZE_MAX) avoid this issue?
> +
> guard(pagefault)();
> - for (i = 0; i < XATTR_SIZE_MAX; i++) {
> + for (i = 0; i < max_sz; i++) {
[ ... ]
> +__bpf_kfunc int bpf_strncasecmp(const char *s1__ign, const char *s2__ign, size_t len)
> +{
> + return __bpf_strncasecmp(s1__ign, s2__ign, true, len);
> }
The BPF verifier does not validate ranges for scalar kfunc parameters, so
BPF programs can pass arbitrary size_t values to len, triggering the
truncation issue described above.
---
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/21162711653
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH bpf-next v3 1/2] bpf: add bpf_strncasecmp kfunc
2026-01-20 7:21 ` bot+bpf-ci
@ 2026-01-20 9:29 ` Viktor Malik
2026-01-20 10:59 ` Yuzuki Ishiyama
2026-01-20 11:02 ` Mykyta Yatsenko
0 siblings, 2 replies; 9+ messages in thread
From: Viktor Malik @ 2026-01-20 9:29 UTC (permalink / raw)
To: bot+bpf-ci, ishiyama, bpf
Cc: mykyta.yatsenko5, andrii, ast, daniel, martin.lau, martin.lau,
eddyz87, yonghong.song, clm, ihor.solodrai
On 1/20/26 08:21, bot+bpf-ci@kernel.org wrote:
>> diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
>> index 9eaa4185e0a7..753753f039ff 100644
>> --- a/kernel/bpf/helpers.c
>> +++ b/kernel/bpf/helpers.c
>
> [ ... ]
>
>> -static int __bpf_strcasecmp(const char *s1, const char *s2, bool ignore_case)
>> +static int __bpf_strncasecmp(const char *s1, const char *s2, bool ignore_case, size_t len)
>> {
>> char c1, c2;
>> - int i;
>> + int i, max_sz;
>>
>> if (!copy_from_kernel_nofault_allowed(s1, 1) ||
>> !copy_from_kernel_nofault_allowed(s2, 1)) {
>> return -ERANGE;
>> }
>>
>> + max_sz = min_t(int, len, XATTR_SIZE_MAX);
> ^^^
>
> Can this truncation cause incorrect results when len > INT_MAX? The len
> parameter is size_t, but min_t(int, ...) casts it to signed int before the
> comparison. When len is larger than INT_MAX, the cast produces a negative
> value, and min_t returns that negative value instead of XATTR_SIZE_MAX.
>
> For example, if a BPF program calls bpf_strncasecmp(s1, s2, SIZE_MAX):
> - min_t(int, SIZE_MAX, 65536) evaluates to min_t(int, -1, 65536) = -1
> - max_sz becomes -1
> - The loop condition (i < max_sz) is (0 < -1), which is false
> - The loop never executes, and the function returns 0 without comparing
>
> This would make bpf_strncasecmp("foo", "bar", SIZE_MAX) return 0 (equal)
> when the strings are clearly different.
This is a good point and we should IMO address it.
>
> Would using min_t(size_t, len, XATTR_SIZE_MAX) avoid this issue?
Other string kfuncs use `i < len && i < XATTR_SIZE_MAX` in the loop
condition, I'd stick to that for consistency.
Viktor
>
>> +
>> guard(pagefault)();
>> - for (i = 0; i < XATTR_SIZE_MAX; i++) {
>> + for (i = 0; i < max_sz; i++) {
>
> [ ... ]
>
>> +__bpf_kfunc int bpf_strncasecmp(const char *s1__ign, const char *s2__ign, size_t len)
>> +{
>> + return __bpf_strncasecmp(s1__ign, s2__ign, true, len);
>> }
>
> The BPF verifier does not validate ranges for scalar kfunc parameters, so
> BPF programs can pass arbitrary size_t values to len, triggering the
> truncation issue described above.
>
>
> ---
> 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/21162711653
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH bpf-next v3 2/2] selftests/bpf: Test kfunc bpf_strncasecmp
2026-01-20 7:03 ` [PATCH bpf-next v3 2/2] selftests/bpf: Test kfunc bpf_strncasecmp Yuzuki Ishiyama
@ 2026-01-20 9:30 ` Viktor Malik
0 siblings, 0 replies; 9+ messages in thread
From: Viktor Malik @ 2026-01-20 9:30 UTC (permalink / raw)
To: Yuzuki Ishiyama, bpf; +Cc: mykyta.yatsenko5, andrii, ast, daniel, martin.lau
On 1/20/26 08:03, Yuzuki Ishiyama wrote:
> Add testsuites for kfunc bpf_strncasecmp.
>
> Signed-off-by: Yuzuki Ishiyama <ishiyama@hpc.is.uec.ac.jp>
Please include my ack from v2 (unless you change the patch significantly):
Acked-by: Viktor Malik <vmalik@redhat.com>
> ---
> tools/testing/selftests/bpf/prog_tests/string_kfuncs.c | 1 +
> tools/testing/selftests/bpf/progs/string_kfuncs_failure1.c | 6 ++++++
> tools/testing/selftests/bpf/progs/string_kfuncs_failure2.c | 1 +
> tools/testing/selftests/bpf/progs/string_kfuncs_success.c | 7 +++++++
> 4 files changed, 15 insertions(+)
>
> diff --git a/tools/testing/selftests/bpf/prog_tests/string_kfuncs.c b/tools/testing/selftests/bpf/prog_tests/string_kfuncs.c
> index 0f3bf594e7a5..300032a19445 100644
> --- a/tools/testing/selftests/bpf/prog_tests/string_kfuncs.c
> +++ b/tools/testing/selftests/bpf/prog_tests/string_kfuncs.c
> @@ -9,6 +9,7 @@
> static const char * const test_cases[] = {
> "strcmp",
> "strcasecmp",
> + "strncasecmp",
> "strchr",
> "strchrnul",
> "strnchr",
> diff --git a/tools/testing/selftests/bpf/progs/string_kfuncs_failure1.c b/tools/testing/selftests/bpf/progs/string_kfuncs_failure1.c
> index 826e6b6aff7e..bddc4e8579d2 100644
> --- a/tools/testing/selftests/bpf/progs/string_kfuncs_failure1.c
> +++ b/tools/testing/selftests/bpf/progs/string_kfuncs_failure1.c
> @@ -33,6 +33,8 @@ SEC("syscall") __retval(USER_PTR_ERR) int test_strcmp_null1(void *ctx) { return
> SEC("syscall") __retval(USER_PTR_ERR)int test_strcmp_null2(void *ctx) { return bpf_strcmp("hello", NULL); }
> SEC("syscall") __retval(USER_PTR_ERR) int test_strcasecmp_null1(void *ctx) { return bpf_strcasecmp(NULL, "HELLO"); }
> SEC("syscall") __retval(USER_PTR_ERR)int test_strcasecmp_null2(void *ctx) { return bpf_strcasecmp("HELLO", NULL); }
> +SEC("syscall") __retval(USER_PTR_ERR)int test_strncasecmp_null1(void *ctx) { return bpf_strncasecmp(NULL, "HELLO", 5); }
> +SEC("syscall") __retval(USER_PTR_ERR)int test_strncasecmp_null2(void *ctx) { return bpf_strncasecmp("HELLO", NULL, 5); }
> SEC("syscall") __retval(USER_PTR_ERR)int test_strchr_null(void *ctx) { return bpf_strchr(NULL, 'a'); }
> SEC("syscall") __retval(USER_PTR_ERR)int test_strchrnul_null(void *ctx) { return bpf_strchrnul(NULL, 'a'); }
> SEC("syscall") __retval(USER_PTR_ERR)int test_strnchr_null(void *ctx) { return bpf_strnchr(NULL, 1, 'a'); }
> @@ -57,6 +59,8 @@ SEC("syscall") __retval(USER_PTR_ERR) int test_strcmp_user_ptr1(void *ctx) { ret
> SEC("syscall") __retval(USER_PTR_ERR) int test_strcmp_user_ptr2(void *ctx) { return bpf_strcmp("hello", user_ptr); }
> SEC("syscall") __retval(USER_PTR_ERR) int test_strcasecmp_user_ptr1(void *ctx) { return bpf_strcasecmp(user_ptr, "HELLO"); }
> SEC("syscall") __retval(USER_PTR_ERR) int test_strcasecmp_user_ptr2(void *ctx) { return bpf_strcasecmp("HELLO", user_ptr); }
> +SEC("syscall") __retval(USER_PTR_ERR) int test_strncasecmp_user_ptr1(void *ctx) { return bpf_strncasecmp(user_ptr, "HELLO", 5); }
> +SEC("syscall") __retval(USER_PTR_ERR) int test_strncasecmp_user_ptr2(void *ctx) { return bpf_strncasecmp("HELLO", user_ptr, 5); }
> SEC("syscall") __retval(USER_PTR_ERR) int test_strchr_user_ptr(void *ctx) { return bpf_strchr(user_ptr, 'a'); }
> SEC("syscall") __retval(USER_PTR_ERR) int test_strchrnul_user_ptr(void *ctx) { return bpf_strchrnul(user_ptr, 'a'); }
> SEC("syscall") __retval(USER_PTR_ERR) int test_strnchr_user_ptr(void *ctx) { return bpf_strnchr(user_ptr, 1, 'a'); }
> @@ -83,6 +87,8 @@ SEC("syscall") __retval(-EFAULT) int test_strcmp_pagefault1(void *ctx) { return
> SEC("syscall") __retval(-EFAULT) int test_strcmp_pagefault2(void *ctx) { return bpf_strcmp("hello", invalid_kern_ptr); }
> SEC("syscall") __retval(-EFAULT) int test_strcasecmp_pagefault1(void *ctx) { return bpf_strcasecmp(invalid_kern_ptr, "HELLO"); }
> SEC("syscall") __retval(-EFAULT) int test_strcasecmp_pagefault2(void *ctx) { return bpf_strcasecmp("HELLO", invalid_kern_ptr); }
> +SEC("syscall") __retval(-EFAULT) int test_strncasecmp_pagefault1(void *ctx) { return bpf_strncasecmp(invalid_kern_ptr, "HELLO", 5); }
> +SEC("syscall") __retval(-EFAULT) int test_strncasecmp_pagefault2(void *ctx) { return bpf_strncasecmp("HELLO", invalid_kern_ptr, 5); }
> SEC("syscall") __retval(-EFAULT) int test_strchr_pagefault(void *ctx) { return bpf_strchr(invalid_kern_ptr, 'a'); }
> SEC("syscall") __retval(-EFAULT) int test_strchrnul_pagefault(void *ctx) { return bpf_strchrnul(invalid_kern_ptr, 'a'); }
> SEC("syscall") __retval(-EFAULT) int test_strnchr_pagefault(void *ctx) { return bpf_strnchr(invalid_kern_ptr, 1, 'a'); }
> diff --git a/tools/testing/selftests/bpf/progs/string_kfuncs_failure2.c b/tools/testing/selftests/bpf/progs/string_kfuncs_failure2.c
> index 05e1da1f250f..412c53b87b18 100644
> --- a/tools/testing/selftests/bpf/progs/string_kfuncs_failure2.c
> +++ b/tools/testing/selftests/bpf/progs/string_kfuncs_failure2.c
> @@ -8,6 +8,7 @@ char long_str[XATTR_SIZE_MAX + 1];
>
> SEC("syscall") int test_strcmp_too_long(void *ctx) { return bpf_strcmp(long_str, long_str); }
> SEC("syscall") int test_strcasecmp_too_long(void *ctx) { return bpf_strcasecmp(long_str, long_str); }
> +SEC("syscall") int test_strncasecmp_too_long(void *ctx) { return bpf_strncasecmp(long_str, long_str, sizeof(long_str)); }
> SEC("syscall") int test_strchr_too_long(void *ctx) { return bpf_strchr(long_str, 'b'); }
> SEC("syscall") int test_strchrnul_too_long(void *ctx) { return bpf_strchrnul(long_str, 'b'); }
> SEC("syscall") int test_strnchr_too_long(void *ctx) { return bpf_strnchr(long_str, sizeof(long_str), 'b'); }
> diff --git a/tools/testing/selftests/bpf/progs/string_kfuncs_success.c b/tools/testing/selftests/bpf/progs/string_kfuncs_success.c
> index a8513964516b..3ccfae4d27d3 100644
> --- a/tools/testing/selftests/bpf/progs/string_kfuncs_success.c
> +++ b/tools/testing/selftests/bpf/progs/string_kfuncs_success.c
> @@ -17,6 +17,13 @@ __test(0) int test_strcasecmp_eq2(void *ctx) { return bpf_strcasecmp(str, "HELLO
> __test(0) int test_strcasecmp_eq3(void *ctx) { return bpf_strcasecmp(str, "HELLO world"); }
> __test(1) int test_strcasecmp_neq1(void *ctx) { return bpf_strcasecmp(str, "hello"); }
> __test(1) int test_strcasecmp_neq2(void *ctx) { return bpf_strcasecmp(str, "HELLO"); }
> +__test(0) int test_strncasecmp_eq1(void *ctx) { return bpf_strncasecmp(str, "hello world", 11); }
> +__test(0) int test_strncasecmp_eq2(void *ctx) { return bpf_strncasecmp(str, "HELLO WORLD", 11); }
> +__test(0) int test_strncasecmp_eq3(void *ctx) { return bpf_strncasecmp(str, "HELLO world", 11); }
> +__test(0) int test_strncasecmp_eq4(void *ctx) { return bpf_strncasecmp(str, "hello", 5); }
> +__test(0) int test_strncasecmp_eq6(void *ctx) { return bpf_strncasecmp(str, "hello world!", 11); }
> +__test(-1) int test_strncasecmp_neq1(void *ctx) { return bpf_strncasecmp(str, "hello!", 6); }
> +__test(1) int test_strncasecmp_neq2(void *ctx) { return bpf_strncasecmp(str, "abc", 3); }
> __test(1) int test_strchr_found(void *ctx) { return bpf_strchr(str, 'e'); }
> __test(11) int test_strchr_null(void *ctx) { return bpf_strchr(str, '\0'); }
> __test(-ENOENT) int test_strchr_notfound(void *ctx) { return bpf_strchr(str, 'x'); }
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH bpf-next v3 1/2] bpf: add bpf_strncasecmp kfunc
2026-01-20 9:29 ` Viktor Malik
@ 2026-01-20 10:59 ` Yuzuki Ishiyama
2026-01-20 11:02 ` Mykyta Yatsenko
1 sibling, 0 replies; 9+ messages in thread
From: Yuzuki Ishiyama @ 2026-01-20 10:59 UTC (permalink / raw)
To: Viktor Malik
Cc: bot+bpf-ci, bpf, mykyta.yatsenko5, andrii, ast, daniel,
martin.lau, martin.lau, eddyz87, yonghong.song, clm,
ihor.solodrai
Understood. I'll send v4 soon!
Yuzuki
2026年1月20日(火) 18:29 Viktor Malik <vmalik@redhat.com>:
>
> On 1/20/26 08:21, bot+bpf-ci@kernel.org wrote:
> >> diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
> >> index 9eaa4185e0a7..753753f039ff 100644
> >> --- a/kernel/bpf/helpers.c
> >> +++ b/kernel/bpf/helpers.c
> >
> > [ ... ]
> >
> >> -static int __bpf_strcasecmp(const char *s1, const char *s2, bool ignore_case)
> >> +static int __bpf_strncasecmp(const char *s1, const char *s2, bool ignore_case, size_t len)
> >> {
> >> char c1, c2;
> >> - int i;
> >> + int i, max_sz;
> >>
> >> if (!copy_from_kernel_nofault_allowed(s1, 1) ||
> >> !copy_from_kernel_nofault_allowed(s2, 1)) {
> >> return -ERANGE;
> >> }
> >>
> >> + max_sz = min_t(int, len, XATTR_SIZE_MAX);
> > ^^^
> >
> > Can this truncation cause incorrect results when len > INT_MAX? The len
> > parameter is size_t, but min_t(int, ...) casts it to signed int before the
> > comparison. When len is larger than INT_MAX, the cast produces a negative
> > value, and min_t returns that negative value instead of XATTR_SIZE_MAX.
> >
> > For example, if a BPF program calls bpf_strncasecmp(s1, s2, SIZE_MAX):
> > - min_t(int, SIZE_MAX, 65536) evaluates to min_t(int, -1, 65536) = -1
> > - max_sz becomes -1
> > - The loop condition (i < max_sz) is (0 < -1), which is false
> > - The loop never executes, and the function returns 0 without comparing
> >
> > This would make bpf_strncasecmp("foo", "bar", SIZE_MAX) return 0 (equal)
> > when the strings are clearly different.
>
> This is a good point and we should IMO address it.
>
> >
> > Would using min_t(size_t, len, XATTR_SIZE_MAX) avoid this issue?
>
> Other string kfuncs use `i < len && i < XATTR_SIZE_MAX` in the loop
> condition, I'd stick to that for consistency.
>
> Viktor
>
> >
> >> +
> >> guard(pagefault)();
> >> - for (i = 0; i < XATTR_SIZE_MAX; i++) {
> >> + for (i = 0; i < max_sz; i++) {
> >
> > [ ... ]
> >
> >> +__bpf_kfunc int bpf_strncasecmp(const char *s1__ign, const char *s2__ign, size_t len)
> >> +{
> >> + return __bpf_strncasecmp(s1__ign, s2__ign, true, len);
> >> }
> >
> > The BPF verifier does not validate ranges for scalar kfunc parameters, so
> > BPF programs can pass arbitrary size_t values to len, triggering the
> > truncation issue described above.
> >
> >
> > ---
> > 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/21162711653
>
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH bpf-next v3 1/2] bpf: add bpf_strncasecmp kfunc
2026-01-20 9:29 ` Viktor Malik
2026-01-20 10:59 ` Yuzuki Ishiyama
@ 2026-01-20 11:02 ` Mykyta Yatsenko
2026-01-20 11:15 ` Viktor Malik
1 sibling, 1 reply; 9+ messages in thread
From: Mykyta Yatsenko @ 2026-01-20 11:02 UTC (permalink / raw)
To: Viktor Malik, bot+bpf-ci, ishiyama, bpf
Cc: andrii, ast, daniel, martin.lau, martin.lau, eddyz87,
yonghong.song, clm, ihor.solodrai
On 1/20/26 09:29, Viktor Malik wrote:
> On 1/20/26 08:21, bot+bpf-ci@kernel.org wrote:
>>> diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
>>> index 9eaa4185e0a7..753753f039ff 100644
>>> --- a/kernel/bpf/helpers.c
>>> +++ b/kernel/bpf/helpers.c
>> [ ... ]
>>
>>> -static int __bpf_strcasecmp(const char *s1, const char *s2, bool ignore_case)
>>> +static int __bpf_strncasecmp(const char *s1, const char *s2, bool ignore_case, size_t len)
>>> {
>>> char c1, c2;
>>> - int i;
>>> + int i, max_sz;
>>>
>>> if (!copy_from_kernel_nofault_allowed(s1, 1) ||
>>> !copy_from_kernel_nofault_allowed(s2, 1)) {
>>> return -ERANGE;
>>> }
>>>
>>> + max_sz = min_t(int, len, XATTR_SIZE_MAX);
>> ^^^
>>
>> Can this truncation cause incorrect results when len > INT_MAX? The len
>> parameter is size_t, but min_t(int, ...) casts it to signed int before the
>> comparison. When len is larger than INT_MAX, the cast produces a negative
>> value, and min_t returns that negative value instead of XATTR_SIZE_MAX.
>>
>> For example, if a BPF program calls bpf_strncasecmp(s1, s2, SIZE_MAX):
>> - min_t(int, SIZE_MAX, 65536) evaluates to min_t(int, -1, 65536) = -1
>> - max_sz becomes -1
>> - The loop condition (i < max_sz) is (0 < -1), which is false
>> - The loop never executes, and the function returns 0 without comparing
>>
>> This would make bpf_strncasecmp("foo", "bar", SIZE_MAX) return 0 (equal)
>> when the strings are clearly different.
> This is a good point and we should IMO address it.
>
>> Would using min_t(size_t, len, XATTR_SIZE_MAX) avoid this issue?
> Other string kfuncs use `i < len && i < XATTR_SIZE_MAX` in the loop
> condition, I'd stick to that for consistency.
>
> Viktor
Why not just use proper type (size_t for max_sz), obviously truncation is a
problem there, so use the wider type?
>
>>> +
>>> guard(pagefault)();
>>> - for (i = 0; i < XATTR_SIZE_MAX; i++) {
>>> + for (i = 0; i < max_sz; i++) {
>> [ ... ]
>>
>>> +__bpf_kfunc int bpf_strncasecmp(const char *s1__ign, const char *s2__ign, size_t len)
>>> +{
>>> + return __bpf_strncasecmp(s1__ign, s2__ign, true, len);
>>> }
>> The BPF verifier does not validate ranges for scalar kfunc parameters, so
>> BPF programs can pass arbitrary size_t values to len, triggering the
>> truncation issue described above.
>>
>>
>> ---
>> 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/21162711653
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH bpf-next v3 1/2] bpf: add bpf_strncasecmp kfunc
2026-01-20 11:02 ` Mykyta Yatsenko
@ 2026-01-20 11:15 ` Viktor Malik
0 siblings, 0 replies; 9+ messages in thread
From: Viktor Malik @ 2026-01-20 11:15 UTC (permalink / raw)
To: Mykyta Yatsenko, bot+bpf-ci, ishiyama, bpf
Cc: andrii, ast, daniel, martin.lau, martin.lau, eddyz87,
yonghong.song, clm, ihor.solodrai
On 1/20/26 12:02, Mykyta Yatsenko wrote:
> On 1/20/26 09:29, Viktor Malik wrote:
>> On 1/20/26 08:21, bot+bpf-ci@kernel.org wrote:
>>>> diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
>>>> index 9eaa4185e0a7..753753f039ff 100644
>>>> --- a/kernel/bpf/helpers.c
>>>> +++ b/kernel/bpf/helpers.c
>>> [ ... ]
>>>
>>>> -static int __bpf_strcasecmp(const char *s1, const char *s2, bool ignore_case)
>>>> +static int __bpf_strncasecmp(const char *s1, const char *s2, bool ignore_case, size_t len)
>>>> {
>>>> char c1, c2;
>>>> - int i;
>>>> + int i, max_sz;
>>>>
>>>> if (!copy_from_kernel_nofault_allowed(s1, 1) ||
>>>> !copy_from_kernel_nofault_allowed(s2, 1)) {
>>>> return -ERANGE;
>>>> }
>>>>
>>>> + max_sz = min_t(int, len, XATTR_SIZE_MAX);
>>> ^^^
>>>
>>> Can this truncation cause incorrect results when len > INT_MAX? The len
>>> parameter is size_t, but min_t(int, ...) casts it to signed int before the
>>> comparison. When len is larger than INT_MAX, the cast produces a negative
>>> value, and min_t returns that negative value instead of XATTR_SIZE_MAX.
>>>
>>> For example, if a BPF program calls bpf_strncasecmp(s1, s2, SIZE_MAX):
>>> - min_t(int, SIZE_MAX, 65536) evaluates to min_t(int, -1, 65536) = -1
>>> - max_sz becomes -1
>>> - The loop condition (i < max_sz) is (0 < -1), which is false
>>> - The loop never executes, and the function returns 0 without comparing
>>>
>>> This would make bpf_strncasecmp("foo", "bar", SIZE_MAX) return 0 (equal)
>>> when the strings are clearly different.
>> This is a good point and we should IMO address it.
>>
>>> Would using min_t(size_t, len, XATTR_SIZE_MAX) avoid this issue?
>> Other string kfuncs use `i < len && i < XATTR_SIZE_MAX` in the loop
>> condition, I'd stick to that for consistency.
>>
>> Viktor
> Why not just use proper type (size_t for max_sz), obviously truncation is a
> problem there, so use the wider type?
No problem with that either. I slightly prefer consistency but using
min_t with size_t is a good solution, too (and slightly more efficient one).
>>
>>>> +
>>>> guard(pagefault)();
>>>> - for (i = 0; i < XATTR_SIZE_MAX; i++) {
>>>> + for (i = 0; i < max_sz; i++) {
>>> [ ... ]
>>>
>>>> +__bpf_kfunc int bpf_strncasecmp(const char *s1__ign, const char *s2__ign, size_t len)
>>>> +{
>>>> + return __bpf_strncasecmp(s1__ign, s2__ign, true, len);
>>>> }
>>> The BPF verifier does not validate ranges for scalar kfunc parameters, so
>>> BPF programs can pass arbitrary size_t values to len, triggering the
>>> truncation issue described above.
>>>
>>>
>>> ---
>>> 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/21162711653
>
^ permalink raw reply [flat|nested] 9+ messages in thread
end of thread, other threads:[~2026-01-20 11:15 UTC | newest]
Thread overview: 9+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-01-20 7:03 [PATCH bpf-next v3 0/2] bpf: Add kfunc bpf_strncasecmp() Yuzuki Ishiyama
2026-01-20 7:03 ` [PATCH bpf-next v3 1/2] bpf: add bpf_strncasecmp kfunc Yuzuki Ishiyama
2026-01-20 7:21 ` bot+bpf-ci
2026-01-20 9:29 ` Viktor Malik
2026-01-20 10:59 ` Yuzuki Ishiyama
2026-01-20 11:02 ` Mykyta Yatsenko
2026-01-20 11:15 ` Viktor Malik
2026-01-20 7:03 ` [PATCH bpf-next v3 2/2] selftests/bpf: Test kfunc bpf_strncasecmp Yuzuki Ishiyama
2026-01-20 9:30 ` Viktor Malik
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox