* [PATCH bpf-next v2 1/2] bpf: add bpf_strcat,bpf_strncat kfunc
[not found] <cover.1784078494.git.rtoax@foxmail.com>
@ 2026-07-15 1:27 ` Rong Tao
2026-07-15 1:40 ` sashiko-bot
2026-07-15 8:56 ` Viktor Malik
2026-07-15 1:27 ` [PATCH bpf-next v2 2/2] selftests/bpf: Test bpf_strcat,bpf_strncat kfuncs Rong Tao
1 sibling, 2 replies; 5+ messages in thread
From: Rong Tao @ 2026-07-15 1:27 UTC (permalink / raw)
To: andrii, vmalik, ast
Cc: rtoax, Rong Tao, Daniel Borkmann, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Martin KaFai Lau, Song Liu,
Yonghong Song, Jiri Olsa, Emil Tsalapatis, Shuah Khan,
Yuzuki Ishiyama,
open list:BPF [GENERAL] (Safe Dynamic Programs and Tools),
open list, open list:KERNEL SELFTEST FRAMEWORK
From: Rong Tao <rongtao@cestc.cn>
Add string concatenation kfuncs, prototype:
int bpf_strcat(char *dst__ign, u32 dst__sz, const char *src__ign);
int bpf_strncat(char *dst__ign, u32 dst__sz, const char *src__ign, u32 len);
This differs from the glibc library functions strcat and strncat, which,
for safety reasons, require the size of the target string's memory space
as a parameter.
Signed-off-by: Rong Tao <rongtao@cestc.cn>
---
kernel/bpf/helpers.c | 92 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 92 insertions(+)
diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index c18f1e16edee..401f94efd687 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -4195,6 +4195,96 @@ __bpf_kfunc int bpf_strncasestr(const char *s1__ign, const char *s2__ign,
return __bpf_strnstr(s1__ign, s2__ign, len, true);
}
+static int __bpf_strncat(char *dst, u32 dsz, const char *src, u32 sz)
+{
+ int dlen, slen, space, copied;
+ char cs = '?';
+
+ if (!copy_from_kernel_nofault_allowed(dst, 1) ||
+ !copy_from_kernel_nofault_allowed(src, 1)) {
+ return -ERANGE;
+ }
+
+ dlen = bpf_strnlen(dst, dsz);
+ if (dlen < 0)
+ return dlen;
+ slen = bpf_strnlen(src, sz);
+ if (slen < 0)
+ return slen;
+
+ if (dlen >= dsz || sz == 0 || dsz == 0)
+ return -EINVAL;
+
+ space = dsz - dlen;
+ if (space <= 1 || space < min(slen, sz) + 1)
+ return -E2BIG;
+
+ guard(pagefault)();
+ for (copied = 0; copied < space - 1 && copied < slen; copied++) {
+ __get_kernel_nofault(&cs, src, char, err_out);
+ if (cs == '\0')
+ break;
+
+ __put_kernel_nofault(dst + dlen + copied, &cs, char, err_out);
+
+ src++;
+ }
+ cs = '\0';
+ __put_kernel_nofault(dst + dlen + copied, &cs, char, err_out);
+
+ __get_kernel_nofault(&cs, src, char, err_out);
+ if (cs != '\0' && sz > copied)
+ return -E2BIG;
+
+ return dlen + copied;
+err_out:
+ return -EFAULT;
+}
+
+/**
+ * bpf_strcat - Append non-null bytes from a source string, and null-terminate
+ * the result
+ * @dst: Destination string.
+ * @dst__sz: Maximum bytes of @dst__ign, includes the trailing NUL.
+ * @src__ign: Source string.
+ *
+ * Return:
+ * * >=0 - Length of the concatenated string.
+ *
+ * * %-EINVAL - String @dst__ign is invalid.
+ * * %-EFAULT - Cannot read or write one of the strings.
+ * * %-E2BIG - String @src__ign is too large or the remaining space in
+ * @dst__ign is too small.
+ * * %-ERANGE - One of the strings is outside of kernel address space
+ */
+__bpf_kfunc int bpf_strcat(char *dst, u32 dst__sz, const char *src__ign)
+{
+ return __bpf_strncat(dst, dst__sz, src__ign, XATTR_SIZE_MAX);
+}
+
+/**
+ * bpf_strncat - Append non-null bytes from a source string, and null-terminate
+ * the result
+ * @dst: Destination string.
+ * @dst__sz: Maximum bytes of @dst__ign, includes the trailing NUL.
+ * @src__ign: Source string.
+ * @len: the maximum number of characters to concatenate
+ *
+ * Return:
+ * * >=0 - Length of the concatenated string.
+ *
+ * * %-EINVAL - String @dst__ign is invalid.
+ * * %-EFAULT - Cannot read or write one of the strings.
+ * * %-E2BIG - String @src__ign is too large or the remaining space in
+ * @dst__ign is too small.
+ * * %-ERANGE - One of the strings is outside of kernel address space
+ */
+__bpf_kfunc int bpf_strncat(char *dst, u32 dst__sz, const char *src__ign,
+ u32 len)
+{
+ return __bpf_strncat(dst, dst__sz, src__ign, len);
+}
+
#ifdef CONFIG_KEYS
/**
* bpf_lookup_user_key - lookup a key by its serial
@@ -4958,6 +5048,8 @@ BTF_ID_FLAGS(func, bpf_strstr);
BTF_ID_FLAGS(func, bpf_strcasestr);
BTF_ID_FLAGS(func, bpf_strnstr);
BTF_ID_FLAGS(func, bpf_strncasestr);
+BTF_ID_FLAGS(func, bpf_strcat);
+BTF_ID_FLAGS(func, bpf_strncat);
#if defined(CONFIG_BPF_LSM) && defined(CONFIG_CGROUPS)
BTF_ID_FLAGS(func, bpf_cgroup_read_xattr, KF_RCU)
#endif
--
2.55.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH bpf-next v2 2/2] selftests/bpf: Test bpf_strcat,bpf_strncat kfuncs
[not found] <cover.1784078494.git.rtoax@foxmail.com>
2026-07-15 1:27 ` [PATCH bpf-next v2 1/2] bpf: add bpf_strcat,bpf_strncat kfunc Rong Tao
@ 2026-07-15 1:27 ` Rong Tao
2026-07-15 1:42 ` sashiko-bot
1 sibling, 1 reply; 5+ messages in thread
From: Rong Tao @ 2026-07-15 1:27 UTC (permalink / raw)
To: andrii, vmalik, ast
Cc: rtoax, Rong Tao, Daniel Borkmann, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Martin KaFai Lau, Song Liu,
Yonghong Song, Jiri Olsa, Emil Tsalapatis, Shuah Khan,
Yuzuki Ishiyama,
open list:BPF [GENERAL] (Safe Dynamic Programs and Tools),
open list:KERNEL SELFTEST FRAMEWORK, open list
From: Rong Tao <rongtao@cestc.cn>
Add tests for new kfuncs bpf_strcat() and bpf_strncat().
Signed-off-by: Rong Tao <rongtao@cestc.cn>
---
.../selftests/bpf/prog_tests/string_kfuncs.c | 2 ++
.../selftests/bpf/progs/string_kfuncs_failure1.c | 13 +++++++++++++
.../selftests/bpf/progs/string_kfuncs_failure2.c | 2 ++
.../selftests/bpf/progs/string_kfuncs_success.c | 3 +++
4 files changed, 20 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/string_kfuncs.c b/tools/testing/selftests/bpf/prog_tests/string_kfuncs.c
index 300032a19445..460567ef622a 100644
--- a/tools/testing/selftests/bpf/prog_tests/string_kfuncs.c
+++ b/tools/testing/selftests/bpf/prog_tests/string_kfuncs.c
@@ -24,6 +24,8 @@ static const char * const test_cases[] = {
"strcasestr",
"strnstr",
"strncasestr",
+ "strcat",
+ "strncat",
};
void run_too_long_tests(void)
diff --git a/tools/testing/selftests/bpf/progs/string_kfuncs_failure1.c b/tools/testing/selftests/bpf/progs/string_kfuncs_failure1.c
index bddc4e8579d2..29bc7eabbb6a 100644
--- a/tools/testing/selftests/bpf/progs/string_kfuncs_failure1.c
+++ b/tools/testing/selftests/bpf/progs/string_kfuncs_failure1.c
@@ -8,6 +8,7 @@
char *user_ptr = (char *)1;
char *invalid_kern_ptr = (char *)-1;
+char kern_buf[32] = { "hello" };
/*
* When passing userspace pointers, the error code differs based on arch:
@@ -53,6 +54,10 @@ 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_strcat_null1(void *ctx) { return bpf_strcat(NULL, 6, "hello"); }
+SEC("syscall") __retval(USER_PTR_ERR)int test_strcat_null2(void *ctx) { return bpf_strcat(kern_buf, sizeof(kern_buf), NULL); }
+SEC("syscall") __retval(USER_PTR_ERR)int test_strncat_null1(void *ctx) { return bpf_strncat(NULL, 6, "hello", 2); }
+SEC("syscall") __retval(USER_PTR_ERR)int test_strncat_null2(void *ctx) { return bpf_strncat(kern_buf, sizeof(kern_buf), NULL, 2); }
/* 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,10 @@ 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_strcat_user_ptr1(void *ctx) { return bpf_strcat(user_ptr, 1, "hello"); }
+SEC("syscall") __retval(USER_PTR_ERR) int test_strcat_user_ptr2(void *ctx) { return bpf_strcat(kern_buf, sizeof(kern_buf), user_ptr); }
+SEC("syscall") __retval(USER_PTR_ERR) int test_strncat_user_ptr1(void *ctx) { return bpf_strncat(user_ptr, 1, "hello", 2); }
+SEC("syscall") __retval(USER_PTR_ERR) int test_strncat_user_ptr2(void *ctx) { return bpf_strncat(kern_buf, sizeof(kern_buf), user_ptr, 1); }
#endif /* __TARGET_ARCH_s390 */
@@ -107,5 +116,9 @@ 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_strcat_pagefault1(void *ctx) { return bpf_strcat(invalid_kern_ptr, 1, "hello"); }
+SEC("syscall") __retval(-EFAULT) int test_strcat_pagefault2(void *ctx) { return bpf_strcat(kern_buf, sizeof(kern_buf), invalid_kern_ptr); }
+SEC("syscall") __retval(-EFAULT) int test_strncat_pagefault1(void *ctx) { return bpf_strncat(invalid_kern_ptr, 1, "hello", 2); }
+SEC("syscall") __retval(-EFAULT) int test_strncat_pagefault2(void *ctx) { return bpf_strncat(kern_buf, sizeof(kern_buf), invalid_kern_ptr, 2); }
char _license[] SEC("license") = "GPL";
diff --git a/tools/testing/selftests/bpf/progs/string_kfuncs_failure2.c b/tools/testing/selftests/bpf/progs/string_kfuncs_failure2.c
index 412c53b87b18..38f0a5f326b5 100644
--- a/tools/testing/selftests/bpf/progs/string_kfuncs_failure2.c
+++ b/tools/testing/selftests/bpf/progs/string_kfuncs_failure2.c
@@ -23,5 +23,7 @@ SEC("syscall") int test_strstr_too_long(void *ctx) { return bpf_strstr(long_str,
SEC("syscall") int test_strcasestr_too_long(void *ctx) { return bpf_strcasestr(long_str, "hello"); }
SEC("syscall") int test_strnstr_too_long(void *ctx) { return bpf_strnstr(long_str, "hello", sizeof(long_str)); }
SEC("syscall") int test_strncasestr_too_long(void *ctx) { return bpf_strncasestr(long_str, "hello", sizeof(long_str)); }
+SEC("syscall") int test_strcat_too_long(void *ctx) { return bpf_strcat(long_str, sizeof(long_str), "hello"); }
+SEC("syscall") int test_strncat_too_long(void *ctx) { return bpf_strncat(long_str, sizeof(long_str), "hello", 3); }
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..93996b0e9595 100644
--- a/tools/testing/selftests/bpf/progs/string_kfuncs_success.c
+++ b/tools/testing/selftests/bpf/progs/string_kfuncs_success.c
@@ -6,6 +6,7 @@
#include "errno.h"
char str[] = "hello world";
+char buf[32] = "hello";
#define __test(retval) SEC("syscall") __success __retval(retval)
@@ -59,5 +60,7 @@ __test(-ENOENT) int test_strncasestr_notfound1(void *ctx) { return bpf_strncases
__test(-ENOENT) int test_strncasestr_notfound2(void *ctx) { return bpf_strncasestr(str, "hello", 4); }
__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); }
+__test(10) int test_strcat_success(void *ctx) { return bpf_strcat(buf, sizeof(buf), "world"); }
+__test(13) int test_strncat_success(void *ctx) { return bpf_strncat(buf, sizeof(buf), "world", 3); }
char _license[] SEC("license") = "GPL";
--
2.55.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH bpf-next v2 1/2] bpf: add bpf_strcat,bpf_strncat kfunc
2026-07-15 1:27 ` [PATCH bpf-next v2 1/2] bpf: add bpf_strcat,bpf_strncat kfunc Rong Tao
@ 2026-07-15 1:40 ` sashiko-bot
2026-07-15 8:56 ` Viktor Malik
1 sibling, 0 replies; 5+ messages in thread
From: sashiko-bot @ 2026-07-15 1:40 UTC (permalink / raw)
To: Rong Tao; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
- [High] bpf_strncat unconditionally reads one byte beyond the caller-specified source limit (len) after the copy loop, causing a spurious -EFAULT if the source buffer ends at a page boundary.
- [Medium] The destination string is left completely non-null-terminated if a memory fault occurs during the copy loop.
- [Medium] Integer overflow in the `space` calculation causes spurious `-E2BIG` failures for destination buffers larger than 2GB.
--
--- Patch [1]: [PATCH bpf-next v2 1/2] bpf: add bpf_strcat,bpf_strncat kfunc ---
commit 638de7ac547b3d0857f680c3e68dbfcae6e2543c
Author: Rong Tao <rongtao@cestc.cn>
bpf: add bpf_strcat,bpf_strncat kfunc
This patch adds string concatenation kfuncs bpf_strcat and bpf_strncat to
the BPF subsystem. These functions allow appending non-null bytes from a
source string to a destination string with size limits.
> diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
> index c18f1e16edee4..401f94efd687c 100644
> --- a/kernel/bpf/helpers.c
> +++ b/kernel/bpf/helpers.c
[ ... ]
> @@ -4195,6 +4195,96 @@ __bpf_kfunc int bpf_strncasestr(const char *s1__ign, const char *s2__ign,
> return __bpf_strnstr(s1__ign, s2__ign, len, true);
> }
>
> +static int __bpf_strncat(char *dst, u32 dsz, const char *src, u32 sz)
> +{
> + int dlen, slen, space, copied;
> + char cs = '?';
> +
> + if (!copy_from_kernel_nofault_allowed(dst, 1) ||
> + !copy_from_kernel_nofault_allowed(src, 1)) {
> + return -ERANGE;
> + }
> +
> + dlen = bpf_strnlen(dst, dsz);
> + if (dlen < 0)
> + return dlen;
> + slen = bpf_strnlen(src, sz);
> + if (slen < 0)
> + return slen;
> +
> + if (dlen >= dsz || sz == 0 || dsz == 0)
> + return -EINVAL;
> +
> + space = dsz - dlen;
> + if (space <= 1 || space < min(slen, sz) + 1)
> + return -E2BIG;
[Severity: Medium]
Will this calculation overflow if dsz is greater than or equal to 2GB?
Since space is a signed int and dsz is a u32, dsz - dlen can result
in a negative number for large destination buffers, such as those from a
BPF arena. A negative value satisfies the space <= 1 check, causing the
function to incorrectly abort and return -E2BIG.
> +
> + guard(pagefault)();
> + for (copied = 0; copied < space - 1 && copied < slen; copied++) {
> + __get_kernel_nofault(&cs, src, char, err_out);
> + if (cs == '\0')
> + break;
> +
> + __put_kernel_nofault(dst + dlen + copied, &cs, char, err_out);
[Severity: Medium]
Does this leave the destination string non-null-terminated if a memory fault
occurs during the copy?
If the loop copies at least one character (overwriting the destination's
original null terminator) and then a subsequent read or write triggers a
page fault, the code jumps directly to err_out without restoring a null
terminator.
While it returns an error, BPF programs that do not check the return value
might operate on corrupted state.
> +
> + src++;
> + }
> + cs = '\0';
> + __put_kernel_nofault(dst + dlen + copied, &cs, char, err_out);
> +
> + __get_kernel_nofault(&cs, src, char, err_out);
> + if (cs != '\0' && sz > copied)
> + return -E2BIG;
[Severity: High]
Can this unconditionally read one byte beyond the caller-specified limit sz?
When the copy loop finishes, copied may equal sz. The pointer src will
have been incremented sz times. If the original src buffer is exactly
sz bytes and ends at a page boundary, this read will trigger a page fault,
causing a spurious -EFAULT return.
Additionally, if copied == sz, the subsequent sz > copied check evaluates
to false anyway, making the read unnecessary in that case.
> +
> + return dlen + copied;
> +err_out:
> + return -EFAULT;
> +}
[ ... ]
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1784078494.git.rtoax@foxmail.com?part=1
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH bpf-next v2 2/2] selftests/bpf: Test bpf_strcat,bpf_strncat kfuncs
2026-07-15 1:27 ` [PATCH bpf-next v2 2/2] selftests/bpf: Test bpf_strcat,bpf_strncat kfuncs Rong Tao
@ 2026-07-15 1:42 ` sashiko-bot
0 siblings, 0 replies; 5+ messages in thread
From: sashiko-bot @ 2026-07-15 1:42 UTC (permalink / raw)
To: Rong Tao; +Cc: bpf
Thank you for your contribution! Sashiko AI review found 3 potential issue(s) to consider:
New issues:
- [Medium] The selftest `test_strncat_success` unconditionally fails because it incorrectly expects a return value of 13, assuming the global buffer retains state across subtests.
- [Medium] Six failure tests for `strcat` and `strncat` will fail to load in the verifier because they pass invalid scalars (`NULL`, `1`, `-1`) to the strictly typed `dst` memory pointer argument.
Pre-existing issues:
- [High] The `__bpf_strncat` kfunc unconditionally performs an out-of-bounds read of the source string to check for truncation, erroneously returning `-EFAULT` on perfectly valid memory.
--
--- Patch [2]: [PATCH bpf-next v2 2/2] selftests/bpf: Test bpf_strcat,bpf_strncat kfuncs ---
commit a3847dce2f7db636af75a6ec747d26e7cc1b00dd
Author: Rong Tao <rongtao@cestc.cn>
selftests/bpf: Test bpf_strcat,bpf_strncat kfuncs
Add tests for new kfuncs bpf_strcat() and bpf_strncat().
> diff --git a/tools/testing/selftests/bpf/progs/string_kfuncs_failure1.c b/tools/testing/selftests/bpf/progs/string_kfuncs_failure1.c
> index bddc4e8579d24..29bc7eabbb6aa 100644
> --- a/tools/testing/selftests/bpf/progs/string_kfuncs_failure1.c
> +++ b/tools/testing/selftests/bpf/progs/string_kfuncs_failure1.c
[ ... ]
> @@ -53,6 +54,10 @@ 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_strcat_null1(void *ctx) { return bpf_strcat(NULL, 6, "hello"); }
[Severity: Medium]
Will this test, along with test_strcat_user_ptr1 and test_strcat_pagefault1
added below, fail to load in the verifier?
Because the dst argument in bpf_strcat() lacks the __ign suffix, the verifier
strictly checks it as a KF_ARG_PTR_TO_MEM_SIZE. Passing NULL or scalar values
like 1 or -1 causes load-time rejection with -EACCES.
Since the __retval(...) annotation expects a successful load followed by a
runtime error, will these tests cause the test runner to abort with an
unexpected load failure?
> +SEC("syscall") __retval(USER_PTR_ERR)int test_strcat_null2(void *ctx) { return bpf_strcat(kern_buf, sizeof(kern_buf), NULL); }
> +SEC("syscall") __retval(USER_PTR_ERR)int test_strncat_null1(void *ctx) { return bpf_strncat(NULL, 6, "hello", 2); }
> +SEC("syscall") __retval(USER_PTR_ERR)int test_strncat_null2(void *ctx) { return bpf_strncat(kern_buf, sizeof(kern_buf), NULL, 2); }
[ ... ]
> diff --git a/tools/testing/selftests/bpf/progs/string_kfuncs_success.c b/tools/testing/selftests/bpf/progs/string_kfuncs_success.c
> index f65b1226a81a3..93996b0e9595e 100644
> --- a/tools/testing/selftests/bpf/progs/string_kfuncs_success.c
> +++ b/tools/testing/selftests/bpf/progs/string_kfuncs_success.c
[ ... ]
> @@ -59,5 +60,7 @@ __test(-ENOENT) int test_strncasestr_notfound1(void *ctx) { return bpf_strncases
> __test(-ENOENT) int test_strncasestr_notfound2(void *ctx) { return bpf_strncasestr(str, "hello", 4); }
> __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); }
> +__test(10) int test_strcat_success(void *ctx) { return bpf_strcat(buf, sizeof(buf), "world"); }
> +__test(13) int test_strncat_success(void *ctx) { return bpf_strncat(buf, sizeof(buf), "world", 3); }
[Severity: Medium]
Does this test expect an incorrect return value?
The BPF test loader isolates subtests by reloading the skeleton for each test
program. This resets the global variable buf back to its initial .data state
of "hello" (5 bytes) before this test runs.
Because bpf_strncat() appends "wor" (3 bytes) to "hello", won't the correct
return value be 8 instead of 13?
[Severity: High]
This is a pre-existing issue, but I noticed a potential problem in the
__bpf_strncat() implementation being tested here.
kernel/bpf/helpers.c:__bpf_strncat() {
...
__get_kernel_nofault(&cs, src, char, err_out);
if (cs != '\0' && sz > copied)
return -E2BIG;
...
}
Does this unconditionally read past the requested size boundary?
If a caller requests exactly sz bytes, and the source string satisfies this
but ends at a page boundary, won't the unconditional read of the next byte
cause a spurious page fault and incorrectly return -EFAULT instead of success?
> char _license[] SEC("license") = "GPL";
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1784078494.git.rtoax@foxmail.com?part=2
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH bpf-next v2 1/2] bpf: add bpf_strcat,bpf_strncat kfunc
2026-07-15 1:27 ` [PATCH bpf-next v2 1/2] bpf: add bpf_strcat,bpf_strncat kfunc Rong Tao
2026-07-15 1:40 ` sashiko-bot
@ 2026-07-15 8:56 ` Viktor Malik
1 sibling, 0 replies; 5+ messages in thread
From: Viktor Malik @ 2026-07-15 8:56 UTC (permalink / raw)
To: Rong Tao, andrii, ast
Cc: Rong Tao, Daniel Borkmann, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Martin KaFai Lau, Song Liu,
Yonghong Song, Jiri Olsa, Emil Tsalapatis, Shuah Khan,
Yuzuki Ishiyama,
open list:BPF [GENERAL] (Safe Dynamic Programs and Tools),
open list, open list:KERNEL SELFTEST FRAMEWORK
On 7/15/26 03:27, Rong Tao wrote:
> From: Rong Tao <rongtao@cestc.cn>
>
> Add string concatenation kfuncs, prototype:
>
> int bpf_strcat(char *dst__ign, u32 dst__sz, const char *src__ign);
> int bpf_strncat(char *dst__ign, u32 dst__sz, const char *src__ign, u32 len);
>
> This differs from the glibc library functions strcat and strncat, which,
> for safety reasons, require the size of the target string's memory space
> as a parameter.
>
> Signed-off-by: Rong Tao <rongtao@cestc.cn>
> ---
> kernel/bpf/helpers.c | 92 ++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 92 insertions(+)
>
> diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
> index c18f1e16edee..401f94efd687 100644
> --- a/kernel/bpf/helpers.c
> +++ b/kernel/bpf/helpers.c
> @@ -4195,6 +4195,96 @@ __bpf_kfunc int bpf_strncasestr(const char *s1__ign, const char *s2__ign,
> return __bpf_strnstr(s1__ign, s2__ign, len, true);
> }
>
> +static int __bpf_strncat(char *dst, u32 dsz, const char *src, u32 sz)
> +{
> + int dlen, slen, space, copied;
> + char cs = '?';
> +
> + if (!copy_from_kernel_nofault_allowed(dst, 1) ||
> + !copy_from_kernel_nofault_allowed(src, 1)) {
> + return -ERANGE;
> + }
> +
> + dlen = bpf_strnlen(dst, dsz);
> + if (dlen < 0)
> + return dlen;
> + slen = bpf_strnlen(src, sz);
> + if (slen < 0)
> + return slen;
> +
> + if (dlen >= dsz || sz == 0 || dsz == 0)
> + return -EINVAL;
> +
> + space = dsz - dlen;
> + if (space <= 1 || space < min(slen, sz) + 1)
> + return -E2BIG;
> +
> + guard(pagefault)();
> + for (copied = 0; copied < space - 1 && copied < slen; copied++) {
> + __get_kernel_nofault(&cs, src, char, err_out);
> + if (cs == '\0')
> + break;
> +
> + __put_kernel_nofault(dst + dlen + copied, &cs, char, err_out);
I don't think that we need __put_kernel_nofault() here. My understanding
is that since we don't use the `__ign` suffix for the destination
string, the verifier should make sure that `dst` points to a valid
memory with sufficient capacity (thanks to the `dst__sz` arg). We could
use `strncpy_from_kernel_nofault(dst + dlen, src, space)`, which makes
me wonder how would `bpf_strcat` be different from directly using
bpf_probe_read_kernel_str().
Viktor
> +
> + src++;
> + }
> + cs = '\0';
> + __put_kernel_nofault(dst + dlen + copied, &cs, char, err_out);
> +
> + __get_kernel_nofault(&cs, src, char, err_out);
> + if (cs != '\0' && sz > copied)
> + return -E2BIG;
> +
> + return dlen + copied;
> +err_out:
> + return -EFAULT;
> +}
> +
> +/**
> + * bpf_strcat - Append non-null bytes from a source string, and null-terminate
> + * the result
> + * @dst: Destination string.
> + * @dst__sz: Maximum bytes of @dst__ign, includes the trailing NUL.
> + * @src__ign: Source string.
> + *
> + * Return:
> + * * >=0 - Length of the concatenated string.
> + *
> + * * %-EINVAL - String @dst__ign is invalid.
> + * * %-EFAULT - Cannot read or write one of the strings.
> + * * %-E2BIG - String @src__ign is too large or the remaining space in
> + * @dst__ign is too small.
> + * * %-ERANGE - One of the strings is outside of kernel address space
> + */
> +__bpf_kfunc int bpf_strcat(char *dst, u32 dst__sz, const char *src__ign)
> +{
> + return __bpf_strncat(dst, dst__sz, src__ign, XATTR_SIZE_MAX);
> +}
> +
> +/**
> + * bpf_strncat - Append non-null bytes from a source string, and null-terminate
> + * the result
> + * @dst: Destination string.
> + * @dst__sz: Maximum bytes of @dst__ign, includes the trailing NUL.
> + * @src__ign: Source string.
> + * @len: the maximum number of characters to concatenate
> + *
> + * Return:
> + * * >=0 - Length of the concatenated string.
> + *
> + * * %-EINVAL - String @dst__ign is invalid.
> + * * %-EFAULT - Cannot read or write one of the strings.
> + * * %-E2BIG - String @src__ign is too large or the remaining space in
> + * @dst__ign is too small.
> + * * %-ERANGE - One of the strings is outside of kernel address space
> + */
> +__bpf_kfunc int bpf_strncat(char *dst, u32 dst__sz, const char *src__ign,
> + u32 len)
> +{
> + return __bpf_strncat(dst, dst__sz, src__ign, len);
> +}
> +
> #ifdef CONFIG_KEYS
> /**
> * bpf_lookup_user_key - lookup a key by its serial
> @@ -4958,6 +5048,8 @@ BTF_ID_FLAGS(func, bpf_strstr);
> BTF_ID_FLAGS(func, bpf_strcasestr);
> BTF_ID_FLAGS(func, bpf_strnstr);
> BTF_ID_FLAGS(func, bpf_strncasestr);
> +BTF_ID_FLAGS(func, bpf_strcat);
> +BTF_ID_FLAGS(func, bpf_strncat);
> #if defined(CONFIG_BPF_LSM) && defined(CONFIG_CGROUPS)
> BTF_ID_FLAGS(func, bpf_cgroup_read_xattr, KF_RCU)
> #endif
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-07-15 8:56 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
[not found] <cover.1784078494.git.rtoax@foxmail.com>
2026-07-15 1:27 ` [PATCH bpf-next v2 1/2] bpf: add bpf_strcat,bpf_strncat kfunc Rong Tao
2026-07-15 1:40 ` sashiko-bot
2026-07-15 8:56 ` Viktor Malik
2026-07-15 1:27 ` [PATCH bpf-next v2 2/2] selftests/bpf: Test bpf_strcat,bpf_strncat kfuncs Rong Tao
2026-07-15 1:42 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox