* [PATCHv3 bpf-next 1/8] bpf: Honor const on generic kfunc memory arguments
2026-11-05 10:23 [PATCHv3 bpf-next 0/8] bpf: Add user memory kfuncs Jiri Olsa
@ 2026-11-05 10:23 ` Jiri Olsa
2026-11-05 10:59 ` bot+bpf-ci
2026-11-05 10:23 ` [PATCHv3 bpf-next 2/8] bpf: Add user memory kfuncs Jiri Olsa
` (6 subsequent siblings)
7 siblings, 1 reply; 13+ messages in thread
From: Jiri Olsa @ 2026-11-05 10:23 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann
Cc: bpf, linux-mm, Andrii Nakryiko, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Lorenzo Stoakes, John Fastabend,
Federico Di Pierro
Mark generic kfunc memory arguments as read-only when their BTF pointer
type points to const data. Preserve that access mode when resolving
scalar structs to fixed-size memory.
Derive the memory access flag from the pointer's BTF type, and preserve it
when resolving scalar structs to fixed-size memory. Document that const
memory arguments require read access only.
Assisted-by: Codex:GPT-6
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
Documentation/bpf/kfuncs.rst | 4 ++++
kernel/bpf/verifier.c | 24 +++++++++++++++++++++---
2 files changed, 25 insertions(+), 3 deletions(-)
diff --git a/Documentation/bpf/kfuncs.rst b/Documentation/bpf/kfuncs.rst
index 901c37504217..4f8dd7c1d785 100644
--- a/Documentation/bpf/kfuncs.rst
+++ b/Documentation/bpf/kfuncs.rst
@@ -137,6 +137,10 @@ argument as its size. By default, without __sz annotation, the size of the type
of the pointer is used. Without __sz annotation, a kfunc cannot accept a void
pointer.
+The verifier checks such memory for read and write access. When the argument
+is a pointer to const, like ``const void *mem``, the verifier checks it for
+read access only, and read-only memory (e.g. ``.rodata``) can be passed.
+
2.3.2 __k Annotation
--------------------
diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
index 6c93437f8db0..8f885d3acaa9 100644
--- a/kernel/bpf/verifier.c
+++ b/kernel/bpf/verifier.c
@@ -13237,6 +13237,22 @@ static bool is_kfunc_arg_const_mem_size(const struct btf *btf,
return btf_param_match_suffix(btf, arg, "__szk");
}
+/* A kfunc only reads through 'const T *arg'. The compiler enforces it. */
+static enum bpf_type_flag kfunc_arg_mem_flag(const struct btf *btf,
+ const struct btf_param *arg)
+{
+ const struct btf_type *t = btf_type_skip_modifiers(btf, arg->type, NULL);
+ u32 id, ref_id;
+
+ btf_type_skip_modifiers(btf, t->type, &ref_id);
+ for (id = t->type; id != ref_id; id = t->type) {
+ t = btf_type_by_id(btf, id);
+ if (btf_kind(t) == BTF_KIND_CONST)
+ return MEM_RDONLY;
+ }
+ return MEM_WRITE;
+}
+
static bool is_kfunc_arg_constant(const struct btf *btf, const struct btf_param *arg)
{
return btf_param_match_suffix(btf, arg, "__k");
@@ -13618,7 +13634,8 @@ static int resolve_func_arg_type(struct bpf_verifier_env *env,
PTR_ERR(resolve_ret));
return -EINVAL;
}
- *arg_type = ARG_PTR_TO_MEM | MEM_FIXED_SIZE | MEM_WRITE |
+ *arg_type = ARG_PTR_TO_MEM | MEM_FIXED_SIZE |
+ kfunc_arg_mem_flag(meta->btf, &args[arg]) |
(*arg_type & (PTR_MAYBE_NULL | MEM_UNINIT));
return 0;
@@ -14170,7 +14187,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 | kfunc_arg_mem_flag(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;
@@ -14195,7 +14212,8 @@ 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 |
+ kfunc_arg_mem_flag(meta->btf, &args[arg]);
}
if (is_kfunc_arg_uninit(meta->btf, &args[arg]))
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* Re: [PATCHv3 bpf-next 1/8] bpf: Honor const on generic kfunc memory arguments
2026-11-05 10:23 ` [PATCHv3 bpf-next 1/8] bpf: Honor const on generic kfunc memory arguments Jiri Olsa
@ 2026-11-05 10:59 ` bot+bpf-ci
0 siblings, 0 replies; 13+ messages in thread
From: bot+bpf-ci @ 2026-11-05 10:59 UTC (permalink / raw)
To: jolsa, ast, daniel
Cc: bpf, linux-mm, andrii, eddyz87, memxor, ljs, john.fastabend,
nierro92, ast, andrii, daniel, martin.lau, eddyz87, yonghong.song,
mason, ihor.solodrai
[-- Attachment #1: Type: text/plain, Size: 2807 bytes --]
> bpf: Honor const on generic kfunc memory arguments
TL;DR: Comment and kfuncs.rst claim the compiler enforces const, but casts
drop it; a kfunc writing through a const memory arg could corrupt verifier
state. No in-tree case found; docs should state the contract.
> diff --git a/kernel/bpf/verifier.c b/kernel/bpf/verifier.c
> --- a/kernel/bpf/verifier.c
> +++ b/kernel/bpf/verifier.c
[ ... ]
> @@ -13448,6 +13448,22 @@ static bool is_kfunc_arg_const_mem_size(const struct btf *btf,
> return btf_param_match_suffix(btf, arg, "__szk");
> }
>
> +/* A kfunc only reads through 'const T *arg'. The compiler enforces it. */
> +static enum bpf_type_flag kfunc_arg_mem_flag(const struct btf *btf,
> + const struct btf_param *arg)
> +{
> + const struct btf_type *t = btf_type_skip_modifiers(btf, arg->type, NULL);
> + u32 id, ref_id;
> +
> + btf_type_skip_modifiers(btf, t->type, &ref_id);
> + for (id = t->type; id != ref_id; id = t->type) {
> + t = btf_type_by_id(btf, id);
> + if (btf_kind(t) == BTF_KIND_CONST)
> + return MEM_RDONLY;
> + }
> + return MEM_WRITE;
> +}
This isn't a bug, but is the comment "The compiler enforces it" accurate?
In C a cast silently drops const, and one registered kfunc already does
this:
__bpf_kfunc int bpf_ct_set_status(const struct nf_conn___init *nfct,
u32 status)
{
return nf_ct_change_status_common((struct nf_conn *)nfct, status);
}
That argument is ARG_PTR_TO_BTF_ID today, so this patch does not reach it.
With MEM_RDONLY returned from kfunc_arg_mem_flag(), func_arg_access_type()
yields BPF_READ only, so the BPF_WRITE handling is skipped:
- check_stack_range_initialized() does not clobber the stack, so spilled
pointers and known constants survive the call.
- check_map_access_type() accepts BPF_F_RDONLY_PROG map values, whose
contents the verifier constant-folds.
- check_reg_type() accepts PTR_TO_MEM|MEM_RDONLY and PTR_TO_BUF|MEM_RDONLY.
- may_access_direct_pkt_data() no longer sets seen_direct_write for packet
pointers.
A kfunc taking a pointer to const scalar, void or scalar-only struct memory
that writes through it after a cast would corrupt state the verifier
believes is intact. I did not find an in-tree kfunc that does this, so it
is not a live problem.
Could the comment, and the new kfuncs.rst paragraph that only says the
verifier checks such memory for read access, state the actual contract,
for example that a kfunc must not write through a const memory argument
because the verifier relies on it?
[ ... ]
---
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/42012074982
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCHv3 bpf-next 2/8] bpf: Add user memory kfuncs
2026-11-05 10:23 [PATCHv3 bpf-next 0/8] bpf: Add user memory kfuncs Jiri Olsa
2026-11-05 10:23 ` [PATCHv3 bpf-next 1/8] bpf: Honor const on generic kfunc memory arguments Jiri Olsa
@ 2026-11-05 10:23 ` Jiri Olsa
2026-11-05 10:44 ` sashiko-bot
2026-11-05 10:23 ` [PATCHv3 bpf-next 3/8] selftests/bpf: Add get_procmap_info helper Jiri Olsa
` (5 subsequent siblings)
7 siblings, 1 reply; 13+ messages in thread
From: Jiri Olsa @ 2026-11-05 10:23 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann
Cc: bpf, linux-mm, Andrii Nakryiko, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Lorenzo Stoakes, John Fastabend,
Federico Di Pierro
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 | 17 ++++++
kernel/bpf/helpers.c | 107 +++++++++++++++++++++++++++++++++++
kernel/bpf/syscall.c | 10 ++--
3 files changed, 129 insertions(+), 5 deletions(-)
diff --git a/Documentation/bpf/kfuncs.rst b/Documentation/bpf/kfuncs.rst
index 4f8dd7c1d785..81686d556f58 100644
--- a/Documentation/bpf/kfuncs.rst
+++ b/Documentation/bpf/kfuncs.rst
@@ -976,3 +976,20 @@ 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. A seccomp filter can still allow or deny the
+syscall that triggers a BPF program, but it cannot separately filter the
+mapping performed by these kfuncs.
+
+.. 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 6788489a2608..937a0d7d6839 100644
--- a/kernel/bpf/helpers.c
+++ b/kernel/bpf/helpers.c
@@ -29,6 +29,9 @@
#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>
#include "../../lib/kstrtox.h"
@@ -3768,6 +3771,88 @@ __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));
+}
+
+/**
+ * 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.
+ *
+ * Note that ia32 tasks are unsupported. This calls vm_mmap() directly and
+ * bypasses the compat mmap syscall wrapper.
+ *
+ * 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 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
+ *
+ * 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(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
@@ -5175,6 +5260,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)
@@ -5302,6 +5408,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
^ permalink raw reply related [flat|nested] 13+ messages in thread* Re: [PATCHv3 bpf-next 2/8] bpf: Add user memory kfuncs
2026-11-05 10:23 ` [PATCHv3 bpf-next 2/8] bpf: Add user memory kfuncs Jiri Olsa
@ 2026-11-05 10:44 ` sashiko-bot
0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-11-05 10:44 UTC (permalink / raw)
To: Jiri Olsa, Alexei Starovoitov, Daniel Borkmann
Cc: bpf, linux-mm, Andrii Nakryiko, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Lorenzo Stoakes, John Fastabend,
Federico Di Pierro
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] bpf: bpf_user_anon_mmap() triggers false in_32bit_syscall() on x86_64
--
commit db9c75bc63d6f183a3894259aadc59e0c0a612de
Author: Jiri Olsa <jolsa@kernel.org>
bpf: Add user memory kfuncs
This commit adds the sleepable bpf_copy_to_user(), bpf_user_anon_mmap(),
and bpf_user_munmap() kfuncs for BPF_PROG_TYPE_KPROBE programs to manipulate
the current process's user address space.
> diff --git a/kernel/bpf/helpers.c b/kernel/bpf/helpers.c
> index 6788489a2608b..937a0d7d68390 100644
> --- a/kernel/bpf/helpers.c
> +++ b/kernel/bpf/helpers.c
[ ... ]
> @@ -3768,6 +3771,88 @@ __bpf_kfunc int bpf_copy_from_user_mm(void *dst, u32 dst__sz,
[ ... ]
> +__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 vm_mmap(NULL, 0, len, prot, MAP_PRIVATE | MAP_ANONYMOUS, 0);
> +}
[Severity: Medium]
As reported by bot+bpf-ci@kernel.org on v1 [1], when bpf_user_anon_mmap() is
called from a sleepable uprobe or uretprobe on x86_64 with
CONFIG_X86_X32_ABI=y, can this falsely trigger in_32bit_syscall() for native
64-bit processes and restrict the mapping to the 32-bit compatibility address
range?
On x86_64, entering a uprobe or uretprobe via an int3 trap or via
sys_uprobe / sys_uretprobe sets task_pt_regs(current)->orig_ax to -1. Under
CONFIG_X86_X32_ABI=y, in_32bit_syscall() checks in_x32_syscall():
arch/x86/include/asm/compat.h:
static inline bool in_x32_syscall(void)
{
#ifdef CONFIG_X86_X32_ABI
if (task_pt_regs(current)->orig_ax & __X32_SYSCALL_BIT)
return true;
#endif
return false;
}
Because bpf_user_anon_mmap() passes addr = 0 to vm_mmap(),
__get_unmapped_area() calls arch_get_unmapped_area_topdown() /
arch_get_unmapped_area(), which invoke get_mmap_base() and find_start_end():
arch/x86/mm/mmap.c:get_mmap_base() {
...
#ifdef CONFIG_HAVE_ARCH_COMPAT_MMAP_BASES
if (in_32bit_syscall()) {
return is_legacy ? mm->mmap_compat_legacy_base
: mm->mmap_compat_base;
}
#endif
...
}
Since orig_ax is -1, in_32bit_syscall() (and in_compat_syscall() in
__thp_get_unmapped_area()) evaluates to true, placing mappings for native
64-bit tasks below 4 GiB and failing with -ENOMEM if the low 4 GiB range is
exhausted or len exceeds task_size_32bit().
[1] https://lore.kernel.org/r/571f32a410a85199bc9bcecfe07a9a9b6ef425b2052643e64031dd246c13691e@mail.kernel.org
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261105102343.311331-1-jolsa@kernel.org?part=1
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCHv3 bpf-next 3/8] selftests/bpf: Add get_procmap_info helper
2026-11-05 10:23 [PATCHv3 bpf-next 0/8] bpf: Add user memory kfuncs Jiri Olsa
2026-11-05 10:23 ` [PATCHv3 bpf-next 1/8] bpf: Honor const on generic kfunc memory arguments Jiri Olsa
2026-11-05 10:23 ` [PATCHv3 bpf-next 2/8] bpf: Add user memory kfuncs Jiri Olsa
@ 2026-11-05 10:23 ` Jiri Olsa
2026-11-05 10:23 ` [PATCHv3 bpf-next 4/8] selftests/bpf: Test const kfunc memory arguments Jiri Olsa
` (4 subsequent siblings)
7 siblings, 0 replies; 13+ messages in thread
From: Jiri Olsa @ 2026-11-05 10:23 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann
Cc: bpf, linux-mm, Andrii Nakryiko, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Lorenzo Stoakes, John Fastabend,
Federico Di Pierro
Adding get_procmap_info that queries the current process VMA range
and permissions through PROCMAP_QUERY. Extend procmap_query to return
the VMA end address and update its callers.
I don't think we need the search fallback, the PROCMAP api is merged
for some time now.
Assisted-by: Codex:GPT-6
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
tools/testing/selftests/bpf/trace_helpers.c | 38 ++++++++++++++++++---
tools/testing/selftests/bpf/trace_helpers.h | 1 +
2 files changed, 34 insertions(+), 5 deletions(-)
diff --git a/tools/testing/selftests/bpf/trace_helpers.c b/tools/testing/selftests/bpf/trace_helpers.c
index 6d7a4b8c4ab8..e871a26b991d 100644
--- a/tools/testing/selftests/bpf/trace_helpers.c
+++ b/tools/testing/selftests/bpf/trace_helpers.c
@@ -286,7 +286,8 @@ unsigned long long module_get_base_addr_sec(const char *module, const char *sec)
#ifdef PROCMAP_QUERY
int env_verbosity __weak = 0;
-static int procmap_query(int fd, const void *addr, __u32 query_flags, size_t *start, size_t *offset, int *flags)
+static int procmap_query(int fd, const void *addr, __u32 query_flags,
+ size_t *start, size_t *end, size_t *offset, int *flags)
{
char path_buf[PATH_MAX], build_id_buf[20];
struct procmap_query q;
@@ -325,6 +326,7 @@ static int procmap_query(int fd, const void *addr, __u32 query_flags, size_t *st
}
*start = q.vma_start;
+ *end = q.vma_end;
*offset = q.vma_offset;
*flags = q.vma_flags;
return 0;
@@ -333,13 +335,37 @@ static int procmap_query(int fd, const void *addr, __u32 query_flags, size_t *st
# ifndef PROCMAP_QUERY_VMA_EXECUTABLE
# define PROCMAP_QUERY_VMA_EXECUTABLE 0x04
# endif
-
-static int procmap_query(int fd, const void *addr, __u32 query_flags, size_t *start, size_t *offset, int *flags)
+static int procmap_query(int fd, const void *addr, __u32 query_flags,
+ size_t *start, size_t *end, size_t *offset, int *flags)
{
return -EOPNOTSUPP;
}
#endif
+int get_procmap_info(const void *addr, size_t *start, size_t *end, char perms[5])
+{
+ int flags, err;
+ size_t offset;
+ FILE *f;
+
+ f = fopen("/proc/self/maps", "r");
+ if (!f)
+ return -errno;
+
+ err = procmap_query(fileno(f), addr, 0, start, end, &offset, &flags);
+#ifdef PROCMAP_QUERY
+ if (!err && perms) {
+ snprintf(perms, 5, "%c%c%c%c",
+ (flags & PROCMAP_QUERY_VMA_READABLE) ? 'r' : '-',
+ (flags & PROCMAP_QUERY_VMA_WRITABLE) ? 'w' : '-',
+ (flags & PROCMAP_QUERY_VMA_EXECUTABLE) ? 'x' : '-',
+ (flags & PROCMAP_QUERY_VMA_SHARED) ? 's' : 'p');
+ }
+#endif
+ fclose(f);
+ return err;
+}
+
ssize_t get_uprobe_offset(const void *addr)
{
size_t start, base, end;
@@ -352,7 +378,8 @@ ssize_t get_uprobe_offset(const void *addr)
return -errno;
/* requested executable VMA only */
- err = procmap_query(fileno(f), addr, PROCMAP_QUERY_VMA_EXECUTABLE, &start, &base, &flags);
+ err = procmap_query(fileno(f), addr, PROCMAP_QUERY_VMA_EXECUTABLE,
+ &start, &end, &base, &flags);
if (err == -EOPNOTSUPP) {
bool found = false;
@@ -418,7 +445,8 @@ ssize_t get_rel_offset(uintptr_t addr)
if (!f)
return -errno;
- err = procmap_query(fileno(f), (const void *)addr, 0, &start, &offset, &flags);
+ err = procmap_query(fileno(f), (const void *)addr, 0,
+ &start, &end, &offset, &flags);
if (err == 0) {
fclose(f);
return (size_t)addr - start + offset;
diff --git a/tools/testing/selftests/bpf/trace_helpers.h b/tools/testing/selftests/bpf/trace_helpers.h
index 0f8d99746084..85b8f8e30232 100644
--- a/tools/testing/selftests/bpf/trace_helpers.h
+++ b/tools/testing/selftests/bpf/trace_helpers.h
@@ -59,6 +59,7 @@ int read_trace_pipe_iter(void (*cb)(const char *str, void *data),
ssize_t get_uprobe_offset(const void *addr);
ssize_t get_rel_offset(uintptr_t addr);
+int get_procmap_info(const void *addr, size_t *start, size_t *end, char perms[5]);
int read_build_id(const char *path, char *build_id, size_t size);
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* [PATCHv3 bpf-next 4/8] selftests/bpf: Test const kfunc memory arguments
2026-11-05 10:23 [PATCHv3 bpf-next 0/8] bpf: Add user memory kfuncs Jiri Olsa
` (2 preceding siblings ...)
2026-11-05 10:23 ` [PATCHv3 bpf-next 3/8] selftests/bpf: Add get_procmap_info helper Jiri Olsa
@ 2026-11-05 10:23 ` Jiri Olsa
2026-11-05 10:32 ` sashiko-bot
2026-11-05 10:23 ` [PATCHv3 bpf-next 5/8] selftests/bpf: Test user memory mmap kfuncs Jiri Olsa
` (3 subsequent siblings)
7 siblings, 1 reply; 13+ messages in thread
From: Jiri Olsa @ 2026-11-05 10:23 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann
Cc: bpf, linux-mm, Andrii Nakryiko, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Lorenzo Stoakes, John Fastabend,
Federico Di Pierro
Verify that const kfunc arguments accept read-only global data,
writable stack data, and const scalar structs. Check that read-only
data is rejected when passed to a writable memory argument.
Assisted-by: Codex:GPT-6
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
.../testing/selftests/bpf/prog_tests/kfunc_call.c | 2 ++
.../testing/selftests/bpf/progs/kfunc_call_fail.c | 10 ++++++++++
.../testing/selftests/bpf/progs/kfunc_call_test.c | 14 ++++++++++++++
.../testing/selftests/bpf/test_kmods/bpf_testmod.c | 10 ++++++++++
.../selftests/bpf/test_kmods/bpf_testmod_kfunc.h | 2 ++
5 files changed, 38 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..f2152591982b 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_const_mem, 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..bef956d3f89e 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 kfunc_mem_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 *)kfunc_mem_rodata,
+ sizeof(kfunc_mem_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..4f4a6713aa6e 100644
--- a/tools/testing/selftests/bpf/progs/kfunc_call_test.c
+++ b/tools/testing/selftests/bpf/progs/kfunc_call_test.c
@@ -5,6 +5,9 @@
#include "bpf_misc.h"
#include "../test_kmods/bpf_testmod_kfunc.h"
+static const char kfunc_const_data[] = "read-only source";
+static const struct prog_test_pass1 kfunc_const_struct = { .x0 = 1 };
+
static struct bpf_spin_lock kfunc_call_lock SEC(".data.A");
SEC("tc")
@@ -208,6 +211,17 @@ int kfunc_call_test_pass(struct __sk_buff *skb)
return 0;
}
+SEC("tc")
+int kfunc_call_test_const_mem(struct __sk_buff *skb)
+{
+ char stack_data[] = "read-write";
+
+ bpf_kfunc_call_test_const_mem(kfunc_const_data, sizeof(kfunc_const_data));
+ bpf_kfunc_call_test_const_mem(stack_data, sizeof(stack_data));
+ bpf_kfunc_call_test_const_struct(&kfunc_const_struct);
+ 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 76fbe64755ef..41ff36f0ca77 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod.c
@@ -1049,6 +1049,8 @@ 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_const_mem)
+BTF_ID_FLAGS(func, bpf_kfunc_call_test_const_struct)
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)
@@ -1552,6 +1554,14 @@ __bpf_kfunc void bpf_kfunc_call_test_mem_len_pass1(void *mem, int mem__sz)
{
}
+__bpf_kfunc void bpf_kfunc_call_test_const_mem(const void *mem, int mem__sz)
+{
+}
+
+__bpf_kfunc void bpf_kfunc_call_test_const_struct(const struct prog_test_pass1 *p)
+{
+}
+
__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 e9902e651d72..0a7df1798603 100644
--- a/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h
+++ b/tools/testing/selftests/bpf/test_kmods/bpf_testmod_kfunc.h
@@ -203,6 +203,8 @@ 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_const_mem(const void *mem, int mem__sz) __ksym;
+void bpf_kfunc_call_test_const_struct(const struct prog_test_pass1 *p) __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.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* Re: [PATCHv3 bpf-next 4/8] selftests/bpf: Test const kfunc memory arguments
2026-11-05 10:23 ` [PATCHv3 bpf-next 4/8] selftests/bpf: Test const kfunc memory arguments Jiri Olsa
@ 2026-11-05 10:32 ` sashiko-bot
0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-11-05 10:32 UTC (permalink / raw)
To: Jiri Olsa, Alexei Starovoitov, Daniel Borkmann
Cc: bpf, linux-mm, Andrii Nakryiko, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Lorenzo Stoakes, John Fastabend,
Federico Di Pierro
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] selftests/bpf: kfunc_call_test_const_mem() fails due to MEM_WRITE in verifier
--
commit 0e976b113cb623dc07d1d416bf2badf98c0f26f7
Author: Jiri Olsa <jolsa@kernel.org>
selftests/bpf: Test const kfunc memory arguments
This commit adds BPF selftests and bpf_testmod kfuncs to verify passing
read-only global data, writable stack data, and const scalar structs to
const-qualified kfunc memory arguments, while rejecting read-only data
passed to writable memory arguments.
> diff --git a/tools/testing/selftests/bpf/progs/kfunc_call_test.c b/tools/testing/selftests/bpf/progs/kfunc_call_test.c
> index 8e6560c31e781..4f4a6713aa6e5 100644
> --- a/tools/testing/selftests/bpf/progs/kfunc_call_test.c
> +++ b/tools/testing/selftests/bpf/progs/kfunc_call_test.c
> @@ -5,6 +5,9 @@
> #include "bpf_misc.h"
> #include "../test_kmods/bpf_testmod_kfunc.h"
>
> +static const char kfunc_const_data[] = "read-only source";
> +static const struct prog_test_pass1 kfunc_const_struct = { .x0 = 1 };
> +
> static struct bpf_spin_lock kfunc_call_lock SEC(".data.A");
>
> SEC("tc")
> @@ -208,6 +211,17 @@ int kfunc_call_test_pass(struct __sk_buff *skb)
> return 0;
> }
>
> +SEC("tc")
> +int kfunc_call_test_const_mem(struct __sk_buff *skb)
> +{
> + char stack_data[] = "read-write";
> +
> + bpf_kfunc_call_test_const_mem(kfunc_const_data, sizeof(kfunc_const_data));
> + bpf_kfunc_call_test_const_mem(stack_data, sizeof(stack_data));
> + bpf_kfunc_call_test_const_struct(&kfunc_const_struct);
> + return 0;
> +}
[Severity: Medium]
Does kfunc_call_test_const_mem() pass verification without a corresponding
verifier change to preserve const qualification on kfunc memory arguments?
In kernel/bpf/verifier.c, get_kfunc_arg_type() strips modifiers from the
pointed-to type via btf_type_skip_modifiers() and unconditionally marks
pointer+size and fixed-size memory arguments as writable:
kernel/bpf/verifier.c:get_kfunc_arg_type() {
ref_id_ptr = &t->type;
ref_t = btf_type_skip_modifiers(meta->btf, *ref_id_ptr, &ref_id);
while (*ref_id_ptr != ref_id)
ref_id_ptr = &btf_type_by_id(meta->btf, *ref_id_ptr)->type;
...
} else if (arg + 1 < nargs &&
(is_kfunc_arg_mem_size(meta->btf, &args[arg + 1]) ||
is_kfunc_arg_const_mem_size(meta->btf, &args[arg + 1]))) {
...
arg_type = ARG_PTR_TO_MEM | MEM_WRITE;
} else if (btf_type_is_struct(ref_t))
arg_type = ARG_PTR_TO_BTF_ID;
else {
...
arg_type = ARG_PTR_TO_MEM | MEM_FIXED_SIZE | MEM_WRITE;
}
}
Similarly, for bpf_kfunc_call_test_const_struct(), get_kfunc_arg_type()
records the argument as a BTF struct pointer with proto->arg_btf_id[arg]
pointing past the const modifier, and resolve_func_arg_type() then rewrites
the scalar struct argument to writable memory:
kernel/bpf/verifier.c:resolve_func_arg_type() {
...
*arg_type = ARG_PTR_TO_MEM | MEM_FIXED_SIZE | MEM_WRITE |
(*arg_type & (PTR_MAYBE_NULL | MEM_UNINIT));
}
When kfunc_call_test_const_mem() passes the .rodata globals kfunc_const_data
and &kfunc_const_struct to bpf_kfunc_call_test_const_mem() and
bpf_kfunc_call_test_const_struct(), check_func_arg() requests write access
via func_arg_access_type() -> check_helper_mem_access() ->
check_map_access_type(), which rejects the program with "write into map
forbidden".
Because kfunc_call_test_const_mem() is an autoloaded program in
kfunc_call_test.c, won't this also cause kfunc_call_test__open_and_load()
and kfunc_call_test_lskel__open_and_load() in verify_success() to fail for
all subtests using the kfunc_call_test skeleton?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261105102343.311331-5-jolsa@kernel.org?part=1
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCHv3 bpf-next 5/8] selftests/bpf: Test user memory mmap kfuncs
2026-11-05 10:23 [PATCHv3 bpf-next 0/8] bpf: Add user memory kfuncs Jiri Olsa
` (3 preceding siblings ...)
2026-11-05 10:23 ` [PATCHv3 bpf-next 4/8] selftests/bpf: Test const kfunc memory arguments Jiri Olsa
@ 2026-11-05 10:23 ` Jiri Olsa
2026-11-05 10:33 ` sashiko-bot
2026-11-05 10:23 ` [PATCHv3 bpf-next 6/8] selftests/bpf: Test user memory page exec Jiri Olsa
` (2 subsequent siblings)
7 siblings, 1 reply; 13+ messages in thread
From: Jiri Olsa @ 2026-11-05 10:23 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann
Cc: bpf, linux-mm, Andrii Nakryiko, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Lorenzo Stoakes, John Fastabend,
Federico Di Pierro
Add BPF selftests for bpf_user_anon_mmap and bpf_user_munmap from
a sleepable uprobe. Check that created mappings have the requested
permissions and that unmapped addresses are no longer mapped.
Assisted-by: Codex:GPT-6
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
.../selftests/bpf/prog_tests/user_mem.c | 111 ++++++++++++++++++
tools/testing/selftests/bpf/progs/user_mem.c | 43 +++++++
2 files changed, 154 insertions(+)
create mode 100644 tools/testing/selftests/bpf/prog_tests/user_mem.c
create mode 100644 tools/testing/selftests/bpf/progs/user_mem.c
diff --git a/tools/testing/selftests/bpf/prog_tests/user_mem.c b/tools/testing/selftests/bpf/prog_tests/user_mem.c
new file mode 100644
index 000000000000..eadf668f7056
--- /dev/null
+++ b/tools/testing/selftests/bpf/prog_tests/user_mem.c
@@ -0,0 +1,111 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#include <test_progs.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include "user_mem.skel.h"
+#include "trace_helpers.h"
+
+enum {
+ USER_MEM_MMAP = 1,
+ USER_MEM_UNMAP,
+};
+
+static noinline void user_mem_trigger(void)
+{
+ asm volatile ("" ::: "memory");
+}
+
+static bool check_user_mem_vma(void *addr, unsigned long size, const char *perms)
+{
+ size_t start, end;
+ char vma_perms[5];
+ int err;
+
+ err = get_procmap_info(addr, &start, &end, vma_perms);
+ if (!ASSERT_OK(err, "user_mem_vma"))
+ return false;
+
+ /* VMA can be merged, so ensure it covers at least the requested range. */
+ if (!ASSERT_GE(end - (size_t)addr, size, "vma_size"))
+ return false;
+
+ return ASSERT_STREQ(vma_perms, perms, "vma_perms");
+}
+
+static bool check_user_mem_unmapped(void *addr)
+{
+ size_t start, end;
+ int err;
+
+ err = get_procmap_info(addr, &start, &end, NULL);
+ return ASSERT_EQ(err, -ESRCH, "user_mem_unmapped");
+}
+
+static bool user_mem_mmap(struct user_mem *skel, unsigned long len,
+ unsigned long prot, unsigned long *addr)
+{
+ skel->bss->map_len = len;
+ skel->bss->map_prot = prot;
+ skel->bss->action = USER_MEM_MMAP;
+ user_mem_trigger();
+
+ /* vm_mmap encodes errors in the top MAX_ERRNO unsigned-long values. */
+ if (!ASSERT_LT(skel->bss->user_addr, -4095UL, "user_addr"))
+ return false;
+
+ *addr = skel->bss->user_addr;
+ return true;
+}
+
+static bool user_mem_munmap(struct user_mem *skel, unsigned long addr,
+ unsigned long len)
+{
+ skel->bss->user_addr = addr;
+ skel->bss->map_len = len;
+ skel->bss->action = USER_MEM_UNMAP;
+ user_mem_trigger();
+
+ return ASSERT_OK(skel->bss->ret, "user_munmap");
+}
+
+static void test_mapping(struct user_mem *skel)
+{
+ long page_size = sysconf(_SC_PAGESIZE);
+ const unsigned long len = page_size * 3;
+ unsigned long addr = ~0UL;
+
+ if (!user_mem_mmap(skel, len, PROT_READ | PROT_WRITE, &addr))
+ goto out;
+ if (!check_user_mem_vma((void *)addr, len, "rw-p"))
+ goto out;
+ if (!user_mem_munmap(skel, addr, len))
+ goto out;
+ if (!check_user_mem_unmapped((void *)addr))
+ goto out;
+ addr = ~0UL;
+
+out:
+ if (addr != ~0UL)
+ munmap((void *)addr, len);
+}
+
+void test_user_mem(void)
+{
+ struct user_mem *skel;
+
+ skel = user_mem__open_and_load();
+ if (!ASSERT_OK_PTR(skel, "open_and_load"))
+ return;
+
+ skel->bss->monitored_pid = getpid();
+ if (!ASSERT_OK(user_mem__attach(skel), "attach"))
+ goto out;
+
+ if (test__start_subtest("mapping"))
+ test_mapping(skel);
+
+out:
+ user_mem__destroy(skel);
+}
diff --git a/tools/testing/selftests/bpf/progs/user_mem.c b/tools/testing/selftests/bpf/progs/user_mem.c
new file mode 100644
index 000000000000..e3b0689f5f2a
--- /dev/null
+++ b/tools/testing/selftests/bpf/progs/user_mem.c
@@ -0,0 +1,43 @@
+// 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";
+
+extern unsigned long bpf_user_anon_mmap(unsigned long len, unsigned long prot) __ksym;
+extern int bpf_user_munmap(unsigned long addr, unsigned long len) __ksym;
+
+enum {
+ USER_MEM_MMAP = 1,
+ USER_MEM_UNMAP,
+};
+
+int monitored_pid;
+int action;
+int ret;
+
+unsigned long user_addr;
+unsigned long map_len;
+unsigned long map_prot;
+
+SEC("uprobe.s//proc/self/exe:user_mem_trigger")
+int user_mem_test(struct pt_regs *ctx)
+{
+ if ((bpf_get_current_pid_tgid() >> 32) != monitored_pid)
+ return 0;
+
+ switch (action) {
+ case USER_MEM_MMAP:
+ user_addr = bpf_user_anon_mmap(map_len, map_prot);
+ break;
+ case USER_MEM_UNMAP:
+ ret = bpf_user_munmap(user_addr, map_len);
+ break;
+ }
+
+ return 0;
+}
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* Re: [PATCHv3 bpf-next 5/8] selftests/bpf: Test user memory mmap kfuncs
2026-11-05 10:23 ` [PATCHv3 bpf-next 5/8] selftests/bpf: Test user memory mmap kfuncs Jiri Olsa
@ 2026-11-05 10:33 ` sashiko-bot
0 siblings, 0 replies; 13+ messages in thread
From: sashiko-bot @ 2026-11-05 10:33 UTC (permalink / raw)
To: Jiri Olsa, Alexei Starovoitov, Daniel Borkmann
Cc: bpf, linux-mm, Andrii Nakryiko, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Lorenzo Stoakes, John Fastabend,
Federico Di Pierro
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:
- [Medium] selftests/bpf: call to undefined get_procmap_info() breaks build
- [Medium] selftests/bpf: user_mem_test() calls non-existent user mmap kfuncs
--
commit 602c938f4e2233efed73c162a7e2295814283b95
Author: Jiri Olsa <jolsa@kernel.org>
selftests/bpf: Test user memory mmap kfuncs
This commit adds BPF selftests for bpf_user_anon_mmap() and bpf_user_munmap()
invoked from a sleepable uprobe, checking that created mappings have the
expected permissions and that unmapped addresses are removed.
> diff --git a/tools/testing/selftests/bpf/prog_tests/user_mem.c b/tools/testing/selftests/bpf/prog_tests/user_mem.c
> new file mode 100644
> index 0000000000000..eadf668f7056b
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/prog_tests/user_mem.c
> @@ -0,0 +1,111 @@
> +// SPDX-License-Identifier: GPL-2.0
> +
> +#include <test_progs.h>
> +#include <sys/mman.h>
> +#include <unistd.h>
> +
> +#include "user_mem.skel.h"
> +#include "trace_helpers.h"
[ ... ]
> +static bool check_user_mem_vma(void *addr, unsigned long size, const char *perms)
> +{
> + size_t start, end;
> + char vma_perms[5];
> + int err;
> +
> + err = get_procmap_info(addr, &start, &end, vma_perms);
> + if (!ASSERT_OK(err, "user_mem_vma"))
> + return false;
> +
> + /* VMA can be merged, so ensure it covers at least the requested range. */
> + if (!ASSERT_GE(end - (size_t)addr, size, "vma_size"))
> + return false;
> +
> + return ASSERT_STREQ(vma_perms, perms, "vma_perms");
> +}
> +
> +static bool check_user_mem_unmapped(void *addr)
> +{
> + size_t start, end;
> + int err;
> +
> + err = get_procmap_info(addr, &start, &end, NULL);
> + return ASSERT_EQ(err, -ESRCH, "user_mem_unmapped");
> +}
[Severity: Medium]
Was a prerequisite patch adding get_procmap_info() omitted from this series?
Both check_user_mem_vma() and check_user_mem_unmapped() call
get_procmap_info(), and tools/testing/selftests/bpf/prog_tests/user_mem.c
includes trace_helpers.h, but neither tools/testing/selftests/bpf/trace_helpers.h
nor tools/testing/selftests/bpf/trace_helpers.c declares or defines
get_procmap_info().
Because the BPF selftests are compiled with -Werror, does this cause building
test_progs to fail with an implicit function declaration error and an
undefined reference at link time?
[ ... ]
> diff --git a/tools/testing/selftests/bpf/progs/user_mem.c b/tools/testing/selftests/bpf/progs/user_mem.c
> new file mode 100644
> index 0000000000000..e3b0689f5f2a5
> --- /dev/null
> +++ b/tools/testing/selftests/bpf/progs/user_mem.c
> @@ -0,0 +1,43 @@
> +// 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";
> +
> +extern unsigned long bpf_user_anon_mmap(unsigned long len, unsigned long prot) __ksym;
> +extern int bpf_user_munmap(unsigned long addr, unsigned long len) __ksym;
[ ... ]
> +SEC("uprobe.s//proc/self/exe:user_mem_trigger")
> +int user_mem_test(struct pt_regs *ctx)
> +{
> + if ((bpf_get_current_pid_tgid() >> 32) != monitored_pid)
> + return 0;
> +
> + switch (action) {
> + case USER_MEM_MMAP:
> + user_addr = bpf_user_anon_mmap(map_len, map_prot);
> + break;
> + case USER_MEM_UNMAP:
> + ret = bpf_user_munmap(user_addr, map_len);
> + break;
> + }
[Severity: Medium]
Are the kernel definitions for bpf_user_anon_mmap() and bpf_user_munmap()
missing from the tree?
In tools/testing/selftests/bpf/progs/user_mem.c, user_mem_test() invokes
bpf_user_anon_mmap() and bpf_user_munmap() declared as strong __ksym externs,
and test_user_mem() in tools/testing/selftests/bpf/prog_tests/user_mem.c
loads the program via user_mem__open_and_load():
skel = user_mem__open_and_load();
if (!ASSERT_OK_PTR(skel, "open_and_load"))
return;
Since neither bpf_user_anon_mmap nor bpf_user_munmap is defined or registered
in any BTF kfunc set in the kernel or selftest kernel modules, won't
user_mem__open_and_load() fail unconditionally when libbpf tries to resolve
their BTF IDs?
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261105102343.311331-6-jolsa@kernel.org?part=1
^ permalink raw reply [flat|nested] 13+ messages in thread
* [PATCHv3 bpf-next 6/8] selftests/bpf: Test user memory page exec
2026-11-05 10:23 [PATCHv3 bpf-next 0/8] bpf: Add user memory kfuncs Jiri Olsa
` (4 preceding siblings ...)
2026-11-05 10:23 ` [PATCHv3 bpf-next 5/8] selftests/bpf: Test user memory mmap kfuncs Jiri Olsa
@ 2026-11-05 10:23 ` Jiri Olsa
2026-11-05 10:23 ` [PATCHv3 bpf-next 7/8] selftests/bpf: Test bpf_copy_to_user kfunc Jiri Olsa
2026-11-05 10:23 ` [PATCHv3 bpf-next 8/8] selftests/bpf: Test user memory kfunc errors Jiri Olsa
7 siblings, 0 replies; 13+ messages in thread
From: Jiri Olsa @ 2026-11-05 10:23 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann
Cc: bpf, linux-mm, Andrii Nakryiko, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Lorenzo Stoakes, John Fastabend,
Federico Di Pierro
Use bpf_copy_to_user from a uprobe to write architecture-specific
instructions into user memory, then execute them on x86-64 and arm64.
Test execution from a mapping created with read and execute permissions,
and from individual pages made executable with mprotect.
Assisted-by: Codex:GPT-6
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
.../selftests/bpf/prog_tests/user_mem.c | 75 +++++++++++++++++++
tools/testing/selftests/bpf/progs/user_mem.c | 22 ++++++
2 files changed, 97 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/user_mem.c b/tools/testing/selftests/bpf/prog_tests/user_mem.c
index eadf668f7056..e1748ffb1edf 100644
--- a/tools/testing/selftests/bpf/prog_tests/user_mem.c
+++ b/tools/testing/selftests/bpf/prog_tests/user_mem.c
@@ -10,6 +10,7 @@
enum {
USER_MEM_MMAP = 1,
USER_MEM_UNMAP,
+ USER_MEM_COPY_INSNS,
};
static noinline void user_mem_trigger(void)
@@ -91,6 +92,76 @@ static void test_mapping(struct user_mem *skel)
munmap((void *)addr, len);
}
+static bool user_mem_copy_insns(struct user_mem *skel, unsigned long addr)
+{
+ skel->bss->user_addr = addr;
+ skel->bss->action = USER_MEM_COPY_INSNS;
+ user_mem_trigger();
+
+ return ASSERT_OK(skel->bss->ret, "user_write");
+}
+
+static void test_execute_user_page(struct user_mem *skel)
+{
+ long page_size = sysconf(_SC_PAGESIZE);
+ unsigned long addr = ~0UL;
+
+#if !defined(__x86_64__) && !defined(__aarch64__)
+ test__skip();
+ return;
+#endif
+
+ if (!user_mem_mmap(skel, page_size, PROT_READ | PROT_EXEC, &addr))
+ return;
+ if (!check_user_mem_vma((void *)addr, page_size, "r-xp"))
+ goto out;
+ if (!user_mem_copy_insns(skel, addr))
+ goto out;
+
+ /* Execute user page. */
+ ASSERT_EQ(((int (*)(void))addr)(), 42, "execute_user_page");
+
+out:
+ if (addr != ~0UL)
+ munmap((void *)addr, page_size);
+}
+
+static void test_partial_exec(struct user_mem *skel)
+{
+ long page_size = sysconf(_SC_PAGESIZE);
+ unsigned long addr = ~0UL;
+
+#if !defined(__x86_64__) && !defined(__aarch64__)
+ test__skip();
+ return;
+#endif
+
+ if (!user_mem_mmap(skel, page_size * 3, PROT_READ | PROT_WRITE, &addr))
+ return;
+
+ /* Copy instructions to middle page. */
+ if (!user_mem_copy_insns(skel, addr + page_size))
+ goto out;
+
+ /* Set +x to middle page. */
+ if (!ASSERT_OK(mprotect((void *)(addr + page_size), page_size,
+ PROT_READ | PROT_EXEC), "middle_page_mprotect"))
+ goto out;
+
+ if (!check_user_mem_vma((void *)addr, page_size, "rw-p") ||
+ !check_user_mem_vma((void *)(addr + page_size), page_size, "r-xp") ||
+ !check_user_mem_vma((void *)(addr + 2 * page_size), page_size, "rw-p"))
+ goto out;
+
+ /* Execute middle page. */
+ if (!ASSERT_EQ(((int (*)(void))(addr + page_size))(), 42, "execute_middle_page"))
+ goto out;
+
+out:
+ if (addr != ~0UL)
+ munmap((void *)addr, page_size * 3);
+}
+
void test_user_mem(void)
{
struct user_mem *skel;
@@ -105,6 +176,10 @@ void test_user_mem(void)
if (test__start_subtest("mapping"))
test_mapping(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_mem__destroy(skel);
diff --git a/tools/testing/selftests/bpf/progs/user_mem.c b/tools/testing/selftests/bpf/progs/user_mem.c
index e3b0689f5f2a..3bbd5d2f02d6 100644
--- a/tools/testing/selftests/bpf/progs/user_mem.c
+++ b/tools/testing/selftests/bpf/progs/user_mem.c
@@ -10,10 +10,13 @@ char _license[] SEC("license") = "GPL";
extern unsigned long bpf_user_anon_mmap(unsigned long len, unsigned long prot) __ksym;
extern int bpf_user_munmap(unsigned long addr, unsigned long len) __ksym;
+extern int bpf_copy_to_user(void *unsafe_ptr__ign, const void *src,
+ u32 src__sz, u64 flags) __ksym;
enum {
USER_MEM_MMAP = 1,
USER_MEM_UNMAP,
+ USER_MEM_COPY_INSNS,
};
int monitored_pid;
@@ -24,6 +27,22 @@ unsigned long user_addr;
unsigned long map_len;
unsigned long map_prot;
+#if defined(__TARGET_ARCH_x86)
+static const unsigned char insns[] = {
+ 0xf3, 0x0f, 0x1e, 0xfa, /* endbr64 */
+ 0xb8, 42, 0, 0, 0, 0xc3, /* mov $42, %eax; ret */
+};
+#elif defined(__TARGET_ARCH_arm64)
+static const unsigned char insns[] = {
+ 0x5f, 0x24, 0x03, 0xd5, /* bti c */
+ 0x40, 0x05, 0x80, 0x52, /* mov w0, #42 */
+ 0xc0, 0x03, 0x5f, 0xd6, /* ret */
+};
+#else
+/* The execute tests are skipped on other architectures. */
+static const unsigned char insns[] = { 0 };
+#endif
+
SEC("uprobe.s//proc/self/exe:user_mem_trigger")
int user_mem_test(struct pt_regs *ctx)
{
@@ -37,6 +56,9 @@ int user_mem_test(struct pt_regs *ctx)
case USER_MEM_UNMAP:
ret = bpf_user_munmap(user_addr, map_len);
break;
+ case USER_MEM_COPY_INSNS:
+ ret = bpf_copy_to_user((void *)user_addr, insns, sizeof(insns), 0);
+ break;
}
return 0;
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* [PATCHv3 bpf-next 7/8] selftests/bpf: Test bpf_copy_to_user kfunc
2026-11-05 10:23 [PATCHv3 bpf-next 0/8] bpf: Add user memory kfuncs Jiri Olsa
` (5 preceding siblings ...)
2026-11-05 10:23 ` [PATCHv3 bpf-next 6/8] selftests/bpf: Test user memory page exec Jiri Olsa
@ 2026-11-05 10:23 ` Jiri Olsa
2026-11-05 10:23 ` [PATCHv3 bpf-next 8/8] selftests/bpf: Test user memory kfunc errors Jiri Olsa
7 siblings, 0 replies; 13+ messages in thread
From: Jiri Olsa @ 2026-11-05 10:23 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann
Cc: bpf, linux-mm, Andrii Nakryiko, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Lorenzo Stoakes, John Fastabend,
Federico Di Pierro
Test that bpf_copy_to_user can fault in a paged-out page and write to a
read-only mapping. Also verify that a partial copy crossing into an unmapped
page returns -EFAULT while leaving the copied byte in place.
Assisted-by: Codex:GPT-6
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
.../selftests/bpf/prog_tests/user_mem.c | 106 ++++++++++++++++++
tools/testing/selftests/bpf/progs/user_mem.c | 17 +++
2 files changed, 123 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/user_mem.c b/tools/testing/selftests/bpf/prog_tests/user_mem.c
index e1748ffb1edf..f13aff73c9bf 100644
--- a/tools/testing/selftests/bpf/prog_tests/user_mem.c
+++ b/tools/testing/selftests/bpf/prog_tests/user_mem.c
@@ -11,8 +11,11 @@ enum {
USER_MEM_MMAP = 1,
USER_MEM_UNMAP,
USER_MEM_COPY_INSNS,
+ USER_MEM_COPY,
};
+static const char data[] = "fault-enabled BPF write";
+
static noinline void user_mem_trigger(void)
{
asm volatile ("" ::: "memory");
@@ -101,6 +104,18 @@ static bool user_mem_copy_insns(struct user_mem *skel, unsigned long addr)
return ASSERT_OK(skel->bss->ret, "user_write");
}
+static int user_mem_copy(struct user_mem *skel, unsigned long addr,
+ u32 size, u64 flags)
+{
+ skel->bss->user_addr = addr;
+ skel->bss->copy_size = size;
+ skel->bss->copy_flags = flags;
+ skel->bss->action = USER_MEM_COPY;
+ user_mem_trigger();
+
+ return skel->bss->ret;
+}
+
static void test_execute_user_page(struct user_mem *skel)
{
long page_size = sysconf(_SC_PAGESIZE);
@@ -162,6 +177,91 @@ static void test_partial_exec(struct user_mem *skel)
munmap((void *)addr, page_size * 3);
}
+static void test_faultable_write(struct user_mem *skel)
+{
+ long page_size = sysconf(_SC_PAGESIZE);
+ unsigned long addr = ~0UL;
+ unsigned char vec;
+ int i;
+
+ if (!user_mem_mmap(skel, page_size, PROT_READ | PROT_WRITE, &addr))
+ return;
+
+ /* Make sure there is a private resident page to page out. */
+ memset((void *)addr, 0, page_size);
+
+ /* Retry pageout until mincore confirms that the page is no longer resident. */
+ for (i = 0; i < 500; i++) {
+ if (madvise((void *)addr, page_size, MADV_PAGEOUT)) {
+ test__skip();
+ goto out;
+ }
+ if (!ASSERT_OK(mincore((void *)addr, page_size, &vec), "mincore"))
+ goto out;
+ if (!(vec & 1))
+ break;
+ usleep(100);
+ }
+ if (i == 500) {
+ test__skip();
+ goto out;
+ }
+
+ /* ..and do the copy to it. */
+ if (!ASSERT_OK(user_mem_copy(skel, addr, sizeof(data), 0), "user_write_data"))
+ goto out;
+
+ ASSERT_EQ(memcmp((void *)addr, data, sizeof(data)), 0, "written_data");
+
+out:
+ if (addr != ~0UL)
+ munmap((void *)addr, page_size);
+}
+
+static void test_copy_to_readonly_mapping(struct user_mem *skel)
+{
+ long page_size = sysconf(_SC_PAGESIZE);
+ unsigned long addr = ~0UL;
+
+ if (!user_mem_mmap(skel, page_size, PROT_READ, &addr))
+ return;
+
+ /* Assert we can copy to read-only mapping. */
+ if (!ASSERT_OK(user_mem_copy(skel, addr, sizeof(data), 0), "readonly_copy"))
+ goto out;
+
+ ASSERT_EQ(memcmp((void *)addr, data, sizeof(data)), 0, "readonly_copy_data");
+
+out:
+ if (addr != ~0UL)
+ munmap((void *)addr, page_size);
+}
+
+static void test_partial_copy_fault(struct user_mem *skel)
+{
+ long page_size = sysconf(_SC_PAGESIZE);
+ bool second_page_unmapped = false;
+ unsigned long addr = ~0UL;
+
+ if (!user_mem_mmap(skel, page_size * 2, PROT_READ | PROT_WRITE, &addr))
+ return;
+
+ if (!user_mem_munmap(skel, addr + page_size, page_size))
+ goto out;
+
+ second_page_unmapped = true;
+
+ ASSERT_EQ(user_mem_copy(skel, addr + page_size - 1, sizeof(data), 0),
+ -EFAULT, "partial_copy");
+
+ /* The byte before the fault stays written. */
+ ASSERT_EQ(((char *)addr)[page_size - 1], 'f', "partial_copy_data");
+
+out:
+ if (addr != ~0UL)
+ munmap((void *)addr, second_page_unmapped ? page_size : page_size * 2);
+}
+
void test_user_mem(void)
{
struct user_mem *skel;
@@ -180,6 +280,12 @@ void test_user_mem(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);
+ if (test__start_subtest("copy_to_readonly_mapping"))
+ test_copy_to_readonly_mapping(skel);
+ if (test__start_subtest("partial_copy_fault"))
+ test_partial_copy_fault(skel);
out:
user_mem__destroy(skel);
diff --git a/tools/testing/selftests/bpf/progs/user_mem.c b/tools/testing/selftests/bpf/progs/user_mem.c
index 3bbd5d2f02d6..e12a19fc8784 100644
--- a/tools/testing/selftests/bpf/progs/user_mem.c
+++ b/tools/testing/selftests/bpf/progs/user_mem.c
@@ -17,6 +17,7 @@ enum {
USER_MEM_MMAP = 1,
USER_MEM_UNMAP,
USER_MEM_COPY_INSNS,
+ USER_MEM_COPY,
};
int monitored_pid;
@@ -27,6 +28,9 @@ unsigned long user_addr;
unsigned long map_len;
unsigned long map_prot;
+u64 copy_flags;
+u32 copy_size;
+
#if defined(__TARGET_ARCH_x86)
static const unsigned char insns[] = {
0xf3, 0x0f, 0x1e, 0xfa, /* endbr64 */
@@ -43,9 +47,13 @@ static const unsigned char insns[] = {
static const unsigned char insns[] = { 0 };
#endif
+static const char data[] = "fault-enabled BPF write";
+
SEC("uprobe.s//proc/self/exe:user_mem_trigger")
int user_mem_test(struct pt_regs *ctx)
{
+ u32 size;
+
if ((bpf_get_current_pid_tgid() >> 32) != monitored_pid)
return 0;
@@ -59,6 +67,15 @@ int user_mem_test(struct pt_regs *ctx)
case USER_MEM_COPY_INSNS:
ret = bpf_copy_to_user((void *)user_addr, insns, sizeof(insns), 0);
break;
+ case USER_MEM_COPY:
+ /* Bound the kfunc length using a local copy of copy_size. */
+ size = copy_size;
+ if (size > sizeof(data)) {
+ ret = -22; /* -EINVAL */
+ break;
+ }
+ ret = bpf_copy_to_user((void *)user_addr, data, size, copy_flags);
+ break;
}
return 0;
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread* [PATCHv3 bpf-next 8/8] selftests/bpf: Test user memory kfunc errors
2026-11-05 10:23 [PATCHv3 bpf-next 0/8] bpf: Add user memory kfuncs Jiri Olsa
` (6 preceding siblings ...)
2026-11-05 10:23 ` [PATCHv3 bpf-next 7/8] selftests/bpf: Test bpf_copy_to_user kfunc Jiri Olsa
@ 2026-11-05 10:23 ` Jiri Olsa
7 siblings, 0 replies; 13+ messages in thread
From: Jiri Olsa @ 2026-11-05 10:23 UTC (permalink / raw)
To: Alexei Starovoitov, Daniel Borkmann
Cc: bpf, linux-mm, Andrii Nakryiko, Eduard Zingerman,
Kumar Kartikeya Dwivedi, Lorenzo Stoakes, John Fastabend,
Federico Di Pierro
Test invalid copy arguments, zero-length mapping operations, partial copy
faults, and copying to read-only mappings. Verify that a failed unmap
leaves the mapping intact, and that non-sleepable programs and programs
without CAP_SYS_ADMIN cannot call bpf_user_anon_mmap.
Assisted-by: Codex:GPT-6
Signed-off-by: Jiri Olsa <jolsa@kernel.org>
---
.../selftests/bpf/prog_tests/user_mem.c | 57 +++++++++++++++++++
tools/testing/selftests/bpf/progs/user_mem.c | 19 +++++++
2 files changed, 76 insertions(+)
diff --git a/tools/testing/selftests/bpf/prog_tests/user_mem.c b/tools/testing/selftests/bpf/prog_tests/user_mem.c
index f13aff73c9bf..f1b88bba71c9 100644
--- a/tools/testing/selftests/bpf/prog_tests/user_mem.c
+++ b/tools/testing/selftests/bpf/prog_tests/user_mem.c
@@ -262,10 +262,59 @@ static void test_partial_copy_fault(struct user_mem *skel)
munmap((void *)addr, second_page_unmapped ? page_size : page_size * 2);
}
+static void test_zero_length_mapping(struct user_mem *skel)
+{
+ skel->bss->map_len = 0;
+ skel->bss->map_prot = PROT_READ | PROT_WRITE;
+ skel->bss->action = USER_MEM_MMAP;
+ user_mem_trigger();
+
+ ASSERT_EQ(skel->bss->user_addr, (unsigned long)-EINVAL, "zero_length_mapping");
+}
+
+static void test_failed_unmap(struct user_mem *skel)
+{
+ long page_size = sysconf(_SC_PAGESIZE);
+ unsigned long addr = ~0UL;
+
+ if (!user_mem_mmap(skel, page_size, PROT_READ | PROT_WRITE, &addr))
+ return;
+
+ skel->bss->user_addr = addr;
+ skel->bss->map_len = 0;
+ skel->bss->action = USER_MEM_UNMAP;
+ user_mem_trigger();
+
+ if (ASSERT_EQ(skel->bss->ret, -EINVAL, "zero_length_unmap"))
+ check_user_mem_vma((void *)addr, page_size, "rw-p");
+
+ munmap((void *)addr, page_size);
+}
+
+static void test_invalid_copy(struct user_mem *skel)
+{
+ ASSERT_EQ(user_mem_copy(skel, 1, sizeof(data), 0), -EFAULT, "bad_copy");
+ ASSERT_EQ(user_mem_copy(skel, 1, sizeof(data), 1), -EINVAL, "bad_copy_flags");
+ ASSERT_EQ(user_mem_copy(skel, 1, 0, 0), 0, "zero_length_copy");
+ ASSERT_EQ(user_mem_copy(skel, 1, 0, 1), -EINVAL, "zero_length_copy_bad_flags");
+}
+
+static void test_invalid_prot(struct user_mem *skel)
+{
+ skel->bss->map_len = sysconf(_SC_PAGESIZE);
+ skel->bss->map_prot = 0x80000000;
+ skel->bss->action = USER_MEM_MMAP;
+ user_mem_trigger();
+
+ ASSERT_EQ(skel->bss->user_addr, -EINVAL, "invalid_prot");
+}
+
void test_user_mem(void)
{
struct user_mem *skel;
+ RUN_TESTS(user_mem);
+
skel = user_mem__open_and_load();
if (!ASSERT_OK_PTR(skel, "open_and_load"))
return;
@@ -286,6 +335,14 @@ void test_user_mem(void)
test_copy_to_readonly_mapping(skel);
if (test__start_subtest("partial_copy_fault"))
test_partial_copy_fault(skel);
+ if (test__start_subtest("failed_unmap"))
+ test_failed_unmap(skel);
+ if (test__start_subtest("invalid_copy"))
+ test_invalid_copy(skel);
+ if (test__start_subtest("invalid_prot"))
+ test_invalid_prot(skel);
+ if (test__start_subtest("zero_length_mapping"))
+ test_zero_length_mapping(skel);
out:
user_mem__destroy(skel);
diff --git a/tools/testing/selftests/bpf/progs/user_mem.c b/tools/testing/selftests/bpf/progs/user_mem.c
index e12a19fc8784..6d397fe1b3a9 100644
--- a/tools/testing/selftests/bpf/progs/user_mem.c
+++ b/tools/testing/selftests/bpf/progs/user_mem.c
@@ -80,3 +80,22 @@ int user_mem_test(struct pt_regs *ctx)
return 0;
}
+
+SEC("?uprobe//proc/self/exe:user_mem_trigger")
+__failure
+__msg("program must be sleepable to call sleepable kfunc bpf_user_anon_mmap")
+__msg("cannot be used in non-sleepable program")
+int user_mem_nonsleepable(struct pt_regs *ctx)
+{
+ bpf_user_anon_mmap(4096, 1);
+ return 0;
+}
+
+SEC("?uprobe.s//proc/self/exe:user_mem_trigger")
+__failure_unpriv __caps_unpriv(CAP_BPF | CAP_PERFMON)
+__msg_unpriv("calling kernel function bpf_user_anon_mmap is not allowed")
+int user_mem_no_sysadmin(struct pt_regs *ctx)
+{
+ bpf_user_anon_mmap(4096, 1);
+ return 0;
+}
--
2.43.0
^ permalink raw reply related [flat|nested] 13+ messages in thread