* [RFC bpf-next 1/9] bpf: Add __srcptr kfunc argument suffix
2026-10-21 19:57 [RFC bpf-next 0/9] bpf: Add support to allocate user page Jiri Olsa
@ 2026-10-21 19:57 ` Jiri Olsa
2026-10-21 19:57 ` [RFC bpf-next 2/9] bpf: Add uprobe faultable user copy kfunc Jiri Olsa
` (8 subsequent siblings)
9 siblings, 0 replies; 19+ messages in thread
From: Jiri Olsa @ 2026-10-21 19:57 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann
Cc: bpf, Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
linux-mm, Andrew Morton, Liam R. Howlett, Lorenzo Stoakes,
linux-kselftest, Shuah Khan, John Fastabend
Generic sized pointer arguments are treated as writable memory. This
prevents kfuncs that only read from a source buffer from accepting
read-only BPF memory, such as const data.
Adding __srcptr to mark pointers the kfunc only reads.
Assisted-by: Codex:GPT-5
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
Documentation/bpf/kfuncs.rst | 23 +++++++++++++++++++++++
kernel/bpf/verifier.c | 18 ++++++++++++++++--
2 files changed, 39 insertions(+), 2 deletions(-)
diff --git a/Documentation/bpf/kfuncs.rst b/Documentation/bpf/kfuncs.rst
index 73578a4c3b1f..6466b76fd721 100644
--- a/Documentation/bpf/kfuncs.rst
+++ b/Documentation/bpf/kfuncs.rst
@@ -351,6 +351,29 @@ In this case, a program attached to the ``inode_init_security`` LSM hook
can pass the hook's own ``xattr_count`` argument through to the kfunc,
which claims xattr slots by writing through it.
+2.3.10 __srcptr Annotation
+-------------------------
+
+This annotation marks a pointer argument as a source buffer that the kfunc
+reads without writing. The verifier requires read access, so the source may
+point to read-only or read/write memory. Pair variable-size buffers with a
+following ``__sz`` or ``__szk`` argument. For a scalar pointer, the verifier
+uses the pointee's BTF size; a ``void *`` requires an explicit size.
+
+For example, the first kfunc takes a variable-size buffer, while the second
+takes a scalar source::
+
+ __bpf_kfunc void bpf_consume_data(const void *src__srcptr,
+ u32 src__sz)
+ {
+ ...
+ }
+
+ __bpf_kfunc void bpf_consume_scalar(const u32 *src__srcptr)
+ {
+ ...
+ }
+
.. _BPF_kfunc_nodef:
2.4 Using an existing kernel function
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 5601d2056218..7dd7b172a297 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -13205,6 +13205,12 @@ static bool is_kfunc_arg_mem_size(const struct btf *btf,
return btf_param_match_suffix(btf, arg, "__sz");
}
+static bool is_kfunc_arg_srcptr(const struct btf *btf,
+ const struct btf_param *arg)
+{
+ return btf_param_match_suffix(btf, arg, "__srcptr");
+}
+
static bool is_kfunc_arg_const_mem_size(const struct btf *btf,
const struct btf_param *arg)
{
@@ -13966,6 +13972,14 @@ u32 btf_func_arg_align(const struct btf *btf, const struct btf_type *t)
return __btf_func_arg_align(btf, t, 0);
}
+static int mem_prot(const struct btf *btf, const struct btf_param *arg)
+{
+ if (is_kfunc_arg_srcptr(btf, arg))
+ return MEM_RDONLY;
+ else
+ return MEM_WRITE;
+}
+
static int
get_kfunc_arg_type(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,
const struct btf_param *args, int arg, int nargs, u32 slot,
@@ -14137,7 +14151,7 @@ get_kfunc_arg_type(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,
reg_arg_name(env, argno), btf_type_str(ref_t), ref_tname);
return -EINVAL;
}
- arg_type = ARG_PTR_TO_MEM | MEM_WRITE;
+ arg_type = ARG_PTR_TO_MEM | mem_prot(meta->btf, &args[arg]);
} else if (btf_type_is_struct(ref_t))
/* A pointer to a struct without a size argument is classified as ARG_PTR_TO_BTF_ID */
arg_type = ARG_PTR_TO_BTF_ID;
@@ -14162,7 +14176,7 @@ get_kfunc_arg_type(struct bpf_verifier_env *env, struct bpf_call_arg_meta *meta,
return -EINVAL;
}
proto->arg_size[arg] = type_size;
- arg_type = ARG_PTR_TO_MEM | MEM_FIXED_SIZE | MEM_WRITE;
+ arg_type = ARG_PTR_TO_MEM | MEM_FIXED_SIZE | mem_prot(meta->btf, &args[arg]);
}
if (is_kfunc_arg_uninit(meta->btf, &args[arg]))
--
2.54.0
^ permalink raw reply related [flat|nested] 19+ messages in thread* [RFC bpf-next 2/9] bpf: Add uprobe faultable user copy kfunc
2026-10-21 19:57 [RFC bpf-next 0/9] bpf: Add support to allocate user page Jiri Olsa
2026-10-21 19:57 ` [RFC bpf-next 1/9] bpf: Add __srcptr kfunc argument suffix Jiri Olsa
@ 2026-10-21 19:57 ` Jiri Olsa
2026-10-21 20:30 ` bot+bpf-ci
2026-10-21 19:57 ` [RFC bpf-next 3/9] mm: Expose locked mprotect operation Jiri Olsa
` (7 subsequent siblings)
9 siblings, 1 reply; 19+ messages in thread
From: Jiri Olsa @ 2026-10-21 19:57 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann
Cc: bpf, Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
linux-mm, Andrew Morton, Liam R. Howlett, Lorenzo Stoakes,
linux-kselftest, Shuah Khan, John Fastabend
The bpf_probe_write_user() helper uses a no-fault copy. Adding kfunc
that can fault in the destination pages while copying to user memory.
Restricting it to sleepable uprobes and to LOCKDOWN_BPF_WRITE_USER.
Assisted-by: Codex:GPT-6
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
kernel/bpf/helpers.c | 57 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 57 insertions(+)
diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index 648abe1ba63a..4d9e9423826a 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -29,6 +29,7 @@
#include <linux/task_work.h>
#include <linux/irq_work.h>
#include <linux/buildid.h>
+#include <asm/tlb.h>
#include "../../lib/kstrtox.h"
@@ -3738,6 +3739,36 @@ __bpf_kfunc int bpf_copy_from_user_mm(void *dst, u32 dst__sz,
return ret;
}
+/**
+ * bpf_probe_write_user_faultable() - Copy data to userspace with page faults
+ * enabled
+ * @unsafe_dst__ign: Destination address in the current task's address space.
+ * @src__srcptr: Source buffer in BPF-accessible memory.
+ * @src__sz: Number of bytes to copy.
+ *
+ * Copies data to userspace, allowing page faults to resolve the destination
+ * mapping. A failed copy may have written a prefix of the source buffer.
+ *
+ * Return: 0 on success, -EFAULT if the copy is incomplete, or -EPERM if the
+ * current task cannot safely access userspace.
+ */
+__bpf_kfunc int bpf_probe_write_user_faultable(void __user *unsafe_dst__ign,
+ const void *src__srcptr,
+ u32 src__sz)
+{
+ if (unlikely(in_interrupt() ||
+ current->flags & (PF_KTHREAD | PF_EXITING)))
+ return -EPERM;
+ if (unlikely(!nmi_uaccess_okay()))
+ return -EPERM;
+ if (unlikely(!current->mm))
+ return -EFAULT;
+ if (copy_to_user(unsafe_dst__ign, src__srcptr, src__sz))
+ return -EFAULT;
+
+ return 0;
+}
+
/**
* bpf_copy_from_user_mm_str() - Copy a string from an address space
* @dst: Destination address, in kernel space. This buffer must be
@@ -5139,6 +5170,31 @@ 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_probe_write_user_faultable, KF_SLEEPABLE | KF_PERFMON)
+BTF_KFUNCS_END(user_mem_btf_ids)
+
+enum user_mem_kfunc_id {
+ KF_bpf_probe_write_user_faultable,
+};
+
+BTF_ID_LIST_SINGLE(bpf_probe_write_user_faultable_btf_id, func,
+ bpf_probe_write_user_faultable)
+
+static int user_mem_kfunc_filter(const struct bpf_prog *prog, u32 kfunc_id)
+{
+ if (kfunc_id == bpf_probe_write_user_faultable_btf_id[0])
+ return security_locked_down(LOCKDOWN_BPF_WRITE_USER);
+
+ return 0;
+}
+
+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)
@@ -5266,6 +5322,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);
--
2.54.0
^ permalink raw reply related [flat|nested] 19+ messages in thread* Re: [RFC bpf-next 2/9] bpf: Add uprobe faultable user copy kfunc
2026-10-21 19:57 ` [RFC bpf-next 2/9] bpf: Add uprobe faultable user copy kfunc Jiri Olsa
@ 2026-10-21 20:30 ` bot+bpf-ci
0 siblings, 0 replies; 19+ messages in thread
From: bot+bpf-ci @ 2026-10-21 20:30 UTC (permalink / raw)
To: jolsa, ast, daniel
Cc: bpf, andrii, eddyz87, memxor, linux-mm, akpm, liam, ljs,
linux-kselftest, shuah, john.fastabend, ast, andrii, daniel,
martin.lau, eddyz87, yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 3052 bytes --]
> bpf: Add uprobe faultable user copy kfunc
TL;DR:
- New write-to-user kfunc may let CAP_BPF+CAP_PERFMON loaders write user
memory, bypassing the helper's CAP_SYS_ADMIN requirement
- Unused enum (nit)
> diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
> --- a/kernel/bpf/helpers.c
> +++ b/kernel/bpf/helpers.c
[ ... ]
> @@ -5139,6 +5170,31 @@ 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_probe_write_user_faultable, KF_SLEEPABLE | KF_PERFMON)
> +BTF_KFUNCS_END(user_mem_btf_ids)
The new kfunc does the same job as bpf_probe_write_user(), but it is gated
only by KF_PERFMON. The verifier checks that flag as env->allow_ptr_leaks,
which is bpf_token_capable(token, CAP_PERFMON), and loading a
BPF_PROG_TYPE_KPROBE program already requires CAP_PERFMON (see
is_perfmon_prog_type() in syscall.c). For this program type KF_PERFMON
doesn't seem to add any restriction.
The existing helper is gated on CAP_SYS_ADMIN in bpf_tracing_func_proto():
if (!bpf_token_capable(prog->aux->token, CAP_SYS_ADMIN))
return NULL;
switch (func_id) {
case BPF_FUNC_probe_write_user:
return security_locked_down(LOCKDOWN_BPF_WRITE_USER) < 0 ?
NULL : &bpf_probe_write_user_proto;
and include/uapi/linux/capability.h documents "CAP_SYS_ADMIN is required to
use bpf_probe_write_user."
With this patch, can a loader holding only CAP_BPF and CAP_PERFMON write to
user memory through the kfunc even though the helper refuses it?
The commit message carries over the LOCKDOWN_BPF_WRITE_USER restriction from
the helper but says nothing about the capability requirement. Should
user_mem_kfunc_filter() also require
bpf_token_capable(prog->aux->token, CAP_SYS_ADMIN) for this kfunc? If
CAP_PERFMON is meant to be enough, could the changelog and capability.h
explain why?
> +
> +enum user_mem_kfunc_id {
> + KF_bpf_probe_write_user_faultable,
> +};
> +
This isn't a bug, but enum user_mem_kfunc_id and its enumerator
KF_bpf_probe_write_user_faultable don't seem to be referenced anywhere, in
this commit or later in the series. user_mem_kfunc_filter() compares against
bpf_probe_write_user_faultable_btf_id[0] from the BTF_ID_LIST_SINGLE below
instead.
The similar pattern in verifier.c (enum special_kfunc_type) works because it
indexes a BTF_ID_LIST, and there is no such list here. Can the enum be
dropped?
> +BTF_ID_LIST_SINGLE(bpf_probe_write_user_faultable_btf_id, func,
> + bpf_probe_write_user_faultable)
> +
> +static int user_mem_kfunc_filter(const struct bpf_prog *prog, u32 kfunc_id)
> +{
> + if (kfunc_id == bpf_probe_write_user_faultable_btf_id[0])
> + return security_locked_down(LOCKDOWN_BPF_WRITE_USER);
> +
> + return 0;
> +}
[ ... ]
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/39668036298
^ permalink raw reply [flat|nested] 19+ messages in thread
* [RFC bpf-next 3/9] mm: Expose locked mprotect operation
2026-10-21 19:57 [RFC bpf-next 0/9] bpf: Add support to allocate user page Jiri Olsa
2026-10-21 19:57 ` [RFC bpf-next 1/9] bpf: Add __srcptr kfunc argument suffix Jiri Olsa
2026-10-21 19:57 ` [RFC bpf-next 2/9] bpf: Add uprobe faultable user copy kfunc Jiri Olsa
@ 2026-10-21 19:57 ` Jiri Olsa
2026-10-21 20:30 ` bot+bpf-ci
2026-10-22 10:25 ` Lorenzo Stoakes (ARM)
2026-10-21 19:57 ` [RFC bpf-next 4/9] bpf: Add kfuncs to allocate and seal user pages Jiri Olsa
` (6 subsequent siblings)
9 siblings, 2 replies; 19+ messages in thread
From: Jiri Olsa @ 2026-10-21 19:57 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann
Cc: bpf, Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
linux-mm, Andrew Morton, Liam R. Howlett, Lorenzo Stoakes,
linux-kselftest, Shuah Khan, John Fastabend
The bpf_user_page_exec() in following patch needs to change page
protections while holding current->mm's mmap write lock.
Exposing the do_mprotect_pkey_locked helper for that.
Assisted-by: Codex:GPT-6
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
include/linux/mm.h | 3 +++
mm/mprotect.c | 20 +++++++++++++++-----
2 files changed, 18 insertions(+), 5 deletions(-)
diff --git a/include/linux/mm.h b/include/linux/mm.h
index 63f40e615754..47e853093601 100644
--- a/include/linux/mm.h
+++ b/include/linux/mm.h
@@ -3435,6 +3435,9 @@ extern long change_protection(struct mmu_gather *tlb,
extern int mprotect_fixup(struct vma_iterator *vmi, struct mmu_gather *tlb,
struct vm_area_struct *vma, struct vm_area_struct **pprev,
unsigned long start, unsigned long end, vm_flags_t newflags);
+/* Caller must hold current->mm's mmap write lock. */
+int do_mprotect_pkey_locked(unsigned long start, size_t len,
+ unsigned long prot, int pkey);
/*
* doesn't attempt to fault and will return short.
diff --git a/mm/mprotect.c b/mm/mprotect.c
index 2888ee638d87..8d800830d3e1 100644
--- a/mm/mprotect.c
+++ b/mm/mprotect.c
@@ -868,8 +868,8 @@ mprotect_fixup(struct vma_iterator *vmi, struct mmu_gather *tlb,
/*
* pkey==-1 when doing a legacy mprotect()
*/
-static int do_mprotect_pkey(unsigned long start, size_t len,
- unsigned long prot, int pkey)
+int do_mprotect_pkey_locked(unsigned long start, size_t len,
+ unsigned long prot, int pkey)
{
unsigned long nstart, end, tmp, reqprot;
struct vm_area_struct *vma, *prev;
@@ -899,8 +899,7 @@ static int do_mprotect_pkey(unsigned long start, size_t len,
reqprot = prot;
- if (mmap_write_lock_killable(current->mm))
- return -EINTR;
+ mmap_assert_write_locked(current->mm);
/*
* If userspace did not allocate the pkey, do not let
@@ -1013,10 +1012,21 @@ static int do_mprotect_pkey(unsigned long start, size_t len,
error = -ENOMEM;
out:
- mmap_write_unlock(current->mm);
return error;
}
+static int do_mprotect_pkey(unsigned long start, size_t len,
+ unsigned long prot, int pkey)
+{
+ int ret;
+
+ if (mmap_write_lock_killable(current->mm))
+ return -EINTR;
+ ret = do_mprotect_pkey_locked(start, len, prot, pkey);
+ mmap_write_unlock(current->mm);
+ return ret;
+}
+
SYSCALL_DEFINE3(mprotect, unsigned long, start, size_t, len,
unsigned long, prot)
{
--
2.54.0
^ permalink raw reply related [flat|nested] 19+ messages in thread* Re: [RFC bpf-next 3/9] mm: Expose locked mprotect operation
2026-10-21 19:57 ` [RFC bpf-next 3/9] mm: Expose locked mprotect operation Jiri Olsa
@ 2026-10-21 20:30 ` bot+bpf-ci
2026-10-22 10:25 ` Lorenzo Stoakes (ARM)
1 sibling, 0 replies; 19+ messages in thread
From: bot+bpf-ci @ 2026-10-21 20:30 UTC (permalink / raw)
To: jolsa, ast, daniel
Cc: bpf, andrii, eddyz87, memxor, linux-mm, akpm, liam, ljs,
linux-kselftest, shuah, john.fastabend, ast, andrii, daniel,
martin.lau, eddyz87, yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 2355 bytes --]
> mm: Expose locked mprotect operation
TL;DR: do_mprotect_pkey_locked() has no !CONFIG_MMU stub, so once the next
patch calls it, CONFIG_MMU=n with CONFIG_BPF_SYSCALL=y kernels may fail to
link.
> diff --git a/include/linux/mm.h b/include/linux/mm.h
> index 63f40e615754..47e853093601 100644
> --- a/include/linux/mm.h
> +++ b/include/linux/mm.h
> @@ -3435,6 +3435,9 @@ extern long change_protection(struct mmu_gather *tlb,
> extern int mprotect_fixup(struct vma_iterator *vmi, struct mmu_gather *tlb,
> struct vm_area_struct *vma, struct vm_area_struct **pprev,
> unsigned long start, unsigned long end, vm_flags_t newflags);
> +/* Caller must hold current->mm's mmap write lock. */
> +int do_mprotect_pkey_locked(unsigned long start, size_t len,
> + unsigned long prot, int pkey);
Can this declaration lead to an undefined reference on kernels built with
CONFIG_MMU=n and CONFIG_BPF_SYSCALL=y?
The prototype is unconditional and there is no !CONFIG_MMU stub, but the only
definition is in mm/mprotect.c, which is built only for MMU kernels:
mm/Makefile:
mmu-$(CONFIG_MMU) := ... mprotect.o ...
The next patch in the series, f62943356e31 ("bpf: Add kfuncs to allocate and
seal user pages"), calls the helper from bpf_user_page_exec() in
kernel/bpf/helpers.c with no CONFIG_MMU guard:
err = do_mprotect_pkey_locked(addr, PAGE_SIZE, PROT_READ | PROT_EXEC, -1);
helpers.o is built for any CONFIG_BPF_SYSCALL=y kernel, and BPF_SYSCALL has
no MMU dependency in kernel/bpf/Kconfig. Builds without an MMU do appear to
be supported for bpf: arena.o is explicitly limited to
"ifeq ($(CONFIG_MMU)$(CONFIG_64BIT),yy)", and ced6a0993e2f fixed a
!MMU/32-bit bpf build break.
So once f62943356e31 is applied, would a CONFIG_MMU=n, CONFIG_BPF_SYSCALL=y
kernel fail to link? This patch builds on its own, and nothing up to the end
of the series (1b0c19c85c7f) appears to add a fix.
Would it make sense to either declare the helper under "#ifdef CONFIG_MMU"
with a static inline stub that returns an error, or add a CONFIG_MMU guard
around the kfunc and its BTF_ID_FLAGS entry in the bpf patch?
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/39668036298
^ permalink raw reply [flat|nested] 19+ messages in thread* Re: [RFC bpf-next 3/9] mm: Expose locked mprotect operation
2026-10-21 19:57 ` [RFC bpf-next 3/9] mm: Expose locked mprotect operation Jiri Olsa
2026-10-21 20:30 ` bot+bpf-ci
@ 2026-10-22 10:25 ` Lorenzo Stoakes (ARM)
2026-10-22 19:39 ` Jiri Olsa
1 sibling, 1 reply; 19+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-10-22 10:25 UTC (permalink / raw)
To: Jiri Olsa
Cc: Alexei Starovoitov, Daniel Borkmann, bpf, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, linux-mm,
Andrew Morton, Liam R. Howlett, linux-kselftest, Shuah Khan,
John Fastabend
On Wed, Oct 21, 2026 at 09:57:52PM +0200, Jiri Olsa wrote:
> The bpf_user_page_exec() in following patch needs to change page
> protections while holding current->mm's mmap write lock.
>
> Exposing the do_mprotect_pkey_locked helper for that.
Err, we'll need some more justification than this for exposing internal mm
logic to the rest of the kernel please.
Note that you're already allowing any caller to bypass
CONFIG_ARCH_HAS_PKEYS here which is immediately a broken thing.
>
> Assisted-by: Codex:GPT-6
> Signed-off-by: Jiri Olsa <jolsa@kernel.org>
It's a no to this in its current form.
> ---
> include/linux/mm.h | 3 +++
> mm/mprotect.c | 20 +++++++++++++++-----
> 2 files changed, 18 insertions(+), 5 deletions(-)
>
> diff --git a/include/linux/mm.h b/include/linux/mm.h
> index 63f40e615754..47e853093601 100644
> --- a/include/linux/mm.h
> +++ b/include/linux/mm.h
> @@ -3435,6 +3435,9 @@ extern long change_protection(struct mmu_gather *tlb,
> extern int mprotect_fixup(struct vma_iterator *vmi, struct mmu_gather *tlb,
> struct vm_area_struct *vma, struct vm_area_struct **pprev,
> unsigned long start, unsigned long end, vm_flags_t newflags);
> +/* Caller must hold current->mm's mmap write lock. */
> +int do_mprotect_pkey_locked(unsigned long start, size_t len,
> + unsigned long prot, int pkey);
>
> /*
> * doesn't attempt to fault and will return short.
> diff --git a/mm/mprotect.c b/mm/mprotect.c
> index 2888ee638d87..8d800830d3e1 100644
> --- a/mm/mprotect.c
> +++ b/mm/mprotect.c
> @@ -868,8 +868,8 @@ mprotect_fixup(struct vma_iterator *vmi, struct mmu_gather *tlb,
> /*
> * pkey==-1 when doing a legacy mprotect()
> */
> -static int do_mprotect_pkey(unsigned long start, size_t len,
> - unsigned long prot, int pkey)
> +int do_mprotect_pkey_locked(unsigned long start, size_t len,
> + unsigned long prot, int pkey)
> {
> unsigned long nstart, end, tmp, reqprot;
> struct vm_area_struct *vma, *prev;
> @@ -899,8 +899,7 @@ static int do_mprotect_pkey(unsigned long start, size_t len,
>
> reqprot = prot;
>
> - if (mmap_write_lock_killable(current->mm))
> - return -EINTR;
> + mmap_assert_write_locked(current->mm);
>
> /*
> * If userspace did not allocate the pkey, do not let
> @@ -1013,10 +1012,21 @@ static int do_mprotect_pkey(unsigned long start, size_t len,
> error = -ENOMEM;
>
> out:
> - mmap_write_unlock(current->mm);
> return error;
> }
>
> +static int do_mprotect_pkey(unsigned long start, size_t len,
> + unsigned long prot, int pkey)
> +{
> + int ret;
> +
> + if (mmap_write_lock_killable(current->mm))
> + return -EINTR;
> + ret = do_mprotect_pkey_locked(start, len, prot, pkey);
> + mmap_write_unlock(current->mm);
> + return ret;
> +}
> +
> SYSCALL_DEFINE3(mprotect, unsigned long, start, size_t, len,
> unsigned long, prot)
> {
> --
> 2.54.0
>
--
Cheers, Lorenzo
^ permalink raw reply [flat|nested] 19+ messages in thread* Re: [RFC bpf-next 3/9] mm: Expose locked mprotect operation
2026-10-22 10:25 ` Lorenzo Stoakes (ARM)
@ 2026-10-22 19:39 ` Jiri Olsa
0 siblings, 0 replies; 19+ messages in thread
From: Jiri Olsa @ 2026-10-22 19:39 UTC (permalink / raw)
To: Lorenzo Stoakes (ARM)
Cc: Alexei Starovoitov, Daniel Borkmann, bpf, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, linux-mm,
Andrew Morton, Liam R. Howlett, linux-kselftest, Shuah Khan,
John Fastabend
On Thu, Oct 22, 2026 at 11:25:31AM +0100, Lorenzo Stoakes (ARM) wrote:
> On Wed, Oct 21, 2026 at 09:57:52PM +0200, Jiri Olsa wrote:
> > The bpf_user_page_exec() in following patch needs to change page
> > protections while holding current->mm's mmap write lock.
> >
> > Exposing the do_mprotect_pkey_locked helper for that.
>
> Err, we'll need some more justification than this for exposing internal mm
> logic to the rest of the kernel please.
>
> Note that you're already allowing any caller to bypass
> CONFIG_ARCH_HAS_PKEYS here which is immediately a broken thing.
>
> >
> > Assisted-by: Codex:GPT-6
> > Signed-off-by: Jiri Olsa <jolsa@kernel.org>
>
> It's a no to this in its current form.
yes I suspected it'd be a problem, but as you mentioned
in the other reply, Alexei's proposal takes care of that
thanks,
jirka
>
> > ---
> > include/linux/mm.h | 3 +++
> > mm/mprotect.c | 20 +++++++++++++++-----
> > 2 files changed, 18 insertions(+), 5 deletions(-)
> >
> > diff --git a/include/linux/mm.h b/include/linux/mm.h
> > index 63f40e615754..47e853093601 100644
> > --- a/include/linux/mm.h
> > +++ b/include/linux/mm.h
> > @@ -3435,6 +3435,9 @@ extern long change_protection(struct mmu_gather *tlb,
> > extern int mprotect_fixup(struct vma_iterator *vmi, struct mmu_gather *tlb,
> > struct vm_area_struct *vma, struct vm_area_struct **pprev,
> > unsigned long start, unsigned long end, vm_flags_t newflags);
> > +/* Caller must hold current->mm's mmap write lock. */
> > +int do_mprotect_pkey_locked(unsigned long start, size_t len,
> > + unsigned long prot, int pkey);
> >
> > /*
> > * doesn't attempt to fault and will return short.
> > diff --git a/mm/mprotect.c b/mm/mprotect.c
> > index 2888ee638d87..8d800830d3e1 100644
> > --- a/mm/mprotect.c
> > +++ b/mm/mprotect.c
> > @@ -868,8 +868,8 @@ mprotect_fixup(struct vma_iterator *vmi, struct mmu_gather *tlb,
> > /*
> > * pkey==-1 when doing a legacy mprotect()
> > */
> > -static int do_mprotect_pkey(unsigned long start, size_t len,
> > - unsigned long prot, int pkey)
> > +int do_mprotect_pkey_locked(unsigned long start, size_t len,
> > + unsigned long prot, int pkey)
> > {
> > unsigned long nstart, end, tmp, reqprot;
> > struct vm_area_struct *vma, *prev;
> > @@ -899,8 +899,7 @@ static int do_mprotect_pkey(unsigned long start, size_t len,
> >
> > reqprot = prot;
> >
> > - if (mmap_write_lock_killable(current->mm))
> > - return -EINTR;
> > + mmap_assert_write_locked(current->mm);
> >
> > /*
> > * If userspace did not allocate the pkey, do not let
> > @@ -1013,10 +1012,21 @@ static int do_mprotect_pkey(unsigned long start, size_t len,
> > error = -ENOMEM;
> >
> > out:
> > - mmap_write_unlock(current->mm);
> > return error;
> > }
> >
> > +static int do_mprotect_pkey(unsigned long start, size_t len,
> > + unsigned long prot, int pkey)
> > +{
> > + int ret;
> > +
> > + if (mmap_write_lock_killable(current->mm))
> > + return -EINTR;
> > + ret = do_mprotect_pkey_locked(start, len, prot, pkey);
> > + mmap_write_unlock(current->mm);
> > + return ret;
> > +}
> > +
> > SYSCALL_DEFINE3(mprotect, unsigned long, start, size_t, len,
> > unsigned long, prot)
> > {
> > --
> > 2.54.0
> >
>
> --
> Cheers, Lorenzo
^ permalink raw reply [flat|nested] 19+ messages in thread
* [RFC bpf-next 4/9] bpf: Add kfuncs to allocate and seal user pages
2026-10-21 19:57 [RFC bpf-next 0/9] bpf: Add support to allocate user page Jiri Olsa
` (2 preceding siblings ...)
2026-10-21 19:57 ` [RFC bpf-next 3/9] mm: Expose locked mprotect operation Jiri Olsa
@ 2026-10-21 19:57 ` Jiri Olsa
2026-10-21 20:41 ` bot+bpf-ci
2026-10-21 19:57 ` [RFC bpf-next 5/9] selftests/bpf: Add __srcptr kfunc arguments test Jiri Olsa
` (5 subsequent siblings)
9 siblings, 1 reply; 19+ messages in thread
From: Jiri Olsa @ 2026-10-21 19:57 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann
Cc: bpf, Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
linux-mm, Andrew Morton, Liam R. Howlett, Lorenzo Stoakes,
linux-kselftest, Shuah Khan, John Fastabend
Adding sleepable BPF kfuncs for allocating anonymous pages
in the current process and changing an allocated page to
read/execute permissions. The mapping remains in userspace
until it is unmapped or the process exits.
It's allowed only for sleepable kprobe type, which technicaly
means only sleepable uprobe.
Assisted-by: Codex:GPT-6
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
kernel/bpf/helpers.c | 127 +++++++++++++++++++++++++++++++++++++++++++
1 file changed, 127 insertions(+)
diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index 4d9e9423826a..246f4969a144 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -29,6 +29,10 @@
#include <linux/task_work.h>
#include <linux/irq_work.h>
#include <linux/buildid.h>
+#include <linux/mm.h>
+#include <linux/mman.h>
+#include <linux/personality.h>
+#include <asm/cacheflush.h>
#include <asm/tlb.h>
#include "../../lib/kstrtox.h"
@@ -3739,6 +3743,127 @@ __bpf_kfunc int bpf_copy_from_user_mm(void *dst, u32 dst__sz,
return ret;
}
+/* vm_private_data token for the unmergeable anonymous mapping below. */
+static char bpf_user_page_marker;
+
+/**
+ * bpf_user_page_alloc() - Allocate lazy anonymous pages in the current mm
+ * @nr_pages: Number of pages to allocate
+ *
+ * Creates a read/write anonymous mapping in the current task's address space.
+ * The mapping is kept non-executable even for tasks with READ_IMPLIES_EXEC,
+ * to avoid creating a writable executable mapping. The physical page is
+ * allocated on its first access. The mapping remains until userspace unmaps
+ * it or the process exits.
+ *
+ * Return: The userspace address on success, or a negative error code.
+ */
+__bpf_kfunc long bpf_user_page_alloc(unsigned long nr_pages)
+{
+ const unsigned long prot = PROT_READ | PROT_WRITE;
+ const unsigned long flags = MAP_PRIVATE | MAP_ANONYMOUS;
+ struct mm_struct *mm = current->mm;
+ unsigned long addr, populate, len;
+ struct vm_area_struct *vma;
+ int err;
+
+ if (unlikely(in_interrupt() || current->flags & (PF_KTHREAD | PF_EXITING)))
+ return -EPERM;
+ if (unlikely(!nmi_uaccess_okay()))
+ return -EPERM;
+ if (unlikely(!mm))
+ return -EFAULT;
+ if (unlikely(!nr_pages || nr_pages > (ULONG_MAX >> PAGE_SHIFT)))
+ return -EINVAL;
+
+ len = nr_pages << PAGE_SHIFT;
+ err = security_mmap_file(NULL, prot, flags);
+ if (err)
+ return err;
+
+ if (mmap_write_lock_killable(mm))
+ return -EINTR;
+
+ addr = do_mmap(NULL, 0, len, prot, flags,
+ mk_vma_flags(VMA_DONTEXPAND_BIT, VMA_DONTCOPY_BIT, VMA_DONTDUMP_BIT),
+ 0, &populate, NULL);
+ if (!IS_ERR_VALUE(addr)) {
+ /* VM_DONTEXPAND prevents merging this mapping into another VMA. */
+ vma = find_vma(mm, addr);
+ vma_start_write(vma);
+ if (current->personality & READ_IMPLIES_EXEC) {
+ vm_flags_clear(vma, VM_EXEC);
+ vma_set_page_prot(vma);
+ }
+ vma->vm_private_data = &bpf_user_page_marker;
+ }
+
+ mmap_write_unlock(mm);
+
+ if (!IS_ERR_VALUE(addr) && populate)
+ mm_populate(addr, populate);
+ return addr;
+}
+
+/**
+ * bpf_user_page_exec() - Make a BPF-allocated user page read/execute
+ * @addr: Address of the page to change
+ *
+ * Changes a writable page created by bpf_user_page_alloc() to read/execute.
+ * The page cannot be made writable again after this call.
+ *
+ * Return: 0 on success, or a negative error code.
+ */
+__bpf_kfunc int bpf_user_page_exec(unsigned long addr)
+{
+ struct mm_struct *mm = current->mm;
+ struct vm_area_struct *vma;
+ struct page *page;
+ int err = -EINVAL;
+
+ if (unlikely(in_interrupt() ||
+ current->flags & (PF_KTHREAD | PF_EXITING)))
+ return -EPERM;
+ if (unlikely(!nmi_uaccess_okay()))
+ return -EPERM;
+ if (unlikely(!mm))
+ return -EFAULT;
+ if ((addr & ~PAGE_MASK) || addr > ULONG_MAX - PAGE_SIZE)
+ return -EINVAL;
+
+ if (mmap_write_lock_killable(mm))
+ return -EINTR;
+
+ vma = find_vma(mm, addr);
+ if (!vma || vma->vm_start > addr)
+ goto out;
+ /* Allow to change only vma allocated by bpf_user_page_alloc. */
+ if (vma->vm_private_data != &bpf_user_page_marker)
+ goto out;
+ if (!(vma->vm_flags & VM_WRITE) || !(vma->vm_flags & VM_MAYEXEC))
+ goto out;
+
+ vma_start_write(vma);
+
+ err = get_user_pages(addr, 1, FOLL_WRITE, &page);
+ if (err != 1) {
+ if (!err)
+ err = -EFAULT;
+ goto out;
+ }
+
+ flush_icache_user_page(vma, page, addr, PAGE_SIZE);
+ err = do_mprotect_pkey_locked(addr, PAGE_SIZE, PROT_READ | PROT_EXEC, -1);
+ if (!err) {
+ vma = find_vma(mm, addr);
+ vm_flags_clear(vma, VM_MAYWRITE);
+ }
+ put_page(page);
+out:
+ mmap_write_unlock(mm);
+ return err;
+}
+
/**
* bpf_probe_write_user_faultable() - Copy data to userspace with page faults
* enabled
@@ -5171,6 +5296,8 @@ static const struct btf_kfunc_id_set generic_kfunc_set = {
};
BTF_KFUNCS_START(user_mem_btf_ids)
+BTF_ID_FLAGS(func, bpf_user_page_alloc, KF_SLEEPABLE | KF_PERFMON)
+BTF_ID_FLAGS(func, bpf_user_page_exec, KF_SLEEPABLE | KF_PERFMON)
BTF_ID_FLAGS(func, bpf_probe_write_user_faultable, KF_SLEEPABLE | KF_PERFMON)
BTF_KFUNCS_END(user_mem_btf_ids)
--
2.54.0
^ permalink raw reply related [flat|nested] 19+ messages in thread* Re: [RFC bpf-next 4/9] bpf: Add kfuncs to allocate and seal user pages
2026-10-21 19:57 ` [RFC bpf-next 4/9] bpf: Add kfuncs to allocate and seal user pages Jiri Olsa
@ 2026-10-21 20:41 ` bot+bpf-ci
0 siblings, 0 replies; 19+ messages in thread
From: bot+bpf-ci @ 2026-10-21 20:41 UTC (permalink / raw)
To: jolsa, ast, daniel
Cc: bpf, andrii, eddyz87, memxor, linux-mm, akpm, liam, ljs,
linux-kselftest, shuah, john.fastabend, ast, andrii, daniel,
martin.lau, eddyz87, yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 3989 bytes --]
> bpf: Add kfuncs to allocate and seal user pages
TL;DR: bpf_user_page_exec() may skip the icache flush on arm64/riscv, since
flush_icache_user_page() runs before VM_EXEC is set, so with THP a task could
execute stale instructions.
> diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
> index 4d9e9423826a..246f4969a144 100644
> --- a/kernel/bpf/helpers.c
> +++ b/kernel/bpf/helpers.c
[ ... ]
> @@ -3739,6 +3743,127 @@ __bpf_kfunc int bpf_copy_from_user_mm(void *dst, u32 dst__sz,
[ ... ]
> +__bpf_kfunc int bpf_user_page_exec(unsigned long addr)
> +{
[ ... ]
> + err = get_user_pages(addr, 1, FOLL_WRITE, &page);
[ ... ]
> + flush_icache_user_page(vma, page, addr, PAGE_SIZE);
> + err = do_mprotect_pkey_locked(addr, PAGE_SIZE, PROT_READ | PROT_EXEC, -1);
Does flush_icache_user_page() do anything here on the architectures that
need it?
At this point the VMA is still the read/write mapping created by
bpf_user_page_alloc(), so VM_EXEC is not set yet. On riscv the macro is:
#define flush_icache_user_page(vma, pg, addr, len) \
do { \
if (vma->vm_flags & VM_EXEC) \
flush_icache_mm(vma->vm_mm, 0); \
} while (0)
so it is skipped, and VM_EXEC only gets set by the
do_mprotect_pkey_locked() call on the next line. arm64 does not define
flush_icache_user_page(), so it falls back to the empty stub in
include/asm-generic/cacheflush.h.
That leaves only the set_ptes() hook that runs when change_pte_range()
installs the read/execute PTE. On arm64 it is
__sync_cache_and_tags()->__sync_icache_dcache(), and on riscv it is
__set_pte_at()->flush_icache_pte(). Both only do cache maintenance when
PG_dcache_clean is clear on the folio, and then set it for the whole folio:
if (!test_bit(PG_dcache_clean, &folio->flags.f)) {
sync_icache_aliases(folio_address(folio),
folio_address(folio) + folio_size(folio));
set_bit(PG_dcache_clean, &folio->flags.f);
}
The BPF program writes the instructions through the user address with
bpf_probe_write_user_faultable(), which uses copy_to_user() and never calls
flush_dcache_folio(), so PG_dcache_clean is not cleared again after the
first exec mapping of the folio.
With THP enabled (arm64 or riscv, 4K pages), can this sequence happen?
bpf_user_page_alloc(512) returns a PMD-aligned address
the program writes page 0, and the fault maps a 2MB anonymous THP
bpf_user_page_exec(addr) splits the VMA and the PMD, the read/execute
set_ptes() for page 0 syncs the whole 2MB folio and sets PG_dcache_clean
the program writes code into page 1, which is still read/write
bpf_user_page_exec(addr + PAGE_SIZE) calls flush_icache_user_page() again
as a no-op, and the read/execute set_ptes() sees PG_dcache_clean set and
skips maintenance
The task would then execute page 1 without a D-cache clean to the point of
unification and I-cache invalidate (arm64 without CTR_EL0.IDC/DIC), or
without fence.i (riscv), and could fetch stale instructions. mTHP sizes
give the same result for smaller allocations.
The uprobe XOL code, which also writes instructions for user space, does
explicit maintenance through the kernel alias: arm64 arch_uprobe_copy_ixol()
calls sync_icache_aliases(), and riscv, loongarch and mips call
flush_icache_range() on the kmap address.
Should bpf_user_page_exec() do the same, for example with
kmap_local_page(page) followed by flush_icache_range()? Alternatively,
would calling flush_dcache_page(page) before the mprotect, so the arch
set_ptes() hook redoes the sync, be enough? Either way the flush would no
longer depend on VM_EXEC being set at the time of the call.
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/39668036298
^ permalink raw reply [flat|nested] 19+ messages in thread
* [RFC bpf-next 5/9] selftests/bpf: Add __srcptr kfunc arguments test
2026-10-21 19:57 [RFC bpf-next 0/9] bpf: Add support to allocate user page Jiri Olsa
` (3 preceding siblings ...)
2026-10-21 19:57 ` [RFC bpf-next 4/9] bpf: Add kfuncs to allocate and seal user pages Jiri Olsa
@ 2026-10-21 19:57 ` Jiri Olsa
2026-10-21 19:57 ` [RFC bpf-next 6/9] selftests/bpf: Add user page allocation test Jiri Olsa
` (4 subsequent siblings)
9 siblings, 0 replies; 19+ messages in thread
From: Jiri Olsa @ 2026-10-21 19:57 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann
Cc: bpf, Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
linux-mm, Andrew Morton, Liam R. Howlett, Lorenzo Stoakes,
linux-kselftest, Shuah Khan, John Fastabend
Add positive kfunc tests for read-only global and writable stack sources
annotated with __srcptr. Also verify that a read-only source is rejected
when passed to a writable mem_len argument.
Assisted-by: Codex:GPT-6
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
tools/testing/selftests/bpf/prog_tests/kfunc_call.c | 2 ++
tools/testing/selftests/bpf/progs/kfunc_call_fail.c | 10 ++++++++++
tools/testing/selftests/bpf/progs/kfunc_call_test.c | 12 ++++++++++++
tools/testing/selftests/bpf/test_kmods/bpf_testmod.c | 5 +++++
.../selftests/bpf/test_kmods/bpf_testmod_kfunc.h | 1 +
5 files changed, 30 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/kfunc_call.c b/tools/testing/selftests/bpf/prog_tests/kfunc_call.c
index 0063e60d6f2f..9b832c5961f5 100644
--- a/tools/testing/selftests/bpf/prog_tests/kfunc_call.c
+++ b/tools/testing/selftests/bpf/prog_tests/kfunc_call.c
@@ -63,6 +63,7 @@ static struct kfunc_test_params kfunc_tests[] = {
*/
SYSCALL_NULL_CTX_FAIL(kfunc_syscall_test_fail, -EINVAL, "processed 4 insns"),
SYSCALL_NULL_CTX_FAIL(kfunc_syscall_test_null_fail, -EINVAL, "processed 4 insns"),
+ TC_FAIL(kfunc_call_test_mem_len_fail_readonly, 0, "write into map forbidden"),
TC_FAIL(kfunc_call_test_get_mem_fail_rdonly, 0, "R0 cannot write into rdonly_mem"),
TC_FAIL(kfunc_call_test_get_mem_fail_use_after_free, 0, "invalid mem access 'scalar'"),
TC_FAIL(kfunc_call_test_get_mem_fail_oob, 0, "min value is outside of the allowed memory range"),
@@ -82,6 +83,7 @@ static struct kfunc_test_params kfunc_tests[] = {
TC_TEST(kfunc_call_test5_asm, 0),
TC_TEST(kfunc_call_test_ref_btf_id, 0),
TC_TEST(kfunc_call_test_get_mem, 42),
+ TC_TEST(kfunc_call_test_srcptr, 0),
SYSCALL_TEST(kfunc_syscall_test, 0),
SYSCALL_NULL_CTX_TEST(kfunc_syscall_test_null, 0),
TC_TEST(kfunc_call_test_static_unused_arg, 0),
diff --git a/tools/testing/selftests/bpf/progs/kfunc_call_fail.c b/tools/testing/selftests/bpf/progs/kfunc_call_fail.c
index 7e93f7fb1329..1ab85faaf8db 100644
--- a/tools/testing/selftests/bpf/progs/kfunc_call_fail.c
+++ b/tools/testing/selftests/bpf/progs/kfunc_call_fail.c
@@ -4,6 +4,8 @@
#include <bpf/bpf_helpers.h>
#include "../test_kmods/bpf_testmod_kfunc.h"
+static const char mem_len_rodata[] = "read-only data";
+
static struct bpf_spin_lock kfunc_call_lock SEC(".data.A");
SEC("?tc")
@@ -47,6 +49,14 @@ int kfunc_syscall_test_null_fail(struct syscall_test_args *args)
return 0;
}
+SEC("?tc")
+int kfunc_call_test_mem_len_fail_readonly(struct __sk_buff *skb)
+{
+ bpf_kfunc_call_test_mem_len_pass1((void *)mem_len_rodata,
+ sizeof(mem_len_rodata));
+ return 0;
+}
+
SEC("?tc")
int kfunc_call_test_get_mem_fail_rdonly(struct __sk_buff *skb)
{
diff --git a/tools/testing/selftests/bpf/progs/kfunc_call_test.c b/tools/testing/selftests/bpf/progs/kfunc_call_test.c
index 8e6560c31e78..b944e2e186b6 100644
--- a/tools/testing/selftests/bpf/progs/kfunc_call_test.c
+++ b/tools/testing/selftests/bpf/progs/kfunc_call_test.c
@@ -5,6 +5,8 @@
#include "bpf_misc.h"
#include "../test_kmods/bpf_testmod_kfunc.h"
+static const char srcptr_rodata[] = "read-only source";
+
static struct bpf_spin_lock kfunc_call_lock SEC(".data.A");
SEC("tc")
@@ -208,6 +210,16 @@ int kfunc_call_test_pass(struct __sk_buff *skb)
return 0;
}
+SEC("tc")
+int kfunc_call_test_srcptr(struct __sk_buff *skb)
+{
+ char stack_data[] = "read-write source";
+
+ bpf_kfunc_call_test_srcptr(srcptr_rodata, sizeof(srcptr_rodata));
+ bpf_kfunc_call_test_srcptr(stack_data, sizeof(stack_data));
+ return 0;
+}
+
struct syscall_test_args {
__u8 data[16];
size_t size;
diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
index 6380f52f4dd2..5d72f94a8912 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
@@ -1024,6 +1024,7 @@ BTF_ID_FLAGS(func, bpf_kfunc_arena_args5_test)
BTF_ID_FLAGS(func, bpf_kfunc_arena_stack_arg_test)
BTF_ID_FLAGS(func, bpf_kfunc_arena_mixed_test)
BTF_ID_FLAGS(func, bpf_kfunc_call_test_mem_len_pass1)
+BTF_ID_FLAGS(func, bpf_kfunc_call_test_srcptr)
BTF_ID_FLAGS(func, bpf_kfunc_dynptr_test)
BTF_ID_FLAGS(func, bpf_kfunc_nested_acquire_nonzero_offset_test, KF_ACQUIRE)
BTF_ID_FLAGS(func, bpf_kfunc_nested_acquire_zero_offset_test, KF_ACQUIRE)
@@ -1527,6 +1528,10 @@ __bpf_kfunc void bpf_kfunc_call_test_mem_len_pass1(void *mem, int mem__sz)
{
}
+__bpf_kfunc void bpf_kfunc_call_test_srcptr(const void *src__srcptr, int src__sz)
+{
+}
+
__bpf_kfunc void bpf_kfunc_call_test_mem_len_fail1(void *mem, int len)
{
}
diff --git a/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h b/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h
index 3b810f1e438f..919c4c450ef1 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h
@@ -198,6 +198,7 @@ void bpf_kfunc_call_test_release(struct prog_test_ref_kfunc *p) __ksym;
void bpf_kfunc_call_test_ref(struct prog_test_ref_kfunc *p) __ksym;
void bpf_kfunc_call_test_mem_len_pass1(void *mem, int len) __ksym;
+void bpf_kfunc_call_test_srcptr(const void *src__srcptr, int src__sz) __ksym;
__u64 bpf_kfunc_arena_arg_test(__u64 *val__arena) __ksym;
__u64 bpf_kfunc_arena_cap_test(__u64 *val__arena) __ksym;
__u64 bpf_kfunc_arena_cap_nullable_test(__u64 *val__arena__nullable) __ksym;
--
2.54.0
^ permalink raw reply related [flat|nested] 19+ messages in thread* [RFC bpf-next 6/9] selftests/bpf: Add user page allocation test
2026-10-21 19:57 [RFC bpf-next 0/9] bpf: Add support to allocate user page Jiri Olsa
` (4 preceding siblings ...)
2026-10-21 19:57 ` [RFC bpf-next 5/9] selftests/bpf: Add __srcptr kfunc arguments test Jiri Olsa
@ 2026-10-21 19:57 ` Jiri Olsa
2026-10-21 19:57 ` [RFC bpf-next 7/9] selftests/bpf: Add uprobe user page exec test Jiri Olsa
` (3 subsequent siblings)
9 siblings, 0 replies; 19+ messages in thread
From: Jiri Olsa @ 2026-10-21 19:57 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann
Cc: bpf, Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
linux-mm, Andrew Morton, Liam R. Howlett, Lorenzo Stoakes,
linux-kselftest, Shuah Khan, John Fastabend
Check that consecutive allocations remain separate VMAs and
that user page can be allocated again after unmapping.
Assisted-by: Codex:GPT-6
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
.../selftests/bpf/prog_tests/user_page.c | 152 ++++++++++++++++++
tools/testing/selftests/bpf/progs/user_page.c | 31 ++++
2 files changed, 183 insertions(+)
create mode 100644 tools/testing/selftests/bpf/prog_tests/user_page.c
create mode 100644 tools/testing/selftests/bpf/progs/user_page.c
diff --git a/tools/testing/selftests/bpf/prog_tests/user_page.c b/tools/testing/selftests/bpf/prog_tests/user_page.c
new file mode 100644
index 000000000000..ff51e7c598a9
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/user_page.c
@@ -0,0 +1,152 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <test_progs.h>
+#include <sys/mman.h>
+#include <sys/personality.h>
+#include <unistd.h>
+
+#include "user_page.skel.h"
+
+enum {
+ USER_PAGE_ALLOC = 1,
+};
+
+static noinline void user_page_trigger(void)
+{
+ asm volatile ("");
+}
+
+static bool check_user_page_vma(void *addr, long size, const char *perms)
+{
+ unsigned long start, end;
+ bool found = false;
+ char vma_perms[5];
+ char line[512];
+ FILE *maps;
+
+ maps = fopen("/proc/self/maps", "r");
+ if (!ASSERT_OK_PTR(maps, "open_maps"))
+ return false;
+
+ while (fgets(line, sizeof(line), maps)) {
+ if (sscanf(line, "%lx-%lx %4s", &start, &end, vma_perms) != 3 ||
+ (unsigned long)addr < start || (unsigned long)addr >= end)
+ continue;
+ found = ASSERT_EQ(start, (unsigned long)addr, "vma_start") &&
+ ASSERT_EQ(end - start, size, "vma_size") &&
+ (!perms || ASSERT_STREQ(vma_perms, perms, "vma_perms"));
+ break;
+ }
+ fclose(maps);
+ return ASSERT_TRUE(found, "user_page_vma");
+}
+
+static bool alloc_user_pages(struct user_page *skel, unsigned long nr_pages,
+ long *addr)
+{
+ skel->bss->nr_pages = nr_pages;
+ skel->bss->action = USER_PAGE_ALLOC;
+ user_page_trigger();
+ if (!ASSERT_GE(skel->bss->user_addr, 0, "user_addr"))
+ return false;
+
+ *addr = skel->bss->user_addr;
+ return true;
+}
+
+static void test_allocations(struct user_page *skel)
+{
+ void *pages[2] = { MAP_FAILED, MAP_FAILED };
+ long page_size = sysconf(_SC_PAGESIZE);
+ const unsigned long nr_pages = 3;
+ long addr;
+ int i;
+
+ for (i = 0; i < ARRAY_SIZE(pages); i++) {
+ if (!alloc_user_pages(skel, nr_pages, &addr))
+ goto out;
+ pages[i] = (void *)addr;
+ }
+
+ /* Back-to-back allocations must remain individual VMAs. */
+ for (i = 0; i < ARRAY_SIZE(pages); i++) {
+ if (!check_user_page_vma(pages[i], page_size * nr_pages, NULL))
+ break;
+ }
+
+out:
+ for (i = 0; i < ARRAY_SIZE(pages); i++)
+ if (pages[i] != MAP_FAILED)
+ munmap(pages[i], page_size * nr_pages);
+}
+
+static void test_reallocate_after_unmap(struct user_page *skel)
+{
+ long page_size = sysconf(_SC_PAGESIZE);
+ long addr = -1;
+
+ if (!alloc_user_pages(skel, 1, &addr))
+ goto out;
+
+ if (!ASSERT_OK(munmap((void *)addr, page_size), "munmap"))
+ goto out;
+
+ addr = -1;
+ alloc_user_pages(skel, 1, &addr);
+
+out:
+ if (addr >= 0)
+ munmap((void *)addr, page_size);
+}
+
+static void test_read_implies_exec(struct user_page *skel)
+{
+ long page_size = sysconf(_SC_PAGESIZE);
+ long addr = -1;
+ int old_personality, set_personality;
+
+ old_personality = personality(0xffffffffUL);
+ if (!ASSERT_NEQ(old_personality, -1, "get_personality"))
+ return;
+
+ set_personality = old_personality | READ_IMPLIES_EXEC;
+ if (!ASSERT_NEQ(personality(set_personality), -1, "set_personality"))
+ return;
+
+ skel->bss->action = USER_PAGE_ALLOC;
+ skel->bss->nr_pages = 1;
+ user_page_trigger();
+ if (!ASSERT_GE(skel->bss->user_addr, 0, "read_implies_exec_alloc_ret"))
+ goto out;
+ addr = skel->bss->user_addr;
+ check_user_page_vma((void *)addr, page_size, "rw-p");
+
+out:
+ if (addr >= 0)
+ munmap((void *)addr, page_size);
+ ASSERT_EQ(personality(old_personality), set_personality,
+ "restore_personality");
+}
+
+void test_user_page(void)
+{
+ struct user_page *skel;
+
+ skel = user_page__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "open_and_load"))
+ return;
+
+ skel->bss->monitored_pid = getpid();
+ if (!ASSERT_OK(user_page__attach(skel), "attach"))
+ goto out;
+
+ if (test__start_subtest("allocations"))
+ test_allocations(skel);
+ if (test__start_subtest("reallocate_after_unmap"))
+ test_reallocate_after_unmap(skel);
+ if (test__start_subtest("read_implies_exec"))
+ test_read_implies_exec(skel);
+
+out:
+ user_page__destroy(skel);
+}
diff --git a/tools/testing/selftests/bpf/progs/user_page.c b/tools/testing/selftests/bpf/progs/user_page.c
new file mode 100644
index 000000000000..6dd9417463df
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/user_page.c
@@ -0,0 +1,31 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <vmlinux.h>
+#include <bpf/bpf_helpers.h>
+#include <bpf/bpf_tracing.h>
+
+#include "bpf_misc.h"
+
+char _license[] SEC("license") = "GPL";
+
+#define USER_PAGE_ALLOC 1
+
+int monitored_pid;
+int action;
+long user_addr;
+unsigned long nr_pages;
+
+SEC("uprobe.s//proc/self/exe:user_page_trigger")
+int user_page_test(struct pt_regs *ctx)
+{
+ if ((bpf_get_current_pid_tgid() >> 32) != monitored_pid)
+ return 0;
+
+ switch (action) {
+ case USER_PAGE_ALLOC:
+ user_addr = bpf_user_page_alloc(nr_pages);
+ break;
+ }
+
+ return 0;
+}
--
2.54.0
^ permalink raw reply related [flat|nested] 19+ messages in thread* [RFC bpf-next 7/9] selftests/bpf: Add uprobe user page exec test
2026-10-21 19:57 [RFC bpf-next 0/9] bpf: Add support to allocate user page Jiri Olsa
` (5 preceding siblings ...)
2026-10-21 19:57 ` [RFC bpf-next 6/9] selftests/bpf: Add user page allocation test Jiri Olsa
@ 2026-10-21 19:57 ` Jiri Olsa
2026-10-21 19:57 ` [RFC bpf-next 8/9] selftests/bpf: Add faultable user page writes test Jiri Olsa
` (2 subsequent siblings)
9 siblings, 0 replies; 19+ messages in thread
From: Jiri Olsa @ 2026-10-21 19:57 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann
Cc: bpf, Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
linux-mm, Andrew Morton, Liam R. Howlett, Lorenzo Stoakes,
linux-kselftest, Shuah Khan, John Fastabend
Test executable permissions for whole and partial user-page
allocations and execution of code written into a user page.
Assisted-by: Codex:GPT-6
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
.../selftests/bpf/prog_tests/user_page.c | 62 +++++++++++++++++++
tools/testing/selftests/bpf/progs/user_page.c | 18 ++++++
2 files changed, 80 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/user_page.c b/tools/testing/selftests/bpf/prog_tests/user_page.c
index ff51e7c598a9..1b5dff2c8ff6 100644
--- a/tools/testing/selftests/bpf/prog_tests/user_page.c
+++ b/tools/testing/selftests/bpf/prog_tests/user_page.c
@@ -9,6 +9,8 @@
enum {
USER_PAGE_ALLOC = 1,
+ USER_PAGE_EXEC = 2,
+ USER_PAGE_EXEC_ONLY = 3,
};
static noinline void user_page_trigger(void)
@@ -128,6 +130,62 @@ static void test_read_implies_exec(struct user_page *skel)
"restore_personality");
}
+static void test_execute_user_page(struct user_page *skel)
+{
+ long page_size = sysconf(_SC_PAGESIZE);
+ long addr = -1;
+
+#ifndef __x86_64__
+ test__skip();
+ return;
+#endif
+
+ if (!alloc_user_pages(skel, 1, &addr))
+ return;
+
+ skel->bss->exec_addr = addr;
+ skel->bss->action = USER_PAGE_EXEC;
+ user_page_trigger();
+ if (!ASSERT_EQ(skel->bss->write_ret, 0, "exec_write_ret") ||
+ !ASSERT_EQ(skel->bss->exec_ret, 0, "exec_ret"))
+ goto out;
+
+ ASSERT_EQ(((int (*)(void))addr)(), 42, "execute_user_page");
+
+out:
+ if (addr >= 0)
+ munmap((void *)addr, page_size);
+}
+
+static void test_partial_exec(struct user_page *skel)
+{
+ long page_size = sysconf(_SC_PAGESIZE);
+ long addr = -1;
+
+ if (!alloc_user_pages(skel, 3, &addr))
+ return;
+
+ skel->bss->exec_addr = addr + page_size;
+ skel->bss->action = USER_PAGE_EXEC_ONLY;
+ user_page_trigger();
+ if (!ASSERT_OK(skel->bss->exec_ret, "middle_page_exec_ret") ||
+ !check_user_page_vma((void *)(addr + 0 * page_size), page_size, "rw-p") ||
+ !check_user_page_vma((void *)(addr + 1 * page_size), page_size, "r-xp") ||
+ !check_user_page_vma((void *)(addr + 2 * page_size), page_size, "rw-p"))
+ goto out;
+
+ skel->bss->exec_addr = addr + 2 * page_size;
+ user_page_trigger();
+ if (!ASSERT_OK(skel->bss->exec_ret, "last_page_exec_ret"))
+ goto out;
+
+ check_user_page_vma((void *)(addr + 2 * page_size), page_size, "r-xp");
+
+out:
+ if (addr >= 0)
+ munmap((void *)addr, page_size * 3);
+}
+
void test_user_page(void)
{
struct user_page *skel;
@@ -146,6 +204,10 @@ void test_user_page(void)
test_reallocate_after_unmap(skel);
if (test__start_subtest("read_implies_exec"))
test_read_implies_exec(skel);
+ if (test__start_subtest("execute_user_page"))
+ test_execute_user_page(skel);
+ if (test__start_subtest("partial_exec"))
+ test_partial_exec(skel);
out:
user_page__destroy(skel);
diff --git a/tools/testing/selftests/bpf/progs/user_page.c b/tools/testing/selftests/bpf/progs/user_page.c
index 6dd9417463df..38f6fcb89bc6 100644
--- a/tools/testing/selftests/bpf/progs/user_page.c
+++ b/tools/testing/selftests/bpf/progs/user_page.c
@@ -9,11 +9,20 @@
char _license[] SEC("license") = "GPL";
#define USER_PAGE_ALLOC 1
+#define USER_PAGE_EXEC 2
+#define USER_PAGE_EXEC_ONLY 3
int monitored_pid;
int action;
long user_addr;
+long exec_addr;
unsigned long nr_pages;
+int write_ret;
+int exec_ret;
+static const unsigned char insns[] = {
+ 0xf3, 0x0f, 0x1e, 0xfa, /* endbr64 */
+ 0xb8, 42, 0, 0, 0, 0xc3, /* mov $42, %eax; ret */
+};
SEC("uprobe.s//proc/self/exe:user_page_trigger")
int user_page_test(struct pt_regs *ctx)
@@ -25,6 +34,15 @@ int user_page_test(struct pt_regs *ctx)
case USER_PAGE_ALLOC:
user_addr = bpf_user_page_alloc(nr_pages);
break;
+ case USER_PAGE_EXEC:
+ write_ret = bpf_probe_write_user_faultable((void *)exec_addr,
+ insns, sizeof(insns));
+ exec_ret = write_ret ? write_ret :
+ bpf_user_page_exec(exec_addr);
+ break;
+ case USER_PAGE_EXEC_ONLY:
+ exec_ret = bpf_user_page_exec(exec_addr);
+ break;
}
return 0;
--
2.54.0
^ permalink raw reply related [flat|nested] 19+ messages in thread* [RFC bpf-next 8/9] selftests/bpf: Add faultable user page writes test
2026-10-21 19:57 [RFC bpf-next 0/9] bpf: Add support to allocate user page Jiri Olsa
` (6 preceding siblings ...)
2026-10-21 19:57 ` [RFC bpf-next 7/9] selftests/bpf: Add uprobe user page exec test Jiri Olsa
@ 2026-10-21 19:57 ` Jiri Olsa
2026-10-21 19:57 ` [RFC bpf-next 9/9] selftests/bpf: Add user page failures test Jiri Olsa
2026-10-21 21:53 ` [RFC bpf-next 0/9] bpf: Add support to allocate user page Alexei Starovoitov
9 siblings, 0 replies; 19+ messages in thread
From: Jiri Olsa @ 2026-10-21 19:57 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann
Cc: bpf, Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
linux-mm, Andrew Morton, Liam R. Howlett, Lorenzo Stoakes,
linux-kselftest, Shuah Khan, John Fastabend
Exercise fault-enabled writes into an allocated user page
and verify the written data.
Assisted-by: Codex:GPT-6
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
.../selftests/bpf/prog_tests/user_page.c | 25 +++++++++++++++++++
tools/testing/selftests/bpf/progs/user_page.c | 7 ++++++
2 files changed, 32 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/user_page.c b/tools/testing/selftests/bpf/prog_tests/user_page.c
index 1b5dff2c8ff6..74ce52d726ec 100644
--- a/tools/testing/selftests/bpf/prog_tests/user_page.c
+++ b/tools/testing/selftests/bpf/prog_tests/user_page.c
@@ -11,6 +11,7 @@ enum {
USER_PAGE_ALLOC = 1,
USER_PAGE_EXEC = 2,
USER_PAGE_EXEC_ONLY = 3,
+ USER_PAGE_WRITE = 4,
};
static noinline void user_page_trigger(void)
@@ -186,6 +187,28 @@ static void test_partial_exec(struct user_page *skel)
munmap((void *)addr, page_size * 3);
}
+static void test_faultable_write(struct user_page *skel)
+{
+ static const char expected[] = "fault-enabled BPF write";
+ long page_size = sysconf(_SC_PAGESIZE);
+ long addr = -1;
+
+ if (!alloc_user_pages(skel, 1, &addr))
+ return;
+
+ skel->bss->user_addr = addr;
+ skel->bss->action = USER_PAGE_WRITE;
+ user_page_trigger();
+ if (!ASSERT_EQ(skel->bss->write_ret, 0, "write_ret"))
+ goto out;
+
+ ASSERT_EQ(memcmp((void *)addr, expected, sizeof(expected)), 0, "written_data");
+
+out:
+ if (addr >= 0)
+ munmap((void *)addr, page_size);
+}
+
void test_user_page(void)
{
struct user_page *skel;
@@ -208,6 +231,8 @@ void test_user_page(void)
test_execute_user_page(skel);
if (test__start_subtest("partial_exec"))
test_partial_exec(skel);
+ if (test__start_subtest("faultable_write"))
+ test_faultable_write(skel);
out:
user_page__destroy(skel);
diff --git a/tools/testing/selftests/bpf/progs/user_page.c b/tools/testing/selftests/bpf/progs/user_page.c
index 38f6fcb89bc6..a855b0b46350 100644
--- a/tools/testing/selftests/bpf/progs/user_page.c
+++ b/tools/testing/selftests/bpf/progs/user_page.c
@@ -11,6 +11,7 @@ char _license[] SEC("license") = "GPL";
#define USER_PAGE_ALLOC 1
#define USER_PAGE_EXEC 2
#define USER_PAGE_EXEC_ONLY 3
+#define USER_PAGE_WRITE 4
int monitored_pid;
int action;
@@ -19,6 +20,8 @@ long exec_addr;
unsigned long nr_pages;
int write_ret;
int exec_ret;
+
+static const char data[] = "fault-enabled BPF write";
static const unsigned char insns[] = {
0xf3, 0x0f, 0x1e, 0xfa, /* endbr64 */
0xb8, 42, 0, 0, 0, 0xc3, /* mov $42, %eax; ret */
@@ -43,6 +46,10 @@ int user_page_test(struct pt_regs *ctx)
case USER_PAGE_EXEC_ONLY:
exec_ret = bpf_user_page_exec(exec_addr);
break;
+ case USER_PAGE_WRITE:
+ write_ret = bpf_probe_write_user_faultable((void *)user_addr, data,
+ sizeof(data));
+ break;
}
return 0;
--
2.54.0
^ permalink raw reply related [flat|nested] 19+ messages in thread* [RFC bpf-next 9/9] selftests/bpf: Add user page failures test
2026-10-21 19:57 [RFC bpf-next 0/9] bpf: Add support to allocate user page Jiri Olsa
` (7 preceding siblings ...)
2026-10-21 19:57 ` [RFC bpf-next 8/9] selftests/bpf: Add faultable user page writes test Jiri Olsa
@ 2026-10-21 19:57 ` Jiri Olsa
2026-10-21 20:30 ` bot+bpf-ci
2026-10-21 21:53 ` [RFC bpf-next 0/9] bpf: Add support to allocate user page Alexei Starovoitov
9 siblings, 1 reply; 19+ messages in thread
From: Jiri Olsa @ 2026-10-21 19:57 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann
Cc: bpf, Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
linux-mm, Andrew Morton, Liam R. Howlett, Lorenzo Stoakes,
linux-kselftest, Shuah Khan, John Fastabend
Cover expected failures for invalid allocation sizes, misaligned or
unrelated exec requests, replacement of sealed mappings, repeated
execution, bad writes and attempts to restore write permission.
Assisted-by: Codex:GPT-6
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
.../selftests/bpf/prog_tests/user_page.c | 141 ++++++++++++++++++
tools/testing/selftests/bpf/progs/user_page.c | 5 +
2 files changed, 146 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/user_page.c b/tools/testing/selftests/bpf/prog_tests/user_page.c
index 74ce52d726ec..107349aace94 100644
--- a/tools/testing/selftests/bpf/prog_tests/user_page.c
+++ b/tools/testing/selftests/bpf/prog_tests/user_page.c
@@ -12,6 +12,7 @@ enum {
USER_PAGE_EXEC = 2,
USER_PAGE_EXEC_ONLY = 3,
USER_PAGE_WRITE = 4,
+ USER_PAGE_BAD_WRITE = 5,
};
static noinline void user_page_trigger(void)
@@ -209,6 +210,144 @@ static void test_faultable_write(struct user_page *skel)
munmap((void *)addr, page_size);
}
+static void test_invalid_allocation_size(struct user_page *skel)
+{
+ skel->bss->action = USER_PAGE_ALLOC;
+ skel->bss->nr_pages = 0;
+ user_page_trigger();
+ if (!ASSERT_EQ(skel->bss->user_addr, -EINVAL, "zero_pages_ret"))
+ return;
+
+ skel->bss->nr_pages = ~0UL;
+ user_page_trigger();
+ ASSERT_EQ(skel->bss->user_addr, -EINVAL, "overflow_pages_ret");
+}
+
+static void test_misaligned_exec(struct user_page *skel)
+{
+ long page_size = sysconf(_SC_PAGESIZE);
+ long addr = -1;
+
+ if (!alloc_user_pages(skel, 1, &addr))
+ return;
+
+ skel->bss->exec_addr = addr + 1;
+ skel->bss->action = USER_PAGE_EXEC_ONLY;
+ user_page_trigger();
+ ASSERT_EQ(skel->bss->exec_ret, -EINVAL, "misaligned_exec_ret");
+
+ if (addr >= 0)
+ munmap((void *)addr, page_size);
+}
+
+static void test_reject_replacement(struct user_page *skel)
+{
+ long page_size = sysconf(_SC_PAGESIZE);
+ long addr = -1;
+ void *ret;
+
+ if (!alloc_user_pages(skel, 1, &addr))
+ return;
+
+ ret = mmap((void *)addr, page_size, PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
+ if (!ASSERT_EQ(ret, (void *)addr, "replace_mmap"))
+ goto out;
+
+ skel->bss->exec_addr = addr;
+ skel->bss->action = USER_PAGE_EXEC_ONLY;
+ user_page_trigger();
+ ASSERT_EQ(skel->bss->exec_ret, -EINVAL, "replacement_not_bpf");
+
+out:
+ if (addr >= 0)
+ munmap((void *)addr, page_size);
+}
+
+static void test_bad_write(struct user_page *skel)
+{
+ skel->bss->action = USER_PAGE_BAD_WRITE;
+ user_page_trigger();
+ ASSERT_EQ(skel->bss->write_ret, -EFAULT, "bad_write_ret");
+}
+
+static void test_unrelated_mapping_exec_rejected(struct user_page *skel)
+{
+ long page_size = sysconf(_SC_PAGESIZE);
+ void *unrelated;
+
+ unrelated = mmap(NULL, page_size, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (!ASSERT_NEQ(unrelated, MAP_FAILED, "unrelated_mmap"))
+ return;
+
+ skel->bss->exec_addr = (long)unrelated;
+ skel->bss->action = USER_PAGE_EXEC_ONLY;
+ user_page_trigger();
+ ASSERT_EQ(skel->bss->exec_ret, -EINVAL, "bad_exec_ret");
+
+ munmap(unrelated, page_size);
+}
+
+static void test_repeat_exec_rejected(struct user_page *skel)
+{
+ long page_size = sysconf(_SC_PAGESIZE);
+ long addr = -1;
+
+ if (!alloc_user_pages(skel, 1, &addr))
+ return;
+
+ skel->bss->exec_addr = addr;
+ skel->bss->action = USER_PAGE_EXEC_ONLY;
+ user_page_trigger();
+ if (!ASSERT_OK(skel->bss->exec_ret, "first_exec_ret"))
+ goto out;
+ user_page_trigger();
+
+ ASSERT_EQ(skel->bss->exec_ret, -EINVAL, "repeat_exec_ret");
+
+out:
+ if (addr >= 0)
+ munmap((void *)addr, page_size);
+}
+
+static void test_exec_write_protection(struct user_page *skel)
+{
+ long page_size = sysconf(_SC_PAGESIZE);
+ long addr = -1;
+
+ if (!alloc_user_pages(skel, 1, &addr))
+ return;
+
+ skel->bss->exec_addr = addr;
+ skel->bss->action = USER_PAGE_EXEC_ONLY;
+ user_page_trigger();
+ if (!ASSERT_OK(skel->bss->exec_ret, "exec_ret"))
+ goto out;
+
+ errno = 0;
+ if (!ASSERT_EQ(mprotect((void *)addr, page_size,
+ PROT_READ | PROT_WRITE), -1, "restore_write"))
+ goto out;
+
+ ASSERT_EQ(errno, EACCES, "restore_write_errno");
+
+out:
+ if (addr >= 0)
+ munmap((void *)addr, page_size);
+}
+
+static void test_fails(struct user_page *skel)
+{
+ test_invalid_allocation_size(skel);
+ test_misaligned_exec(skel);
+ test_reject_replacement(skel);
+ test_bad_write(skel);
+ test_unrelated_mapping_exec_rejected(skel);
+ test_repeat_exec_rejected(skel);
+ test_exec_write_protection(skel);
+}
+
void test_user_page(void)
{
struct user_page *skel;
@@ -233,6 +372,8 @@ void test_user_page(void)
test_partial_exec(skel);
if (test__start_subtest("faultable_write"))
test_faultable_write(skel);
+ if (test__start_subtest("fails"))
+ test_fails(skel);
out:
user_page__destroy(skel);
diff --git a/tools/testing/selftests/bpf/progs/user_page.c b/tools/testing/selftests/bpf/progs/user_page.c
index a855b0b46350..530a0be3737b 100644
--- a/tools/testing/selftests/bpf/progs/user_page.c
+++ b/tools/testing/selftests/bpf/progs/user_page.c
@@ -12,6 +12,7 @@ char _license[] SEC("license") = "GPL";
#define USER_PAGE_EXEC 2
#define USER_PAGE_EXEC_ONLY 3
#define USER_PAGE_WRITE 4
+#define USER_PAGE_BAD_WRITE 5
int monitored_pid;
int action;
@@ -50,6 +51,10 @@ int user_page_test(struct pt_regs *ctx)
write_ret = bpf_probe_write_user_faultable((void *)user_addr, data,
sizeof(data));
break;
+ case USER_PAGE_BAD_WRITE:
+ write_ret = bpf_probe_write_user_faultable((void *)1, data,
+ sizeof(data));
+ break;
}
return 0;
--
2.54.0
^ permalink raw reply related [flat|nested] 19+ messages in thread* Re: [RFC bpf-next 9/9] selftests/bpf: Add user page failures test
2026-10-21 19:57 ` [RFC bpf-next 9/9] selftests/bpf: Add user page failures test Jiri Olsa
@ 2026-10-21 20:30 ` bot+bpf-ci
0 siblings, 0 replies; 19+ messages in thread
From: bot+bpf-ci @ 2026-10-21 20:30 UTC (permalink / raw)
To: jolsa, ast, daniel
Cc: bpf, andrii, eddyz87, memxor, linux-mm, akpm, liam, ljs,
linux-kselftest, shuah, john.fastabend, ast, andrii, daniel,
martin.lau, eddyz87, yonghong.song, mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 2480 bytes --]
> selftests/bpf: Add user page failures test
TL;DR: Commit message claims the test covers replacing sealed mappings, but
test_reject_replacement() replaces a still-writable page before any exec call;
either reorder the test or reword the changelog.
> selftests/bpf: Add user page failures test
>
> Cover expected failures for invalid allocation sizes, misaligned or
> unrelated exec requests, replacement of sealed mappings, repeated
> execution, bad writes and attempts to restore write permission.
>
> Assisted-by: Codex:GPT-6
> Signed-off-by: Jiri Olsa <jolsa@kernel.org>
The commit message lists "replacement of sealed mappings" as a covered
failure, but does test_reject_replacement() ever seal the mapping before
replacing it?
In this series "seal" refers to the bpf_user_page_exec() transition, which
the kernel-doc describes as "The page cannot be made writable again after
this call". The test goes straight from alloc_user_pages() to the
replacement:
if (!alloc_user_pages(skel, 1, &addr))
return;
ret = mmap((void *)addr, page_size, PROT_WRITE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
if (!ASSERT_EQ(ret, (void *)addr, "replace_mmap"))
goto out;
skel->bss->exec_addr = addr;
skel->bss->action = USER_PAGE_EXEC_ONLY;
user_page_trigger();
ASSERT_EQ(skel->bss->exec_ret, -EINVAL, "replacement_not_bpf");
At the time of the MAP_FIXED mmap() the page is still a plain writable VMA,
and USER_PAGE_EXEC_ONLY is only triggered afterwards, on the replacement.
The replacement itself is also not a failure, since the test asserts that
it succeeds. Only the later bpf_user_page_exec() on the new VMA is
expected to fail with -EINVAL.
So the scenario the changelog describes (replacing an already exec-only,
non-writable page with a writable mapping and checking that exec refuses
it) is never run. A reader could also take the changelog and the test name
to mean that the kernel refuses to replace sealed mappings, which it does
not do.
Should bpf_user_page_exec() be called before the MAP_FIXED mmap() so the
test matches the description, or should the changelog be reworded (for
example "exec of a replaced BPF-allocated mapping")?
---
AI reviewed your patch. Please fix the bug or email reply why it's not a bug.
See: https://github.com/kernel-patches/vmtest/blob/master/ci/claude/README.md
CI run summary: https://github.com/kernel-patches/bpf/actions/runs/39668036298
^ permalink raw reply [flat|nested] 19+ messages in thread
* Re: [RFC bpf-next 0/9] bpf: Add support to allocate user page
2026-10-21 19:57 [RFC bpf-next 0/9] bpf: Add support to allocate user page Jiri Olsa
` (8 preceding siblings ...)
2026-10-21 19:57 ` [RFC bpf-next 9/9] selftests/bpf: Add user page failures test Jiri Olsa
@ 2026-10-21 21:53 ` Alexei Starovoitov
2026-10-22 10:01 ` Jiri Olsa
2026-10-22 10:27 ` Lorenzo Stoakes (ARM)
9 siblings, 2 replies; 19+ messages in thread
From: Alexei Starovoitov @ 2026-10-21 21:53 UTC (permalink / raw)
To: Jiri Olsa, Daniel Borkmann
Cc: bpf, Andrii Nakryiko, Eduard Zingerman, Kumar Kartikeya Dwivedi,
linux-mm, Andrew Morton, Liam R. Howlett, Lorenzo Stoakes,
linux-kselftest, Shuah Khan, John Fastabend
On Wed, Oct 21, 2026 at 12:57 PM Jiri Olsa <jolsa@kernel.org> wrote:
> hi,
> we have few use cases where having the ability to allocate normal
> or executable user space memory would help (like to replace function
> in live process or change environment variables)
I think it's better to do what debuggers do:
vm_mmap() with the final prot and
access_process_vm(FOLL_FORCE | FOLL_WRITE), like PTRACE_POKETEXT,
to fill it. It faults the pages in, writes into r-x private mapping
and copy_to_user_page() takes care of icache.
Then no need for bpf_user_page_exec(), do_mprotect_pkey_locked(),
the marker, READ_IMPLIES_EXEC special case, get_user_pages(), etc.
No mm changes.
Like:
diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
index 648abe1..ca1d88d 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -29,6 +29,7 @@
#include <linux/task_work.h>
#include <linux/irq_work.h>
#include <linux/buildid.h>
+#include <linux/mman.h>
#include "../../lib/kstrtox.h"
@@ -3834,6 +3835,60 @@ __bpf_kfunc int bpf_copy_from_user_task_str(void *dst, u32 dst__sz,
return ret;
}
+/**
+ * bpf_copy_to_user_task() - Copy data to a task's address space
+ * @unsafe_ptr__ign: Destination address in the task's address space
+ * @src: Source address, in kernel space
+ * @src__sz: Number of bytes to copy
+ * @tsk: The task whose address space will be used
+ * @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.
+ *
+ * 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_task(void __user *unsafe_ptr__ign, const void *src,
+ u32 src__sz, struct task_struct *tsk, u64 flags)
+{
+ int ret;
+
+ if (unlikely(flags))
+ return -EINVAL;
+
+ if (unlikely(!src__sz))
+ return 0;
+
+ ret = access_process_vm(tsk, (unsigned long)unsafe_ptr__ign, (void *)src,
+ src__sz, FOLL_FORCE | FOLL_WRITE);
+ return ret == src__sz ? 0 : -EFAULT;
+}
+
+/**
+ * bpf_user_mmap() - Map private anonymous memory into the current process
+ * @len: Length of the mapping
+ * @prot: PROT_* flags of mmap(2)
+ *
+ * Return: The address of the mapping on success, or a negative error code.
+ */
+__bpf_kfunc long bpf_user_mmap(unsigned long len, unsigned long prot)
+{
+ return vm_mmap(NULL, 0, len, prot, MAP_PRIVATE | MAP_ANONYMOUS, 0);
+}
+
+/**
+ * bpf_user_munmap() - Unmap a range of addresses of the current process
+ * @addr: Start of the range
+ * @len: Length of the range
+ *
+ * Return: 0 on success, or a negative error code.
+ */
+__bpf_kfunc int bpf_user_munmap(unsigned long addr, unsigned long len)
+{
+ return vm_munmap(addr, len);
+}
+
/* Keep unsigned long in prototype so that kfunc is usable when emitted to
* vmlinux.h in BPF programs directly, but note that while in BPF prog, the
* unsigned long always points to 8-byte region on stack, the kernel may only
@@ -5139,6 +5194,25 @@ 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_task, KF_SLEEPABLE | KF_PERFMON)
+BTF_ID_FLAGS(func, bpf_user_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;
+ 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)
@@ -5266,6 +5340,8 @@ static int __init kfunc_init(void)
};
ret = register_btf_kfunc_id_set(BPF_PROG_TYPE_TRACING, &generic_kfunc_set);
+ /* Sleepable kprobe prog can only be attached to uprobe */
+ 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);
But I'm not at all sure that it's a good idea
to inject code into a process.
pw-bot: cr
^ permalink raw reply related [flat|nested] 19+ messages in thread* Re: [RFC bpf-next 0/9] bpf: Add support to allocate user page
2026-10-21 21:53 ` [RFC bpf-next 0/9] bpf: Add support to allocate user page Alexei Starovoitov
@ 2026-10-22 10:01 ` Jiri Olsa
2026-10-22 10:27 ` Lorenzo Stoakes (ARM)
1 sibling, 0 replies; 19+ messages in thread
From: Jiri Olsa @ 2026-10-22 10:01 UTC (permalink / raw)
To: Alexei Starovoitov
Cc: Daniel Borkmann, bpf, Andrii Nakryiko, Eduard Zingerman,
Kumar Kartikeya Dwivedi, linux-mm, Andrew Morton, Liam R. Howlett,
Lorenzo Stoakes, linux-kselftest, Shuah Khan, John Fastabend
On Wed, Oct 21, 2026 at 09:53:47PM +0000, Alexei Starovoitov wrote:
> On Wed, Oct 21, 2026 at 12:57 PM Jiri Olsa <jolsa@kernel.org> wrote:
> > hi,
> > we have few use cases where having the ability to allocate normal
> > or executable user space memory would help (like to replace function
> > in live process or change environment variables)
>
> I think it's better to do what debuggers do:
> vm_mmap() with the final prot and
> access_process_vm(FOLL_FORCE | FOLL_WRITE), like PTRACE_POKETEXT,
> to fill it. It faults the pages in, writes into r-x private mapping
> and copy_to_user_page() takes care of icache.
> Then no need for bpf_user_page_exec(), do_mprotect_pkey_locked(),
> the marker, READ_IMPLIES_EXEC special case, get_user_pages(), etc.
> No mm changes.
great, much better, will use it in next version, thanks
>
> Like:
>
> diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
> index 648abe1..ca1d88d 100644
> --- a/kernel/bpf/helpers.c
> +++ b/kernel/bpf/helpers.c
> @@ -29,6 +29,7 @@
> #include <linux/task_work.h>
> #include <linux/irq_work.h>
> #include <linux/buildid.h>
> +#include <linux/mman.h>
>
> #include "../../lib/kstrtox.h"
>
> @@ -3834,6 +3835,60 @@ __bpf_kfunc int bpf_copy_from_user_task_str(void *dst, u32 dst__sz,
> return ret;
> }
>
> +/**
> + * bpf_copy_to_user_task() - Copy data to a task's address space
> + * @unsafe_ptr__ign: Destination address in the task's address space
> + * @src: Source address, in kernel space
> + * @src__sz: Number of bytes to copy
> + * @tsk: The task whose address space will be used
> + * @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.
> + *
> + * 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_task(void __user *unsafe_ptr__ign, const void *src,
> + u32 src__sz, struct task_struct *tsk, u64 flags)
> +{
> + int ret;
> +
> + if (unlikely(flags))
> + return -EINVAL;
> +
> + if (unlikely(!src__sz))
> + return 0;
> +
> + ret = access_process_vm(tsk, (unsigned long)unsafe_ptr__ign, (void *)src,
> + src__sz, FOLL_FORCE | FOLL_WRITE);
> + return ret == src__sz ? 0 : -EFAULT;
> +}
> +
> +/**
> + * bpf_user_mmap() - Map private anonymous memory into the current process
> + * @len: Length of the mapping
> + * @prot: PROT_* flags of mmap(2)
> + *
> + * Return: The address of the mapping on success, or a negative error code.
> + */
> +__bpf_kfunc long bpf_user_mmap(unsigned long len, unsigned long prot)
> +{
> + return vm_mmap(NULL, 0, len, prot, MAP_PRIVATE | MAP_ANONYMOUS, 0);
> +}
> +
> +/**
> + * bpf_user_munmap() - Unmap a range of addresses of the current process
> + * @addr: Start of the range
> + * @len: Length of the range
> + *
> + * Return: 0 on success, or a negative error code.
> + */
> +__bpf_kfunc int bpf_user_munmap(unsigned long addr, unsigned long len)
> +{
> + return vm_munmap(addr, len);
> +}
> +
> /* Keep unsigned long in prototype so that kfunc is usable when emitted to
> * vmlinux.h in BPF programs directly, but note that while in BPF prog, the
> * unsigned long always points to 8-byte region on stack, the kernel may only
> @@ -5139,6 +5194,25 @@ 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_task, KF_SLEEPABLE | KF_PERFMON)
> +BTF_ID_FLAGS(func, bpf_user_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;
> + return security_locked_down(LOCKDOWN_BPF_WRITE_USER);
ah right.. all of those kfuncs should be behing write_user lockdown
> +}
> +
> +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)
> @@ -5266,6 +5340,8 @@ static int __init kfunc_init(void)
> };
>
> ret = register_btf_kfunc_id_set(BPF_PROG_TYPE_TRACING, &generic_kfunc_set);
> + /* Sleepable kprobe prog can only be attached to uprobe */
> + 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);
>
> But I'm not at all sure that it's a good idea
> to inject code into a process.
well FWIW it's still guarded with the LOCKDOWN_BPF_WRITE_USER same as
bpf_probe_write_user helper
at the moment we have fragile way of live patching a userspace process,
this would give us a more direct and reliable way for that
thanks,
jirka
^ permalink raw reply [flat|nested] 19+ messages in thread* Re: [RFC bpf-next 0/9] bpf: Add support to allocate user page
2026-10-21 21:53 ` [RFC bpf-next 0/9] bpf: Add support to allocate user page Alexei Starovoitov
2026-10-22 10:01 ` Jiri Olsa
@ 2026-10-22 10:27 ` Lorenzo Stoakes (ARM)
1 sibling, 0 replies; 19+ messages in thread
From: Lorenzo Stoakes (ARM) @ 2026-10-22 10:27 UTC (permalink / raw)
To: Alexei Starovoitov
Cc: Jiri Olsa, Daniel Borkmann, bpf, Andrii Nakryiko,
Eduard Zingerman, Kumar Kartikeya Dwivedi, linux-mm,
Andrew Morton, Liam R. Howlett, linux-kselftest, Shuah Khan,
John Fastabend
On Wed, Oct 21, 2026 at 09:53:47PM +0000, Alexei Starovoitov wrote:
> On Wed, Oct 21, 2026 at 12:57 PM Jiri Olsa <jolsa@kernel.org> wrote:
> > hi,
> > we have few use cases where having the ability to allocate normal
> > or executable user space memory would help (like to replace function
> > in live process or change environment variables)
>
> I think it's better to do what debuggers do:
> vm_mmap() with the final prot and
> access_process_vm(FOLL_FORCE | FOLL_WRITE), like PTRACE_POKETEXT,
> to fill it. It faults the pages in, writes into r-x private mapping
> and copy_to_user_page() takes care of icache.
> Then no need for bpf_user_page_exec(), do_mprotect_pkey_locked(),
> the marker, READ_IMPLIES_EXEC special case, get_user_pages(), etc.
> No mm changes.
OK thanks that addresses my concerns :)
--
Cheers, Lorenzo
^ permalink raw reply [flat|nested] 19+ messages in thread