On 7/15/26 16:56, Viktor Malik wrote:
On 7/15/26 03:27, Rong Tao wrote:
From: Rong Tao <[email protected]>

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 <[email protected]>
---
  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


Reply via email to