BPF List
 help / color / mirror / Atom feed
* [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