mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [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  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, 1 reply; 4+ 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	[flat|nested] 4+ 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
  1 sibling, 0 replies; 4+ 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	[flat|nested] 4+ 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  8:56   ` Viktor Malik
  2026-07-16  8:16     ` Rong Tao
  0 siblings, 1 reply; 4+ 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] 4+ messages in thread

* Re: [PATCH bpf-next v2 1/2] bpf: add bpf_strcat,bpf_strncat kfunc
  2026-07-15  8:56   ` Viktor Malik
@ 2026-07-16  8:16     ` Rong Tao
  0 siblings, 0 replies; 4+ messages in thread
From: Rong Tao @ 2026-07-16  8:16 UTC (permalink / raw)
  To: Viktor Malik, 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 16:56, Viktor Malik wrote:
> 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().

Indeed, bpf_probe_read_kernel_str() and bpf_strcat() seem to have no

fundamental difference. Perhaps helpers are not as convenient to use

as kfunc? For example, in bpftrace's stdlib, to avoid including certain

header files that would make it inconvenient to use helpers, one would

have to use a complex method like asm() + BPF_FUNC_ to call the helper.

Using kfunc is much more convenient.

Rong Tao

>
> 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] 4+ messages in thread

end of thread, other threads:[~2026-07-16  8:17 UTC | newest]

Thread overview: 4+ 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  8:56   ` Viktor Malik
2026-07-16  8:16     ` Rong Tao
2026-07-15  1:27 ` [PATCH bpf-next v2 2/2] selftests/bpf: Test bpf_strcat,bpf_strncat kfuncs Rong Tao

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox