bpf: Implement dynptr copy kfuncs
authorMykyta Yatsenko <yatsenko@meta.com>
Mon, 12 May 2025 20:53:47 +0000 (21:53 +0100)
committerAlexei Starovoitov <ast@kernel.org>
Tue, 13 May 2025 01:31:51 +0000 (18:31 -0700)
This patch introduces a new set of kfuncs for working with dynptrs in
BPF programs, enabling reading variable-length user or kernel data
into dynptr directly. To enable memory-safety, verifier allows only
constant-sized reads via existing bpf_probe_read_{user|kernel} etc.
kfuncs, dynptr-based kfuncs allow dynamically-sized reads without memory
safety shortcomings.

The following kfuncs are introduced:
* `bpf_probe_read_kernel_dynptr()`: probes kernel-space data into a dynptr
* `bpf_probe_read_user_dynptr()`: probes user-space data into a dynptr
* `bpf_probe_read_kernel_str_dynptr()`: probes kernel-space string into
a dynptr
* `bpf_probe_read_user_str_dynptr()`: probes user-space string into a
dynptr
* `bpf_copy_from_user_dynptr()`: sleepable, copies user-space data into
a dynptr for the current task
* `bpf_copy_from_user_str_dynptr()`: sleepable, copies user-space string
into a dynptr for the current task
* `bpf_copy_from_user_task_dynptr()`: sleepable, copies user-space data
of the task into a dynptr
* `bpf_copy_from_user_task_str_dynptr()`: sleepable, copies user-space
string of the task into a dynptr

The implementation is built on two generic functions:
 * __bpf_dynptr_copy
 * __bpf_dynptr_copy_str
These functions take function pointers as arguments, enabling the
copying of data from various sources, including both kernel and user
space.
Use __always_inline for generic functions and callbacks to make sure the
compiler doesn't generate indirect calls into callbacks, which is more
expensive, especially on some kernel configurations. Inlining allows
compiler to put direct calls into all the specific callback implementations
(copy_user_data_sleepable, copy_user_data_nofault, and so on).

Reviewed-by: Andrii Nakryiko <andrii@kernel.org>
Signed-off-by: Mykyta Yatsenko <yatsenko@meta.com>
Link: https://lore.kernel.org/r/20250512205348.191079-3-mykyta.yatsenko5@gmail.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
kernel/bpf/helpers.c
kernel/trace/bpf_trace.c

