From: Jiri Olsa <jolsa@kernel.org>
To: Alexei Starovoitov <ast@kernel.org>,
Daniel Borkmann <daniel@iogearbox.net>
Cc: bpf@vger.kernel.org, linux-mm@kvack.org,
Andrii Nakryiko <andrii@kernel.org>,
Eduard Zingerman <eddyz87@gmail.com>,
Kumar Kartikeya Dwivedi <memxor@gmail.com>,
Lorenzo Stoakes <ljs@kernel.org>,
John Fastabend <john.fastabend@gmail.com>,
Federico Di Pierro <nierro92@gmail.com>
Subject: [PATCHv2 bpf-next 2/8] bpf: Add user memory kfuncs
Date: Tue, 3 Nov 2026 08:59:09 +0100 [thread overview]
Message-ID: <20261103075916.100050-3-jolsa@kernel.org> (raw)
In-Reply-To: <20261103075916.100050-1-jolsa@kernel.org>
Add sleepable kfuncs to copy data into a task's address space and to map
or unmap private anonymous memory in the current process.
Require a usable user mm for mmap and munmap. Register the kfuncs for
KPROBE programs and gate them on CAP_SYS_ADMIN BPF token capability and
the BPF_WRITE_USER lockdown check.
Use FOLL_FORCE | FOLL_WRITE for task memory copies and return -EFAULT
when a copy is incomplete.
Rename the syscall info-copy helper to bpf_copy_to_user_buf to avoid
the name collision.
Assisted-by: Codex:GPT-6
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
Documentation/bpf/kfuncs.rst | 15 ++++
kernel/bpf/helpers.c | 132 +++++++++++++++++++++++++++++++++++
kernel/bpf/syscall.c | 10 +--
3 files changed, 152 insertions(+), 5 deletions(-)
diff --git a/Documentation/bpf/kfuncs.rst b/Documentation/bpf/kfuncs.rst
index 73578a4c3b1f..8ca7aad8ea36 100644
--- a/Documentation/bpf/kfuncs.rst
+++ b/Documentation/bpf/kfuncs.rst
@@ -965,3 +965,18 @@ the verifier. bpf_cgroup_ancestor() can be used as follows:
BPF provides a set of kfuncs that can be used to query, allocate, mutate, and
destroy struct cpumask * objects. Please refer to :ref:`cpumasks-header-label`
for more details.
+
+4.4 User memory kfuncs
+----------------------
+
+The user memory kfuncs operate on the current process's address space.
+``bpf_copy_to_user()`` writes to this address space, including private mappings
+that are not writable. A partial copy is not rolled back if it faults.
+
+``bpf_user_anon_mmap()`` and ``bpf_user_munmap()`` create and remove mappings
+by calling the kernel's mapping functions directly rather than issuing
+``mmap(2)`` or ``munmap(2)`` syscalls. As a result, seccomp filters do not
+mediate these mapping operations.
+
+.. kernel-doc:: kernel/bpf/helpers.c
+ :identifiers: bpf_copy_to_user bpf_user_anon_mmap bpf_user_munmap
diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index 58e357302850..ae7dfa58ae95 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -29,6 +29,12 @@
#include <linux/task_work.h>
#include <linux/irq_work.h>
#include <linux/buildid.h>
+#include <linux/mm.h>
+#include <linux/mman.h>
+#include <linux/limits.h>
+#ifdef CONFIG_IA32_EMULATION
+#include <asm/thread_info.h>
+#endif
#include "../../lib/kstrtox.h"
@@ -3741,6 +3747,110 @@ __bpf_kfunc int bpf_copy_from_user_mm(void *dst, u32 dst__sz,
return ret;
}
+/**
+ * bpf_copy_to_user() - Copy data to the current task's address space
+ * @unsafe_ptr__ign: Destination address in the current task's address space
+ * @src: Source address, in kernel space
+ * @src__sz: Number of bytes to copy
+ * @flags: Reserved for future use; must be zero
+ *
+ * Faults in the destination and writes the way ptrace(PTRACE_POKETEXT) does:
+ * a private mapping doesn't have to be writable. A partial copy is not undone,
+ * the bytes before the fault stay written.
+ *
+ * The caller must coordinate with other threads so they do not execute the
+ * destination range while it is being modified, and ensure execution begins
+ * only after the copy completes.
+ *
+ * Return: 0 on success, -EINVAL if @flags is non-zero, or -EFAULT if the copy
+ * fails or is partial.
+ */
+__bpf_kfunc int bpf_copy_to_user(void __user *unsafe_ptr__ign, const void *src,
+ u32 src__sz, u64 flags)
+{
+ int ret;
+
+ if (unlikely(flags))
+ return -EINVAL;
+ if (unlikely(src__sz > S32_MAX))
+ return -EINVAL;
+ if (unlikely(!src__sz))
+ return 0;
+ ret = access_process_vm(current, (unsigned long)unsafe_ptr__ign, (void *)src,
+ src__sz, FOLL_FORCE | FOLL_WRITE);
+ return ret == src__sz ? 0 : -EFAULT;
+}
+
+/* bpf_wq and bpf_task_work callbacks can run without a usable mm. */
+static bool bpf_user_mm_ok(void)
+{
+ return current->mm && !(current->flags & (PF_KTHREAD | PF_EXITING));
+}
+
+#ifdef CONFIG_IA32_EMULATION
+static unsigned long bpf_user_vm_mmap(unsigned long len, unsigned long prot)
+{
+ struct thread_info *ti = current_thread_info();
+ bool compat = test_thread_flag(TIF_ADDR32);
+ unsigned long addr;
+
+ /*
+ * We are not in a syscall, but x86 get_unmapped_area() paths
+ * ignore TIF_ADDR32 and rely on in_32bit_syscall().
+ */
+ if (compat)
+ ti->status |= TS_COMPAT;
+ addr = vm_mmap(NULL, 0, len, prot, MAP_PRIVATE | MAP_ANONYMOUS, 0);
+ if (compat)
+ ti->status &= ~TS_COMPAT;
+ return addr;
+}
+#else
+static unsigned long bpf_user_vm_mmap(unsigned long len, unsigned long prot)
+{
+ return vm_mmap(NULL, 0, len, prot, MAP_PRIVATE | MAP_ANONYMOUS, 0);
+}
+#endif
+
+/**
+ * bpf_user_anon_mmap() - Map private anonymous memory into the current process
+ * @len: Length of the mapping
+ * @prot: PROT_* flags of mmap(2), including PROT_EXEC
+ *
+ * This maps memory by calling vm_mmap() directly. It does not invoke the
+ * mmap(2) syscall, so seccomp filters do not mediate this mapping.
+ *
+ * Return: The address of the mapping on success, -EPERM if the caller has no
+ * user mm, or an unsigned-long encoded negative error code, as returned by
+ * vm_mmap().
+ */
+__bpf_kfunc unsigned long bpf_user_anon_mmap(unsigned long len, unsigned long prot)
+{
+ if (!bpf_user_mm_ok())
+ return -EPERM;
+ if (!arch_validate_prot(prot, 0))
+ return -EINVAL;
+ return bpf_user_vm_mmap(len, prot);
+}
+
+/**
+ * bpf_user_munmap() - Unmap a range of addresses of the current process
+ * @addr: Start of the range
+ * @len: Length of the range
+ *
+ * This unmaps memory by calling vm_munmap() directly. It does not invoke the
+ * munmap(2) syscall, so seccomp filters do not mediate this unmapping.
+ *
+ * Return: 0 on success, -EPERM if the caller has no user mm, or a negative
+ * error code.
+ */
+__bpf_kfunc int bpf_user_munmap(unsigned long addr, unsigned long len)
+{
+ if (!bpf_user_mm_ok())
+ return -EPERM;
+ return vm_munmap(untagged_addr(addr), len);
+}
+
/**
* bpf_copy_from_user_mm_str() - Copy a string from an address space
* @dst: Destination address, in kernel space. This buffer must be
@@ -5142,6 +5252,27 @@ static const struct btf_kfunc_id_set generic_kfunc_set = {
.set = &generic_btf_ids,
};
+BTF_KFUNCS_START(user_mem_btf_ids)
+BTF_ID_FLAGS(func, bpf_copy_to_user, KF_SLEEPABLE | KF_PERFMON)
+BTF_ID_FLAGS(func, bpf_user_anon_mmap, KF_SLEEPABLE | KF_PERFMON)
+BTF_ID_FLAGS(func, bpf_user_munmap, KF_SLEEPABLE | KF_PERFMON)
+BTF_KFUNCS_END(user_mem_btf_ids)
+
+static int user_mem_kfunc_filter(const struct bpf_prog *prog, u32 kfunc_id)
+{
+ if (!btf_id_set8_contains(&user_mem_btf_ids, kfunc_id))
+ return 0;
+ if (!bpf_token_capable(prog->aux->token, CAP_SYS_ADMIN))
+ return -EPERM;
+ return security_locked_down(LOCKDOWN_BPF_WRITE_USER);
+}
+
+static const struct btf_kfunc_id_set user_mem_kfunc_set = {
+ .owner = THIS_MODULE,
+ .set = &user_mem_btf_ids,
+ .filter = user_mem_kfunc_filter,
+};
+
BTF_ID_LIST(generic_dtor_ids)
BTF_ID(struct, task_struct)
BTF_ID(func, bpf_task_release_dtor)
@@ -5269,6 +5400,7 @@ static int __init kfunc_init(void)
};
ret = register_btf_kfunc_id_set(BPF_PROG_TYPE_TRACING, &generic_kfunc_set);
+ ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_KPROBE, &user_mem_kfunc_set);
ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_SCHED_CLS, &generic_kfunc_set);
ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_XDP, &generic_kfunc_set);
ret = ret ?: register_btf_kfunc_id_set(BPF_PROG_TYPE_STRUCT_OPS, &generic_kfunc_set);
diff --git a/kernel/bpf/syscall.c b/kernel/bpf/syscall.c
index 02ed5368f056..9004f2673ccf 100644
--- a/kernel/bpf/syscall.c
+++ b/kernel/bpf/syscall.c
@@ -3932,8 +3932,8 @@ static void bpf_raw_tp_link_show_fdinfo(const struct bpf_link *link,
raw_tp_link->cookie);
}
-static int bpf_copy_to_user(char __user *ubuf, const char *buf, u32 ulen,
- u32 len)
+static int bpf_copy_to_user_buf(char __user *ubuf, const char *buf, u32 ulen,
+ u32 len)
{
if (ulen >= len + 1) {
if (copy_to_user(ubuf, buf, len + 1))
@@ -3970,7 +3970,7 @@ static int bpf_raw_tp_link_fill_link_info(const struct bpf_link *link,
if (!ubuf)
return 0;
- return bpf_copy_to_user(ubuf, tp_name, ulen, tp_len);
+ return bpf_copy_to_user_buf(ubuf, tp_name, ulen, tp_len);
}
static const struct bpf_link_ops bpf_raw_tp_link_lops = {
@@ -4031,7 +4031,7 @@ static int bpf_perf_link_fill_common(const struct perf_event *event,
goto out;
if (buf) {
- err = bpf_copy_to_user(uname, buf, ulen, len);
+ err = bpf_copy_to_user_buf(uname, buf, ulen, len);
} else {
char zero = '\0';
@@ -5710,7 +5710,7 @@ static int bpf_task_fd_query_copy(const union bpf_attr *attr,
if (put_user(zero, ubuf))
return -EFAULT;
} else {
- err = bpf_copy_to_user(ubuf, buf, input_len, len);
+ err = bpf_copy_to_user_buf(ubuf, buf, input_len, len);
if (err == -EFAULT)
return err;
}
--
2.43.0
next prev parent reply other threads:[~2026-11-03 7:59 UTC|newest]
Thread overview: 22+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-11-03 7:59 [PATCHv2 bpf-next 0/8] bpf: Add user memory kfuncs Jiri Olsa
2026-11-03 7:59 ` [PATCHv2 bpf-next 1/8] bpf: Honor const on generic kfunc memory arguments Jiri Olsa
2026-11-03 8:52 ` bot+bpf-ci
2026-11-03 18:03 ` Alexei Starovoitov
2026-11-03 21:36 ` Jiri Olsa
2026-11-03 7:59 ` Jiri Olsa [this message]
2026-11-03 8:19 ` [PATCHv2 bpf-next 2/8] bpf: Add user memory kfuncs sashiko-bot
2026-11-03 8:52 ` bot+bpf-ci
2026-11-03 18:05 ` Alexei Starovoitov
2026-11-03 21:36 ` Jiri Olsa
2026-11-03 7:59 ` [PATCHv2 bpf-next 3/8] selftests/bpf: Add get_procmap_info helper Jiri Olsa
2026-11-03 7:59 ` [PATCHv2 bpf-next 4/8] selftests/bpf: Test const kfunc memory arguments Jiri Olsa
2026-11-03 8:10 ` sashiko-bot
2026-11-03 7:59 ` [PATCHv2 bpf-next 5/8] selftests/bpf: Test user memory mmap kfuncs Jiri Olsa
2026-11-03 8:05 ` sashiko-bot
2026-11-03 7:59 ` [PATCHv2 bpf-next 6/8] selftests/bpf: Test user memory page exec Jiri Olsa
2026-11-03 8:39 ` bot+bpf-ci
2026-11-03 7:59 ` [PATCHv2 bpf-next 7/8] selftests/bpf: Test bpf_copy_to_user kfunc Jiri Olsa
2026-11-03 7:59 ` [PATCHv2 bpf-next 8/8] selftests/bpf: Test user memory kfunc errors Jiri Olsa
2026-11-03 8:39 ` bot+bpf-ci
2026-11-03 18:08 ` [PATCHv2 bpf-next 0/8] bpf: Add user memory kfuncs Alexei Starovoitov
2026-11-03 21:36 ` Jiri Olsa
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20261103075916.100050-3-jolsa@kernel.org \
--to=jolsa@kernel.org \
--cc=andrii@kernel.org \
--cc=ast@kernel.org \
--cc=bpf@vger.kernel.org \
--cc=daniel@iogearbox.net \
--cc=eddyz87@gmail.com \
--cc=john.fastabend@gmail.com \
--cc=linux-mm@kvack.org \
--cc=ljs@kernel.org \
--cc=memxor@gmail.com \
--cc=nierro92@gmail.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.