index c96cc6a..ebb604e 100644 (file)
@@ -3378,6 +3378,14 @@ BTF_ID_FLAGS(func, bpf_iter_kmem_cache_next, KF_ITER_NEXT | KF_RET_NULL | KF_SLE
 BTF_ID_FLAGS(func, bpf_iter_kmem_cache_destroy, KF_ITER_DESTROY | KF_SLEEPABLE)
 BTF_ID_FLAGS(func, bpf_local_irq_save)
 BTF_ID_FLAGS(func, bpf_local_irq_restore)
+BTF_ID_FLAGS(func, bpf_probe_read_user_dynptr)
+BTF_ID_FLAGS(func, bpf_probe_read_kernel_dynptr)
+BTF_ID_FLAGS(func, bpf_probe_read_user_str_dynptr)
+BTF_ID_FLAGS(func, bpf_probe_read_kernel_str_dynptr)
+BTF_ID_FLAGS(func, bpf_copy_from_user_dynptr, KF_SLEEPABLE)
+BTF_ID_FLAGS(func, bpf_copy_from_user_str_dynptr, KF_SLEEPABLE)
+BTF_ID_FLAGS(func, bpf_copy_from_user_task_dynptr, KF_SLEEPABLE | KF_TRUSTED_ARGS)
+BTF_ID_FLAGS(func, bpf_copy_from_user_task_str_dynptr, KF_SLEEPABLE | KF_TRUSTED_ARGS)
 BTF_KFUNCS_END(common_btf_ids)
 
 static const struct btf_kfunc_id_set common_kfunc_set = {
index 8689209..985ce38 100644 (file)
@@ -3466,6 +3466,142 @@ static int __init bpf_kprobe_multi_kfuncs_init(void)
 
 late_initcall(bpf_kprobe_multi_kfuncs_init);
 
+typedef int (*copy_fn_t)(void *dst, const void *src, u32 size, struct task_struct *tsk);
+
+/*
+ * The __always_inline is to make sure the compiler doesn't
+ * generate indirect calls into callbacks, which is expensive,
+ * on some kernel configurations. This allows compiler to put
+ * direct calls into all the specific callback implementations
+ * (copy_user_data_sleepable, copy_user_data_nofault, and so on)
+ */
+static __always_inline int __bpf_dynptr_copy_str(struct bpf_dynptr *dptr, u32 doff, u32 size,
+                                                const void *unsafe_src,
+                                                copy_fn_t str_copy_fn,
+                                                struct task_struct *tsk)
+{
+       struct bpf_dynptr_kern *dst;
+       u32 chunk_sz, off;
+       void *dst_slice;
+       int cnt, err;
+       char buf[256];
+
+       dst_slice = bpf_dynptr_slice_rdwr(dptr, doff, NULL, size);
+       if (likely(dst_slice))
+               return str_copy_fn(dst_slice, unsafe_src, size, tsk);
+
+       dst = (struct bpf_dynptr_kern *)dptr;
+       if (bpf_dynptr_check_off_len(dst, doff, size))
+               return -E2BIG;
+
+       for (off = 0; off < size; off += chunk_sz - 1) {
+               chunk_sz = min_t(u32, sizeof(buf), size - off);
+               /* Expect str_copy_fn to return count of copied bytes, including
+                * zero terminator. Next iteration increment off by chunk_sz - 1 to
+                * overwrite NUL.
+                */
+               cnt = str_copy_fn(buf, unsafe_src + off, chunk_sz, tsk);
+               if (cnt < 0)
+                       return cnt;
+               err = __bpf_dynptr_write(dst, doff + off, buf, cnt, 0);
+               if (err)
+                       return err;
+               if (cnt < chunk_sz || chunk_sz == 1) /* we are done */
+                       return off + cnt;
+       }
+       return off;
+}
+
+static __always_inline int __bpf_dynptr_copy(const struct bpf_dynptr *dptr, u32 doff,
+                                            u32 size, const void *unsafe_src,
+                                            copy_fn_t copy_fn, struct task_struct *tsk)
+{
+       struct bpf_dynptr_kern *dst;
+       void *dst_slice;
+       char buf[256];
+       u32 off, chunk_sz;
+       int err;
+
+       dst_slice = bpf_dynptr_slice_rdwr(dptr, doff, NULL, size);
+       if (likely(dst_slice))
+               return copy_fn(dst_slice, unsafe_src, size, tsk);
+
+       dst = (struct bpf_dynptr_kern *)dptr;
+       if (bpf_dynptr_check_off_len(dst, doff, size))
+               return -E2BIG;
+
+       for (off = 0; off < size; off += chunk_sz) {
+               chunk_sz = min_t(u32, sizeof(buf), size - off);
+               err = copy_fn(buf, unsafe_src + off, chunk_sz, tsk);
+               if (err)
+                       return err;
+               err = __bpf_dynptr_write(dst, doff + off, buf, chunk_sz, 0);
+               if (err)
+                       return err;
+       }
+       return 0;
+}
+
+static __always_inline int copy_user_data_nofault(void *dst, const void *unsafe_src,
+                                                 u32 size, struct task_struct *tsk)
+{
+       return copy_from_user_nofault(dst, (const void __user *)unsafe_src, size);
+}
+
+static __always_inline int copy_user_data_sleepable(void *dst, const void *unsafe_src,
+                                                   u32 size, struct task_struct *tsk)
+{
+       int ret;
+
+       if (!tsk) /* Read from the current task */
+               return copy_from_user(dst, (const void __user *)unsafe_src, size);
+
+       ret = access_process_vm(tsk, (unsigned long)unsafe_src, dst, size, 0);
+       if (ret != size)
+               return -EFAULT;
+       return 0;
+}
+
+static __always_inline int copy_kernel_data_nofault(void *dst, const void *unsafe_src,
+                                                   u32 size, struct task_struct *tsk)
+{
+       return copy_from_kernel_nofault(dst, unsafe_src, size);
+}
+
+static __always_inline int copy_user_str_nofault(void *dst, const void *unsafe_src,
+                                                u32 size, struct task_struct *tsk)
+{
+       return strncpy_from_user_nofault(dst, (const void __user *)unsafe_src, size);
+}
+
+static __always_inline int copy_user_str_sleepable(void *dst, const void *unsafe_src,
+                                                  u32 size, struct task_struct *tsk)
+{
+       int ret;
+
+       if (unlikely(size == 0))
+               return 0;
+
+       if (tsk) {
+               ret = copy_remote_vm_str(tsk, (unsigned long)unsafe_src, dst, size, 0);
+       } else {
+               ret = strncpy_from_user(dst, (const void __user *)unsafe_src, size - 1);
+               /* strncpy_from_user does not guarantee NUL termination */
+               if (ret >= 0)
+                       ((char *)dst)[ret] = '\0';
+       }
+
+       if (ret < 0)
+               return ret;
+       return ret + 1;
+}
+
+static __always_inline int copy_kernel_str_nofault(void *dst, const void *unsafe_src,
+                                                  u32 size, struct task_struct *tsk)
+{
+       return strncpy_from_kernel_nofault(dst, unsafe_src, size);
+}
+
 __bpf_kfunc_start_defs();
 
 __bpf_kfunc int bpf_send_signal_task(struct task_struct *task, int sig, enum pid_type type,
@@ -3477,4 +3613,62 @@ __bpf_kfunc int bpf_send_signal_task(struct task_struct *task, int sig, enum pid
        return bpf_send_signal_common(sig, type, task, value);
 }
 
+__bpf_kfunc int bpf_probe_read_user_dynptr(struct bpf_dynptr *dptr, u32 off,
+                                          u32 size, const void __user *unsafe_ptr__ign)
+{
+       return __bpf_dynptr_copy(dptr, off, size, (const void *)unsafe_ptr__ign,
+                                copy_user_data_nofault, NULL);
+}
+
+__bpf_kfunc int bpf_probe_read_kernel_dynptr(struct bpf_dynptr *dptr, u32 off,
+                                            u32 size, const void *unsafe_ptr__ign)
+{
+       return __bpf_dynptr_copy(dptr, off, size, unsafe_ptr__ign,
+                                copy_kernel_data_nofault, NULL);
+}
+
+__bpf_kfunc int bpf_probe_read_user_str_dynptr(struct bpf_dynptr *dptr, u32 off,
+                                              u32 size, const void __user *unsafe_ptr__ign)
+{
+       return __bpf_dynptr_copy_str(dptr, off, size, (const void *)unsafe_ptr__ign,
+                                    copy_user_str_nofault, NULL);
+}
+
+__bpf_kfunc int bpf_probe_read_kernel_str_dynptr(struct bpf_dynptr *dptr, u32 off,
+                                                u32 size, const void *unsafe_ptr__ign)
+{
+       return __bpf_dynptr_copy_str(dptr, off, size, unsafe_ptr__ign,
+                                    copy_kernel_str_nofault, NULL);
+}
+
+__bpf_kfunc int bpf_copy_from_user_dynptr(struct bpf_dynptr *dptr, u32 off,
+                                         u32 size, const void __user *unsafe_ptr__ign)
+{
+       return __bpf_dynptr_copy(dptr, off, size, (const void *)unsafe_ptr__ign,
+                                copy_user_data_sleepable, NULL);
+}
+
+__bpf_kfunc int bpf_copy_from_user_str_dynptr(struct bpf_dynptr *dptr, u32 off,
+                                             u32 size, const void __user *unsafe_ptr__ign)
+{
+       return __bpf_dynptr_copy_str(dptr, off, size, (const void *)unsafe_ptr__ign,
+                                    copy_user_str_sleepable, NULL);
+}
+
+__bpf_kfunc int bpf_copy_from_user_task_dynptr(struct bpf_dynptr *dptr, u32 off,
+                                              u32 size, const void __user *unsafe_ptr__ign,
+                                              struct task_struct *tsk)
+{
+       return __bpf_dynptr_copy(dptr, off, size, (const void *)unsafe_ptr__ign,
+                                copy_user_data_sleepable, tsk);
+}
+
+__bpf_kfunc int bpf_copy_from_user_task_str_dynptr(struct bpf_dynptr *dptr, u32 off,
+                                                  u32 size, const void __user *unsafe_ptr__ign,
+                                                  struct task_struct *tsk)
+{
+       return __bpf_dynptr_copy_str(dptr, off, size, (const void *)unsafe_ptr__ign,
+                                    copy_user_str_sleepable, tsk);
+}
+
 __bpf_kfunc_end_defs();