mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [RFC PATCH 0/6] um: introduce pidfd_mmap()/pidfd_munmap() syscalls
@ 2026-07-10 20:53 Cong Wang
  2026-07-10 20:53 ` [RFC PATCH 1/6] pidfd: add " Cong Wang
                   ` (5 more replies)
  0 siblings, 6 replies; 7+ messages in thread
From: Cong Wang @ 2026-07-10 20:53 UTC (permalink / raw)
  To: Richard Weinberger, Anton Ivanov, Johannes Berg
  Cc: Benjamin Berg, linux-um, linux-kernel, Cong Wang

This RFC adds two syscalls, pidfd_mmap() and pidfd_munmap(), that let a
supervisor install or remove a mapping in another process's address space
without target-side cooperation, and converts User Mode Linux to use them.

  pidfd_mmap(int pidfd, struct pidfd_mmap_args *uargs, unsigned int flags)
  pidfd_munmap(int pidfd, unsigned long addr, unsigned long len)

The backing fd (if any) is resolved in the *caller's* fd table, mirroring
pidfd_getfd()'s cross-task install model. Both are gated by
ptrace_may_access(PTRACE_MODE_ATTACH_REALCREDS); LSM/fsnotify hooks run
against the caller. pidfd_mmap() takes an extensible, size-versioned
argument struct (clone3/openat2 style) since an mmap-shaped call exceeds
the 6-argument syscall limit.

They build on the mm-side remote-install primitives vm_mmap_remote()/
vm_munmap_remote(), posted separately as:

  https://lore.kernel.org/all/20260704231831.354543-2-xiyou.wangcong@gmail.com/

UML is the motivating user. Its SKAS monitor previously installed guest
mappings by driving the stub to execute mmap/munmap itself: in seccomp
mode it passed the physmem fd to the stub over SCM_RIGHTS, and in ptrace
mode it batched STUB_SYSCALL_MMAP/MUNMAP entries for the stub to run.
pidfd_mmap() lets the monitor install mappings into the stub's mm
directly, so the stub never touches the fd or executes mmap/munmap. With
both modes converted, the entire stub-syscall batcher and the SCM_RIGHTS
fd-passing become dead code and are removed (net ~530 lines). The ptrace
conversion (patch 5) is included mainly to demonstrate the primitive's
reach; the security caveats pidfd_mmap() does *not* address (e.g. a guest
blocking SIGALRM to dodge scheduling) are preserved in stub.c.

As this is an RFC, the UML side _intentionally_ does not handle host
portability: it assumes a host kernel that provides pidfd_mmap() and
deliberately omits any detection or fallback for older hosts (which would
otherwise pick the ptrace path, or fail the seccomp probe). That belongs
in a non-RFC version and is left out here to keep the series focused on
demonstrating the impact on UML.

---

Cong Wang (6):
  pidfd: add pidfd_mmap()/pidfd_munmap() syscalls
  um: acquire a stub pidfd via CLONE_PIDFD in seccomp mode
  um: install guest mappings via pidfd_mmap() in seccomp mode
  um: forbid mmap/munmap in the stub seccomp filter
  um: install guest mappings via pidfd_mmap() in both modes
  selftests/pidfd: add pidfd_mmap()/pidfd_munmap() tests

 arch/um/include/shared/os.h                   |   4 -
 arch/um/include/shared/skas/mm_id.h           |   8 +-
 arch/um/include/shared/skas/stub-data.h       |  28 --
 arch/um/kernel/skas/mmu.c                     |   9 +-
 arch/um/kernel/skas/stub.c                    | 151 ++--------
 arch/um/kernel/skas/stub_exe.c                |  20 +-
 arch/um/os-Linux/skas/mem.c                   | 275 ++----------------
 arch/um/os-Linux/skas/process.c               |  92 ++----
 arch/um/os-Linux/start_up.c                   |   8 +-
 arch/x86/entry/syscalls/syscall_64.tbl        |   2 +
 include/linux/syscalls.h                      |   5 +
 include/uapi/linux/pidfd.h                    |  18 ++
 kernel/pid.c                                  | 132 +++++++++
 scripts/syscall.tbl                           |   2 +
 tools/testing/selftests/pidfd/Makefile        |   3 +-
 tools/testing/selftests/pidfd/pidfd.h         |  34 +++
 .../testing/selftests/pidfd/pidfd_mmap_test.c | 234 +++++++++++++++
 17 files changed, 520 insertions(+), 505 deletions(-)
 create mode 100644 tools/testing/selftests/pidfd/pidfd_mmap_test.c


base-commit: 69288464b0af40a988958c0e1b29ff15ccd2acb7
-- 
2.43.0


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [RFC PATCH 1/6] pidfd: add pidfd_mmap()/pidfd_munmap() syscalls
  2026-07-10 20:53 [RFC PATCH 0/6] um: introduce pidfd_mmap()/pidfd_munmap() syscalls Cong Wang
@ 2026-07-10 20:53 ` Cong Wang
  2026-07-10 20:53 ` [RFC PATCH 2/6] um: acquire a stub pidfd via CLONE_PIDFD in seccomp mode Cong Wang
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Cong Wang @ 2026-07-10 20:53 UTC (permalink / raw)
  To: Richard Weinberger, Anton Ivanov, Johannes Berg
  Cc: Benjamin Berg, linux-um, linux-kernel, Cong Wang

From: Cong Wang <cwang@multikernel.io>

A supervisor that may PTRACE_ATTACH a target installs or removes a
mapping in the target's address space directly, without target-side
cooperation. The backing fd is resolved in the *caller's* fd table,
mirroring pidfd_getfd()'s cross-task install model.

pidfd_mmap() takes an extensible struct pidfd_mmap_args (clone3/openat2
style, versioned by a leading size field) since an mmap-shaped call
exceeds the 6-argument syscall limit. pidfd_munmap() is a flat 3-arg
call. Both are gated by ptrace_may_access(PTRACE_MODE_ATTACH_REALCREDS)
and wired for x86_64 and the asm-generic table.

Assisted-by: Claude:claude-opus-4.8
Signed-off-by: Cong Wang <cwang@multikernel.io>
---
 arch/x86/entry/syscalls/syscall_64.tbl |   2 +
 include/linux/syscalls.h               |   5 +
 include/uapi/linux/pidfd.h             |  18 ++++
 kernel/pid.c                           | 132 +++++++++++++++++++++++++
 scripts/syscall.tbl                    |   2 +
 5 files changed, 159 insertions(+)

diff --git a/arch/x86/entry/syscalls/syscall_64.tbl b/arch/x86/entry/syscalls/syscall_64.tbl
index 524155d655da..12e3cbeb0d2c 100644
--- a/arch/x86/entry/syscalls/syscall_64.tbl
+++ b/arch/x86/entry/syscalls/syscall_64.tbl
@@ -396,6 +396,8 @@
 469	common	file_setattr		sys_file_setattr
 470	common	listns			sys_listns
 471	common	rseq_slice_yield	sys_rseq_slice_yield
+472	common	pidfd_mmap		sys_pidfd_mmap
+473	common	pidfd_munmap		sys_pidfd_munmap
 
 #
 # Due to a historical design error, certain syscalls are numbered differently
diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h
index 874d9067a43b..301a2d80823b 100644
--- a/include/linux/syscalls.h
+++ b/include/linux/syscalls.h
@@ -78,6 +78,7 @@ struct statmount;
 struct mnt_id_req;
 struct ns_id_req;
 struct xattr_args;
+struct pidfd_mmap_args;
 struct file_attr;
 
 #include <linux/types.h>
@@ -986,6 +987,10 @@ asmlinkage long sys_pidfd_send_signal(int pidfd, int sig,
 				       siginfo_t __user *info,
 				       unsigned int flags);
 asmlinkage long sys_pidfd_getfd(int pidfd, int fd, unsigned int flags);
+asmlinkage long sys_pidfd_mmap(int pidfd, struct pidfd_mmap_args __user *uargs,
+			       unsigned int flags);
+asmlinkage long sys_pidfd_munmap(int pidfd, unsigned long addr,
+				 unsigned long len);
 asmlinkage long sys_landlock_create_ruleset(const struct landlock_ruleset_attr __user *attr,
 		size_t size, __u32 flags);
 asmlinkage long sys_landlock_add_rule(int ruleset_fd, enum landlock_rule_type rule_type,
diff --git a/include/uapi/linux/pidfd.h b/include/uapi/linux/pidfd.h
index 0919246a1611..d68a11d844e3 100644
--- a/include/uapi/linux/pidfd.h
+++ b/include/uapi/linux/pidfd.h
@@ -107,6 +107,24 @@ struct pidfd_info {
 	__u64 supported_mask;	/* Mask flags that this kernel supports */
 };
 
+/*
+ * Argument block for pidfd_mmap(). Extensible: @size is sizeof(struct) as
+ * known to userspace; the kernel zero-extends older/smaller structs and
+ * rejects unknown non-zero trailing bytes (see copy_struct_from_user()).
+ */
+struct pidfd_mmap_args {
+	__u64 size;	/* sizeof(struct pidfd_mmap_args) */
+	__u64 addr;	/* MAP_FIXED target address, or 0 to let the kernel choose */
+	__u64 len;	/* length in bytes */
+	__u64 prot;	/* PROT_* protection bits */
+	__u64 flags;	/* MAP_* flags */
+	__u64 pgoff;	/* page offset into @fd */
+	__s32 fd;	/* backing fd in the caller's fd table, or -1 (MAP_ANONYMOUS) */
+	__u32 __spare;	/* must be zero */
+};
+
+#define PIDFD_MMAP_ARGS_SIZE_VER0	56 /* sizeof first published struct */
+
 #define PIDFS_IOCTL_MAGIC 0xFF
 
 #define PIDFD_GET_CGROUP_NAMESPACE            _IO(PIDFS_IOCTL_MAGIC, 1)
diff --git a/kernel/pid.c b/kernel/pid.c
index f55189a3d07d..d51471a36f3e 100644
--- a/kernel/pid.c
+++ b/kernel/pid.c
@@ -28,6 +28,12 @@
  */
 
 #include <linux/mm.h>
+#include <linux/mman.h>
+#include <linux/audit.h>
+#include <linux/file.h>
+#include <linux/ptrace.h>
+#include <linux/sched/mm.h>
+#include <linux/uaccess.h>
 #include <linux/export.h>
 #include <linux/slab.h>
 #include <linux/init.h>
@@ -972,3 +978,129 @@ SYSCALL_DEFINE3(pidfd_getfd, int, pidfd, int, fd,
 
 	return pidfd_getfd(pid, fd);
 }
+
+/*
+ * Resolve @pidfd to its task's mm, gated like a ptrace attach. On success the
+ * caller owns the returned mm reference and must mmput() it. Mirrors the
+ * cross-task authorization of pidfd_getfd(): a supervisor that may
+ * PTRACE_ATTACH the target may install into its address space.
+ */
+static int pidfd_target_mm(int pidfd, struct mm_struct **mmp)
+{
+	struct task_struct *task;
+	struct mm_struct *mm;
+	unsigned int f_flags;
+
+	task = pidfd_get_task(pidfd, &f_flags);
+	if (IS_ERR(task))
+		return PTR_ERR(task);
+
+	if (!ptrace_may_access(task, PTRACE_MODE_ATTACH_REALCREDS)) {
+		put_task_struct(task);
+		return -EPERM;
+	}
+
+	mm = get_task_mm(task);
+	put_task_struct(task);
+	if (!mm)
+		return -ESRCH;
+
+	*mmp = mm;
+	return 0;
+}
+
+/**
+ * sys_pidfd_mmap() - install a mapping into another process's address space.
+ * @pidfd: pidfd of the target task.
+ * @uargs: pointer to a struct pidfd_mmap_args describing the mapping.
+ * @flags: reserved, must be zero.
+ *
+ * The backing fd (if any) is resolved in the *caller's* fd table, so a
+ * supervisor maps from its own descriptors into the target -- the target
+ * never needs to receive the fd. Gated by ptrace_may_access(); LSM/fsnotify
+ * hooks run against the caller.
+ *
+ * Returns the mapped address on success, or a negative errno.
+ */
+SYSCALL_DEFINE3(pidfd_mmap, int, pidfd, struct pidfd_mmap_args __user *, uargs,
+		unsigned int, flags)
+{
+	struct pidfd_mmap_args args;
+	struct mm_struct *mm;
+	struct file *file = NULL;
+	unsigned long ret;
+	u64 usize;
+	int err;
+
+	if (flags)
+		return -EINVAL;
+
+	if (get_user(usize, &uargs->size))
+		return -EFAULT;
+	if (usize < PIDFD_MMAP_ARGS_SIZE_VER0)
+		return -EINVAL;
+	if (usize > PAGE_SIZE)
+		return -E2BIG;
+
+	err = copy_struct_from_user(&args, sizeof(args), uargs, usize);
+	if (err)
+		return err;
+	if (args.__spare)
+		return -EINVAL;
+
+	if (args.flags & MAP_HUGETLB)
+		return -EINVAL;
+	if (args.addr != (unsigned long)args.addr ||
+	    args.len != (unsigned long)args.len ||
+	    args.prot != (unsigned long)args.prot ||
+	    args.flags != (unsigned long)args.flags ||
+	    args.pgoff != (unsigned long)args.pgoff)
+		return -EINVAL;
+
+	err = pidfd_target_mm(pidfd, &mm);
+	if (err)
+		return err;
+
+	if (!(args.flags & MAP_ANONYMOUS)) {
+		audit_mmap_fd(args.fd, args.flags);
+		file = fget(args.fd);
+		if (!file) {
+			ret = -EBADF;
+			goto out_mm;
+		}
+	}
+
+	ret = vm_mmap_remote(mm, file, args.addr, args.len, args.prot,
+			     args.flags, args.pgoff, 0);
+
+	if (file)
+		fput(file);
+out_mm:
+	mmput(mm);
+	return ret;
+}
+
+/**
+ * sys_pidfd_munmap() - remove a mapping from another process's address space.
+ * @pidfd: pidfd of the target task.
+ * @addr: start address of the range to unmap.
+ * @len: length in bytes.
+ *
+ * Gated by ptrace_may_access(), like sys_pidfd_mmap().
+ *
+ * Returns 0 on success, or a negative errno.
+ */
+SYSCALL_DEFINE3(pidfd_munmap, int, pidfd, unsigned long, addr,
+		unsigned long, len)
+{
+	struct mm_struct *mm;
+	int ret;
+
+	ret = pidfd_target_mm(pidfd, &mm);
+	if (ret)
+		return ret;
+
+	ret = vm_munmap_remote(mm, addr, len);
+	mmput(mm);
+	return ret;
+}
diff --git a/scripts/syscall.tbl b/scripts/syscall.tbl
index 7a42b32b6577..f4fa7826232b 100644
--- a/scripts/syscall.tbl
+++ b/scripts/syscall.tbl
@@ -412,3 +412,5 @@
 469	common	file_setattr			sys_file_setattr
 470	common	listns				sys_listns
 471	common	rseq_slice_yield		sys_rseq_slice_yield
+472	common	pidfd_mmap			sys_pidfd_mmap
+473	common	pidfd_munmap			sys_pidfd_munmap
-- 
2.43.0


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [RFC PATCH 2/6] um: acquire a stub pidfd via CLONE_PIDFD in seccomp mode
  2026-07-10 20:53 [RFC PATCH 0/6] um: introduce pidfd_mmap()/pidfd_munmap() syscalls Cong Wang
  2026-07-10 20:53 ` [RFC PATCH 1/6] pidfd: add " Cong Wang
@ 2026-07-10 20:53 ` Cong Wang
  2026-07-10 20:53 ` [RFC PATCH 3/6] um: install guest mappings via pidfd_mmap() " Cong Wang
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Cong Wang @ 2026-07-10 20:53 UTC (permalink / raw)
  To: Richard Weinberger, Anton Ivanov, Johannes Berg
  Cc: Benjamin Berg, linux-um, linux-kernel, Cong Wang

From: Cong Wang <cwang@multikernel.io>

In SECCOMP mode the monitor will install guest mappings into the stub's
address space directly via pidfd_mmap(), so it needs a pidfd to the
stub. Acquire one atomically at clone() time via CLONE_PIDFD (returned
through the legacy-clone parent_tid argument), store it in
mm_id->stub_pidfd, and close it on teardown. The ptrace (SKAS0) mode
does not use pidfd_mmap and leaves stub_pidfd as -1.

Assisted-by: Claude:claude-opus-4.8
Signed-off-by: Cong Wang <cwang@multikernel.io>
---
 arch/um/include/shared/skas/mm_id.h |  1 +
 arch/um/kernel/skas/mmu.c           |  6 ++++++
 arch/um/os-Linux/skas/process.c     | 24 ++++++++++++++++++++++--
 3 files changed, 29 insertions(+), 2 deletions(-)

diff --git a/arch/um/include/shared/skas/mm_id.h b/arch/um/include/shared/skas/mm_id.h
index 18c0621430d2..cec97189f12b 100644
--- a/arch/um/include/shared/skas/mm_id.h
+++ b/arch/um/include/shared/skas/mm_id.h
@@ -16,6 +16,7 @@ struct mm_id {
 	int syscall_data_len;
 
 	/* Only used with SECCOMP mode */
+	int stub_pidfd;		/* pidfd to the stub, or -1 */
 	int sock;
 	int syscall_fd_num;
 	int syscall_fd_map[STUB_MAX_FDS];
diff --git a/arch/um/kernel/skas/mmu.c b/arch/um/kernel/skas/mmu.c
index b5017096028b..441dcf94ec9c 100644
--- a/arch/um/kernel/skas/mmu.c
+++ b/arch/um/kernel/skas/mmu.c
@@ -54,6 +54,7 @@ int init_new_context(struct task_struct *task, struct mm_struct *mm)
 		goto out;
 
 	new_id->stack = stack;
+	new_id->stub_pidfd = -1;
 	new_id->syscall_data_len = 0;
 	new_id->syscall_fd_num = 0;
 
@@ -103,6 +104,11 @@ void destroy_context(struct mm_struct *mm)
 		mmu->id.pid = -1;
 	}
 
+	if (mmu->id.stub_pidfd >= 0) {
+		os_close_file(mmu->id.stub_pidfd);
+		mmu->id.stub_pidfd = -1;
+	}
+
 	if (using_seccomp && mmu->id.sock)
 		os_close_file(mmu->id.sock);
 
diff --git a/arch/um/os-Linux/skas/process.c b/arch/um/os-Linux/skas/process.c
index d6c22f8aa06d..3dd97ca7999a 100644
--- a/arch/um/os-Linux/skas/process.c
+++ b/arch/um/os-Linux/skas/process.c
@@ -34,6 +34,10 @@
 #include <asm-generic/rwonce.h>
 #include "../internal.h"
 
+#ifndef CLONE_PIDFD
+#define CLONE_PIDFD 0x00001000
+#endif
+
 int is_skas_winch(int pid, int fd, void *data)
 {
 	return pid == getpgrp();
@@ -448,6 +452,9 @@ int start_userspace(struct mm_id *mm_id)
 	void *stack;
 	unsigned long sp;
 	int status, n, err;
+	int stub_pidfd = -1;
+
+	mm_id->stub_pidfd = -1;
 
 	/* setup a temporary stack page */
 	stack = mmap(NULL, UM_KERN_PAGE_SIZE,
@@ -474,15 +481,25 @@ int start_userspace(struct mm_id *mm_id)
 	if (using_seccomp)
 		proc_data->futex = FUTEX_IN_CHILD;
 
+	/*
+	 * In SECCOMP mode, acquire a pidfd to the stub via CLONE_PIDFD (it is
+	 * returned through the legacy-clone parent_tid argument). The monitor
+	 * installs guest mappings into the stub's mm directly via pidfd_mmap(),
+	 * so the stub itself never needs the mmap capability. The ptrace mode
+	 * does not use it and drives the stub directly.
+	 */
 	mm_id->pid = clone(userspace_tramp, (void *) sp,
-		    CLONE_VFORK | CLONE_VM | SIGCHLD,
-		    (void *)&tramp_data);
+		    CLONE_VFORK | CLONE_VM | (using_seccomp ? CLONE_PIDFD : 0) |
+		    SIGCHLD,
+		    (void *)&tramp_data, &stub_pidfd);
 	if (mm_id->pid < 0) {
 		err = -errno;
 		printk(UM_KERN_ERR "%s : clone failed, errno = %d\n",
 		       __func__, errno);
 		goto out_close;
 	}
+	if (using_seccomp)
+		mm_id->stub_pidfd = stub_pidfd;
 
 	if (using_seccomp) {
 		wait_stub_done_seccomp(mm_id, 1, 1);
@@ -534,8 +551,11 @@ int start_userspace(struct mm_id *mm_id)
 out_close:
 	close(tramp_data.sockpair[0]);
 	close(tramp_data.sockpair[1]);
+	if (stub_pidfd >= 0)
+		close(stub_pidfd);
 
 	mm_id->pid = -1;
+	mm_id->stub_pidfd = -1;
 
 	return err;
 }
-- 
2.43.0


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [RFC PATCH 3/6] um: install guest mappings via pidfd_mmap() in seccomp mode
  2026-07-10 20:53 [RFC PATCH 0/6] um: introduce pidfd_mmap()/pidfd_munmap() syscalls Cong Wang
  2026-07-10 20:53 ` [RFC PATCH 1/6] pidfd: add " Cong Wang
  2026-07-10 20:53 ` [RFC PATCH 2/6] um: acquire a stub pidfd via CLONE_PIDFD in seccomp mode Cong Wang
@ 2026-07-10 20:53 ` Cong Wang
  2026-07-10 20:53 ` [RFC PATCH 4/6] um: forbid mmap/munmap in the stub seccomp filter Cong Wang
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 7+ messages in thread
From: Cong Wang @ 2026-07-10 20:53 UTC (permalink / raw)
  To: Richard Weinberger, Anton Ivanov, Johannes Berg
  Cc: Benjamin Berg, linux-um, linux-kernel, Cong Wang

From: Cong Wang <cwang@multikernel.io>

In SECCOMP mode, map()/unmap() now install into the stub's address
space directly from the monitor via pidfd_mmap()/pidfd_munmap(),
resolving the physmem fd in the monitor's own fd table. The stub neither
receives the fd nor executes mmap/munmap itself.

The ptrace (SKAS0) mode is unchanged: it keeps batching STUB_SYSCALL_MMAP
/MUNMAP for the stub to execute, which works on any host kernel and
needs no new syscall. Only the seccomp path, where the stub is
sandboxed, is converted.

Assisted-by: Claude:claude-opus-4.8
Signed-off-by: Cong Wang <cwang@multikernel.io>
---
 arch/um/os-Linux/skas/mem.c | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/arch/um/os-Linux/skas/mem.c b/arch/um/os-Linux/skas/mem.c
index 8b9921ac3ef8..f751a1ea1c4c 100644
--- a/arch/um/os-Linux/skas/mem.c
+++ b/arch/um/os-Linux/skas/mem.c
@@ -9,6 +9,8 @@
 #include <errno.h>
 #include <string.h>
 #include <sys/mman.h>
+#include <sys/syscall.h>
+#include <linux/pidfd.h>
 #include <init.h>
 #include <as-layout.h>
 #include <mm_id.h>
@@ -236,6 +238,21 @@ int map(struct mm_id *mm_idp, unsigned long virt, unsigned long len, int prot,
 {
 	struct stub_syscall *sc;
 
+	if (using_seccomp) {
+		struct pidfd_mmap_args args = {
+			.size	= sizeof(args),
+			.addr	= virt,
+			.len	= len,
+			.prot	= prot,
+			.flags	= MAP_SHARED | MAP_FIXED,
+			.pgoff	= offset >> UM_KERN_PAGE_SHIFT,
+			.fd	= phys_fd,
+		};
+		long res = syscall(__NR_pidfd_mmap, mm_idp->stub_pidfd, &args, 0);
+
+		return res < 0 ? -errno : 0;
+	}
+
 	/* Compress with previous syscall if that is possible */
 	sc = syscall_stub_get_previous(mm_idp, STUB_SYSCALL_MMAP, virt);
 	if (sc && sc->mem.prot == prot &&
@@ -268,6 +285,13 @@ int unmap(struct mm_id *mm_idp, unsigned long addr, unsigned long len)
 {
 	struct stub_syscall *sc;
 
+	if (using_seccomp) {
+		long res = syscall(__NR_pidfd_munmap, mm_idp->stub_pidfd,
+				   addr, len);
+
+		return res < 0 ? -errno : 0;
+	}
+
 	/* Compress with previous syscall if that is possible */
 	sc = syscall_stub_get_previous(mm_idp, STUB_SYSCALL_MUNMAP, addr);
 	if (sc) {
-- 
2.43.0


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [RFC PATCH 4/6] um: forbid mmap/munmap in the stub seccomp filter
  2026-07-10 20:53 [RFC PATCH 0/6] um: introduce pidfd_mmap()/pidfd_munmap() syscalls Cong Wang
                   ` (2 preceding siblings ...)
  2026-07-10 20:53 ` [RFC PATCH 3/6] um: install guest mappings via pidfd_mmap() " Cong Wang
@ 2026-07-10 20:53 ` Cong Wang
  2026-07-10 20:53 ` [RFC PATCH 5/6] um: install guest mappings via pidfd_mmap() in both modes Cong Wang
  2026-07-10 20:53 ` [RFC PATCH 6/6] selftests/pidfd: add pidfd_mmap()/pidfd_munmap() tests Cong Wang
  5 siblings, 0 replies; 7+ messages in thread
From: Cong Wang @ 2026-07-10 20:53 UTC (permalink / raw)
  To: Richard Weinberger, Anton Ivanov, Johannes Berg
  Cc: Benjamin Berg, linux-um, linux-kernel, Cong Wang

From: Cong Wang <cwang@multikernel.io>

In seccomp mode the monitor now installs all guest mappings via
pidfd_mmap(), so the stub has no legitimate need to mmap/munmap after
its filter is in place (the pre-filter bootstrap mmaps are unaffected).
Drop the STUB_MMAP_NR and __NR_munmap allowlist arms and fix the jump
offsets; both now hit SECCOMP_RET_KILL_PROCESS. A hijacked stub can no
longer map arbitrary physmem. The filter exists only in seccomp mode,
so ptrace (SKAS0) is untouched.

Assisted-by: Claude:claude-opus-4.8
Signed-off-by: Cong Wang <cwang@multikernel.io>
---
 arch/um/kernel/skas/stub_exe.c | 20 +++++++++++---------
 1 file changed, 11 insertions(+), 9 deletions(-)

diff --git a/arch/um/kernel/skas/stub_exe.c b/arch/um/kernel/skas/stub_exe.c
index cbafaa684e66..a88274449168 100644
--- a/arch/um/kernel/skas/stub_exe.c
+++ b/arch/um/kernel/skas/stub_exe.c
@@ -165,16 +165,18 @@ noinline static void real_init(void)
 			BPF_STMT(BPF_LD | BPF_W | BPF_ABS,
 				 offsetof(struct seccomp_data, nr)),
 
-			/* [10-16] Check against permitted syscalls */
+			/*
+			 * [10-14] Check against permitted syscalls. mmap and
+			 * munmap are deliberately absent: in seccomp mode the
+			 * monitor installs all guest mappings via pidfd_mmap(),
+			 * so a stub that attempts mmap/munmap hits the KILL at
+			 * [15].
+			 */
 			BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_futex,
-				 7, 0),
-			BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,__NR_recvmsg,
-				 6, 0),
-			BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K,__NR_close,
 				 5, 0),
-			BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, STUB_MMAP_NR,
+			BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_recvmsg,
 				 4, 0),
-			BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_munmap,
+			BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_close,
 				 3, 0),
 #ifdef __i386__
 			BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_set_thread_area,
@@ -186,10 +188,10 @@ noinline static void real_init(void)
 			BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_rt_sigreturn,
 				 1, 0),
 
-			/* [17] Not one of the permitted syscalls */
+			/* [15] Not one of the permitted syscalls */
 			BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS),
 
-			/* [18] Permitted call for the stub */
+			/* [16] Permitted call for the stub */
 			BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
 		};
 		struct sock_fprog prog = {
-- 
2.43.0


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [RFC PATCH 5/6] um: install guest mappings via pidfd_mmap() in both modes
  2026-07-10 20:53 [RFC PATCH 0/6] um: introduce pidfd_mmap()/pidfd_munmap() syscalls Cong Wang
                   ` (3 preceding siblings ...)
  2026-07-10 20:53 ` [RFC PATCH 4/6] um: forbid mmap/munmap in the stub seccomp filter Cong Wang
@ 2026-07-10 20:53 ` Cong Wang
  2026-07-10 20:53 ` [RFC PATCH 6/6] selftests/pidfd: add pidfd_mmap()/pidfd_munmap() tests Cong Wang
  5 siblings, 0 replies; 7+ messages in thread
From: Cong Wang @ 2026-07-10 20:53 UTC (permalink / raw)
  To: Richard Weinberger, Anton Ivanov, Johannes Berg
  Cc: Benjamin Berg, linux-um, linux-kernel, Cong Wang

From: Cong Wang <cwang@multikernel.io>

Route both seccomp and ptrace (SKAS0) map()/unmap() through
pidfd_mmap()/pidfd_munmap(): the monitor resolves the physmem fd in its
own fd table and installs the mapping into the stub's mm directly, so the
stub never executes mmap/munmap itself in either mode.

Neither mode drives the stub to execute syscalls anymore, so the whole
stub-syscall batcher and the seccomp-only SCM_RIGHTS fd-passing become
dead code and are removed. The seccomp model's inherent risks are not
addressed by pidfd_mmap() (a malicious guest can still block SIGALRM to
dodge scheduling), so that note is preserved in stub.c.

This is an RFC demonstration of pidfd_mmap()'s reach; it assumes a host
kernel that provides the syscall and does not add a fallback for older
hosts intentionally.

Assisted-by: Claude:claude-opus-4.8
Signed-off-by: Cong Wang <cwang@multikernel.io>
---
 arch/um/include/shared/os.h             |   4 -
 arch/um/include/shared/skas/mm_id.h     |   7 -
 arch/um/include/shared/skas/stub-data.h |  28 ---
 arch/um/kernel/skas/mmu.c               |   5 -
 arch/um/kernel/skas/stub.c              | 151 ++----------
 arch/um/kernel/skas/stub_exe.c          |  16 +-
 arch/um/os-Linux/skas/mem.c             | 295 ++----------------------
 arch/um/os-Linux/skas/process.c         |  86 +------
 arch/um/os-Linux/start_up.c             |   8 +-
 9 files changed, 67 insertions(+), 533 deletions(-)

diff --git a/arch/um/include/shared/os.h b/arch/um/include/shared/os.h
index b26e94292fc1..c6917583ca64 100644
--- a/arch/um/include/shared/os.h
+++ b/arch/um/include/shared/os.h
@@ -280,10 +280,6 @@ extern long long os_persistent_clock_emulation(void);
 extern long long os_nsecs(void);
 
 /* skas/mem.c */
-int syscall_stub_flush(struct mm_id *mm_idp);
-struct stub_syscall *syscall_stub_alloc(struct mm_id *mm_idp);
-void syscall_stub_dump_error(struct mm_id *mm_idp);
-
 int map(struct mm_id *mm_idp, unsigned long virt,
 	unsigned long len, int prot, int phys_fd,
 	unsigned long long offset);
diff --git a/arch/um/include/shared/skas/mm_id.h b/arch/um/include/shared/skas/mm_id.h
index cec97189f12b..0f74e1728a1e 100644
--- a/arch/um/include/shared/skas/mm_id.h
+++ b/arch/um/include/shared/skas/mm_id.h
@@ -8,18 +8,11 @@
 
 #include <linux/compiler_types.h>
 
-#define STUB_MAX_FDS 4
-
 struct mm_id {
 	int pid;
 	unsigned long stack;
-	int syscall_data_len;
 
-	/* Only used with SECCOMP mode */
 	int stub_pidfd;		/* pidfd to the stub, or -1 */
-	int sock;
-	int syscall_fd_num;
-	int syscall_fd_map[STUB_MAX_FDS];
 };
 
 struct mutex *__get_turnstile(struct mm_id *mm_id);
diff --git a/arch/um/include/shared/skas/stub-data.h b/arch/um/include/shared/skas/stub-data.h
index 27db38e95df9..eaa208bcdaff 100644
--- a/arch/um/include/shared/skas/stub-data.h
+++ b/arch/um/include/shared/skas/stub-data.h
@@ -31,36 +31,8 @@ struct stub_init_data {
 	unsigned long signal_restorer;
 };
 
-#define STUB_NEXT_SYSCALL(s) \
-	((struct stub_syscall *) (((unsigned long) s) + (s)->cmd_len))
-
-enum stub_syscall_type {
-	STUB_SYSCALL_UNSET = 0,
-	STUB_SYSCALL_MMAP,
-	STUB_SYSCALL_MUNMAP,
-};
-
-struct stub_syscall {
-	struct {
-		unsigned long addr;
-		unsigned long length;
-		unsigned long offset;
-		int fd;
-		int prot;
-	} mem;
-
-	enum stub_syscall_type syscall;
-};
-
 struct stub_data {
-	long err;
-
-	int syscall_data_len;
-	/* 128 leaves enough room for additional fields in the struct */
-	struct stub_syscall syscall_data[(UM_KERN_PAGE_SIZE - 128) / sizeof(struct stub_syscall)] __aligned(16);
-
 	/* data shared with signal handler (only used in seccomp mode) */
-	short restart_wait;
 	unsigned int futex;
 	int signal;
 	unsigned short si_offset;
diff --git a/arch/um/kernel/skas/mmu.c b/arch/um/kernel/skas/mmu.c
index 441dcf94ec9c..b2c9cf023789 100644
--- a/arch/um/kernel/skas/mmu.c
+++ b/arch/um/kernel/skas/mmu.c
@@ -55,8 +55,6 @@ int init_new_context(struct task_struct *task, struct mm_struct *mm)
 
 	new_id->stack = stack;
 	new_id->stub_pidfd = -1;
-	new_id->syscall_data_len = 0;
-	new_id->syscall_fd_num = 0;
 
 	scoped_guard(spinlock_irqsave, &mm_list_lock) {
 		/* Insert into list, used for lookups when the child dies */
@@ -109,9 +107,6 @@ void destroy_context(struct mm_struct *mm)
 		mmu->id.stub_pidfd = -1;
 	}
 
-	if (using_seccomp && mmu->id.sock)
-		os_close_file(mmu->id.sock);
-
 	free_pages(mmu->id.stack, ilog2(STUB_DATA_PAGES));
 }
 
diff --git a/arch/um/kernel/skas/stub.c b/arch/um/kernel/skas/stub.c
index e09216a20cb5..08443f2eb09d 100644
--- a/arch/um/kernel/skas/stub.c
+++ b/arch/um/kernel/skas/stub.c
@@ -6,119 +6,46 @@
 #include <sysdep/stub.h>
 
 #include <linux/futex.h>
-#include <sys/socket.h>
 #include <errno.h>
 
 /*
- * Known security issues
+ * Known security issues (SECCOMP userspace)
  *
- * Userspace can jump to this address to execute *any* syscall that is
- * permitted by the stub. As we will return afterwards, it can do
- * whatever it likes, including:
- * - Tricking the kernel into handing out the memory FD
- * - Using this memory FD to read/write all physical memory
- * - Running in parallel to the kernel processing a syscall
- *   (possibly creating data races?)
- * - Blocking e.g. SIGALRM to avoid time based scheduling
+ * The stub shares its address space with the untrusted guest application.
+ * Installing guest mappings via pidfd_mmap() closed some earlier holes --
+ * the stub no longer holds the physmem fd and no longer executes mmap/munmap
+ * on the guest's behalf (both are rejected by the SECCOMP filter) -- but the
+ * following are inherent to the model and remain unaddressed:
  *
- * To avoid this, the permitted location for each syscall needs to be
- * checked for in the SECCOMP filter (which is reasonably simple). Also,
- * more care will need to go into considerations how the code might be
- * tricked by using a prepared stack (or even modifying the stack from
- * another thread in case SMP support is added).
- *
- * As for the SIGALRM, the best counter measure will be to check in the
- * kernel that the process is reporting back the SIGALRM in a timely
- * fashion.
+ * - A malicious guest can influence the stub's signal state (e.g. via a
+ *   prepared stack restored on rt_sigreturn) and block SIGALRM to avoid
+ *   time-based scheduling. The intended counter measure is for the monitor
+ *   to check that the process reports SIGALRM back in a timely fashion; that
+ *   is not yet implemented.
+ * - The permitted location of each syscall the stub does issue must be
+ *   pinned down in the SECCOMP filter, and more care is needed around a
+ *   prepared stack (or the stack being modified from another thread should
+ *   SMP support be added).
  */
-static __always_inline int syscall_handler(int fd_map[STUB_MAX_FDS])
-{
-	struct stub_data *d = get_stub_data();
-	int i;
-	unsigned long res;
-	int fd;
-
-	for (i = 0; i < d->syscall_data_len; i++) {
-		struct stub_syscall *sc = &d->syscall_data[i];
-
-		switch (sc->syscall) {
-		case STUB_SYSCALL_MMAP:
-			if (fd_map)
-				fd = fd_map[sc->mem.fd];
-			else
-				fd = sc->mem.fd;
-
-			res = stub_syscall6(STUB_MMAP_NR,
-					    sc->mem.addr, sc->mem.length,
-					    sc->mem.prot,
-					    MAP_SHARED | MAP_FIXED,
-					    fd, sc->mem.offset);
-			if (res != sc->mem.addr) {
-				d->err = res;
-				d->syscall_data_len = i;
-				return -1;
-			}
-			break;
-		case STUB_SYSCALL_MUNMAP:
-			res = stub_syscall2(__NR_munmap,
-					    sc->mem.addr, sc->mem.length);
-			if (res) {
-				d->err = res;
-				d->syscall_data_len = i;
-				return -1;
-			}
-			break;
-		default:
-			d->err = -95; /* EOPNOTSUPP */
-			d->syscall_data_len = i;
-			return -1;
-		}
-	}
-
-	d->err = 0;
-	d->syscall_data_len = 0;
-
-	return 0;
-}
-
-void __section(".__syscall_stub")
-stub_syscall_handler(void)
-{
-	syscall_handler(NULL);
-
-	trap_myself();
-}
 
 void __section(".__syscall_stub")
 stub_signal_interrupt(int sig, siginfo_t *info, void *p)
 {
 	struct stub_data *d = get_stub_data();
-	char rcv_data;
-	union {
-		char data[CMSG_SPACE(sizeof(int) * STUB_MAX_FDS)];
-		struct cmsghdr align;
-	} ctrl = {};
-	struct iovec iov = {
-		.iov_base = &rcv_data,
-		.iov_len = 1,
-	};
-	struct msghdr msghdr = {
-		.msg_iov = &iov,
-		.msg_iovlen = 1,
-		.msg_control = &ctrl,
-		.msg_controllen = sizeof(ctrl),
-	};
 	ucontext_t *uc = p;
-	struct cmsghdr *fd_msg;
-	int *fd_map;
-	int num_fds;
 	long res;
 
 	d->signal = sig;
 	d->si_offset = (unsigned long)info - (unsigned long)&d->sigstack[0];
 	d->mctx_offset = (unsigned long)&uc->uc_mcontext - (unsigned long)&d->sigstack[0];
 
-restart_wait:
+	/*
+	 * Hand the guest trap to the monitor and block until it resumes us:
+	 * wake the monitor (FUTEX_WAKE) and FUTEX_WAIT until it is done. In
+	 * seccomp mode the monitor installs any address-space changes itself
+	 * via pidfd_mmap(), so the stub no longer executes syscalls on its
+	 * behalf and never receives an fd.
+	 */
 	d->futex = FUTEX_IN_KERN;
 	do {
 		res = stub_syscall3(__NR_futex, (unsigned long)&d->futex,
@@ -133,40 +60,6 @@ stub_signal_interrupt(int sig, siginfo_t *info, void *p)
 	if (res < 0 && res != -EAGAIN)
 		stub_syscall1(__NR_exit_group, 1);
 
-	if (d->syscall_data_len) {
-		/* Read passed FDs (if any) */
-		do {
-			res = stub_syscall3(__NR_recvmsg, 0, (unsigned long)&msghdr, 0);
-		} while (res == -EINTR);
-
-		/* We should never have a receive error (other than -EAGAIN) */
-		if (res < 0 && res != -EAGAIN)
-			stub_syscall1(__NR_exit_group, 1);
-
-		/* Receive the FDs */
-		num_fds = 0;
-		fd_msg = msghdr.msg_control;
-		fd_map = (void *)CMSG_DATA(fd_msg);
-		if (res == iov.iov_len && msghdr.msg_controllen > sizeof(struct cmsghdr))
-			num_fds = (fd_msg->cmsg_len - CMSG_LEN(0)) / sizeof(int);
-
-		/* Try running queued syscalls. */
-		res = syscall_handler(fd_map);
-
-		while (num_fds)
-			stub_syscall2(__NR_close, fd_map[--num_fds], 0);
-	} else {
-		res = 0;
-	}
-
-	if (res < 0 || d->restart_wait) {
-		/* Report SIGSYS if we restart. */
-		d->signal = SIGSYS;
-		d->restart_wait = 0;
-
-		goto restart_wait;
-	}
-
 	/* Restore arch dependent state that is not part of the mcontext */
 	stub_seccomp_restore_state(&d->arch_data);
 
diff --git a/arch/um/kernel/skas/stub_exe.c b/arch/um/kernel/skas/stub_exe.c
index a88274449168..af1d011be0a4 100644
--- a/arch/um/kernel/skas/stub_exe.c
+++ b/arch/um/kernel/skas/stub_exe.c
@@ -166,15 +166,13 @@ noinline static void real_init(void)
 				 offsetof(struct seccomp_data, nr)),
 
 			/*
-			 * [10-14] Check against permitted syscalls. mmap and
-			 * munmap are deliberately absent: in seccomp mode the
-			 * monitor installs all guest mappings via pidfd_mmap(),
-			 * so a stub that attempts mmap/munmap hits the KILL at
-			 * [15].
+			 * [10-13] Check against permitted syscalls. mmap, munmap
+			 * and recvmsg are deliberately absent: in seccomp mode
+			 * the monitor installs all guest mappings via
+			 * pidfd_mmap() and no longer passes fds to the stub, so a
+			 * stub that attempts mmap/munmap hits the KILL at [14].
 			 */
 			BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_futex,
-				 5, 0),
-			BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_recvmsg,
 				 4, 0),
 			BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_close,
 				 3, 0),
@@ -188,10 +186,10 @@ noinline static void real_init(void)
 			BPF_JUMP(BPF_JMP | BPF_JEQ | BPF_K, __NR_rt_sigreturn,
 				 1, 0),
 
-			/* [15] Not one of the permitted syscalls */
+			/* [14] Not one of the permitted syscalls */
 			BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_KILL_PROCESS),
 
-			/* [16] Permitted call for the stub */
+			/* [15] Permitted call for the stub */
 			BPF_STMT(BPF_RET | BPF_K, SECCOMP_RET_ALLOW),
 		};
 		struct sock_fprog prog = {
diff --git a/arch/um/os-Linux/skas/mem.c b/arch/um/os-Linux/skas/mem.c
index f751a1ea1c4c..9330f97f8beb 100644
--- a/arch/um/os-Linux/skas/mem.c
+++ b/arch/um/os-Linux/skas/mem.c
@@ -22,287 +22,34 @@
 #include <sysdep/stub.h>
 #include "../internal.h"
 
-extern char __syscall_stub_start[];
-
-void syscall_stub_dump_error(struct mm_id *mm_idp)
-{
-	struct stub_data *proc_data = (void *)mm_idp->stack;
-	struct stub_syscall *sc;
-
-	if (proc_data->syscall_data_len < 0 ||
-	    proc_data->syscall_data_len >= ARRAY_SIZE(proc_data->syscall_data))
-		panic("Syscall data was corrupted by stub (len is: %d, expected maximum: %d)!",
-			proc_data->syscall_data_len,
-			mm_idp->syscall_data_len);
-
-	sc = &proc_data->syscall_data[proc_data->syscall_data_len];
-
-	printk(UM_KERN_ERR "%s : length = %d, last offset = %d",
-		__func__, mm_idp->syscall_data_len,
-		proc_data->syscall_data_len);
-	printk(UM_KERN_ERR "%s : stub syscall type %d failed, return value = 0x%lx\n",
-		__func__, sc->syscall, proc_data->err);
-
-	print_hex_dump(UM_KERN_ERR, "    syscall data: ", 0,
-		       16, 4, sc, sizeof(*sc), 0);
-
-	if (using_seccomp) {
-		printk(UM_KERN_ERR "%s: FD map num: %d", __func__,
-		       mm_idp->syscall_fd_num);
-		print_hex_dump(UM_KERN_ERR,
-				"    FD map: ", 0, 16,
-				sizeof(mm_idp->syscall_fd_map[0]),
-				mm_idp->syscall_fd_map,
-				sizeof(mm_idp->syscall_fd_map), 0);
-	}
-}
-
-static inline unsigned long *check_init_stack(struct mm_id * mm_idp,
-					      unsigned long *stack)
-{
-	if (stack == NULL) {
-		stack = (unsigned long *) mm_idp->stack + 2;
-		*stack = 0;
-	}
-	return stack;
-}
-
-static unsigned long syscall_regs[MAX_REG_NR];
-
-static int __init init_syscall_regs(void)
-{
-	get_safe_registers(syscall_regs, NULL);
-
-	syscall_regs[REGS_IP_INDEX] = STUB_CODE +
-		((unsigned long) stub_syscall_handler -
-		 (unsigned long) __syscall_stub_start);
-	syscall_regs[REGS_SP_INDEX] = STUB_DATA +
-		offsetof(struct stub_data, sigstack) +
-		sizeof(((struct stub_data *) 0)->sigstack) -
-		sizeof(void *);
-
-	return 0;
-}
-
-__initcall(init_syscall_regs);
-
-static inline long do_syscall_stub(struct mm_id *mm_idp)
-{
-	struct stub_data *proc_data = (void *)mm_idp->stack;
-	int n, i;
-	int err, pid = mm_idp->pid;
-
-	/* Inform process how much we have filled in. */
-	proc_data->syscall_data_len = mm_idp->syscall_data_len;
-
-	if (using_seccomp) {
-		proc_data->restart_wait = 1;
-		wait_stub_done_seccomp(mm_idp, 0, 1);
-	} else {
-		n = ptrace_setregs(pid, syscall_regs);
-		if (n < 0) {
-			printk(UM_KERN_ERR "Registers -\n");
-			for (i = 0; i < MAX_REG_NR; i++)
-				printk(UM_KERN_ERR "\t%d\t0x%lx\n", i, syscall_regs[i]);
-			panic("%s : PTRACE_SETREGS failed, errno = %d\n",
-			      __func__, -n);
-		}
-
-		err = ptrace(PTRACE_CONT, pid, 0, 0);
-		if (err)
-			panic("Failed to continue stub, pid = %d, errno = %d\n",
-			      pid, errno);
-
-		wait_stub_done(pid);
-	}
-
-	/*
-	 * proc_data->err will be negative if there was an (unexpected) error.
-	 * In that case, syscall_data_len points to the last executed syscall,
-	 * otherwise it will be zero (but we do not need to rely on that).
-	 */
-	if (proc_data->err < 0) {
-		syscall_stub_dump_error(mm_idp);
-
-		/* Store error code in case someone tries to add more syscalls */
-		mm_idp->syscall_data_len = proc_data->err;
-	} else {
-		mm_idp->syscall_data_len = 0;
-	}
-
-	if (using_seccomp)
-		mm_idp->syscall_fd_num = 0;
-
-	return mm_idp->syscall_data_len;
-}
-
-int syscall_stub_flush(struct mm_id *mm_idp)
-{
-	int res;
-
-	if (mm_idp->syscall_data_len == 0)
-		return 0;
-
-	/* If an error happened already, report it and reset the state. */
-	if (mm_idp->syscall_data_len < 0) {
-		res = mm_idp->syscall_data_len;
-		mm_idp->syscall_data_len = 0;
-		return res;
-	}
-
-	res = do_syscall_stub(mm_idp);
-	mm_idp->syscall_data_len = 0;
-
-	return res;
-}
-
-struct stub_syscall *syscall_stub_alloc(struct mm_id *mm_idp)
-{
-	struct stub_syscall *sc;
-	struct stub_data *proc_data = (struct stub_data *) mm_idp->stack;
-
-	if (mm_idp->syscall_data_len > 0 &&
-	    mm_idp->syscall_data_len == ARRAY_SIZE(proc_data->syscall_data))
-		do_syscall_stub(mm_idp);
-
-	if (mm_idp->syscall_data_len < 0) {
-		/* Return dummy to retain error state. */
-		sc = &proc_data->syscall_data[0];
-	} else {
-		sc = &proc_data->syscall_data[mm_idp->syscall_data_len];
-		mm_idp->syscall_data_len += 1;
-	}
-	memset(sc, 0, sizeof(*sc));
-
-	return sc;
-}
-
-static struct stub_syscall *syscall_stub_get_previous(struct mm_id *mm_idp,
-						      int syscall_type,
-						      unsigned long virt)
-{
-	if (mm_idp->syscall_data_len > 0) {
-		struct stub_data *proc_data = (void *) mm_idp->stack;
-		struct stub_syscall *sc;
-
-		sc = &proc_data->syscall_data[mm_idp->syscall_data_len - 1];
-
-		if (sc->syscall == syscall_type &&
-		    sc->mem.addr + sc->mem.length == virt)
-			return sc;
-	}
-
-	return NULL;
-}
-
-static int get_stub_fd(struct mm_id *mm_idp, int fd)
-{
-	int i;
-
-	/* Find an FD slot (or flush and use first) */
-	if (!using_seccomp)
-		return fd;
-
-	/* Already crashed, value does not matter */
-	if (mm_idp->syscall_data_len < 0)
-		return 0;
-
-	/* Find existing FD in map if we can allocate another syscall */
-	if (mm_idp->syscall_data_len <
-	    ARRAY_SIZE(((struct stub_data *)NULL)->syscall_data)) {
-		for (i = 0; i < mm_idp->syscall_fd_num; i++) {
-			if (mm_idp->syscall_fd_map[i] == fd)
-				return i;
-		}
-
-		if (mm_idp->syscall_fd_num < STUB_MAX_FDS) {
-			i = mm_idp->syscall_fd_num;
-			mm_idp->syscall_fd_map[i] = fd;
-
-			mm_idp->syscall_fd_num++;
-
-			return i;
-		}
-	}
-
-	/* FD map full or no syscall space available, continue after flush */
-	do_syscall_stub(mm_idp);
-	mm_idp->syscall_fd_map[0] = fd;
-	mm_idp->syscall_fd_num = 1;
-
-	return 0;
-}
-
+/*
+ * Install (map) or remove (unmap) a guest mapping in the stub's address space.
+ * Both seccomp and ptrace modes now go through pidfd_mmap()/pidfd_munmap():
+ * the monitor resolves the physmem fd in its own fd table and installs the
+ * mapping into the stub's mm directly, so the stub never has to execute
+ * mmap/munmap itself. This makes the ptrace stub-syscall batcher unnecessary
+ * for memory management and shows the primitive works uniformly across modes.
+ */
 int map(struct mm_id *mm_idp, unsigned long virt, unsigned long len, int prot,
 	int phys_fd, unsigned long long offset)
 {
-	struct stub_syscall *sc;
-
-	if (using_seccomp) {
-		struct pidfd_mmap_args args = {
-			.size	= sizeof(args),
-			.addr	= virt,
-			.len	= len,
-			.prot	= prot,
-			.flags	= MAP_SHARED | MAP_FIXED,
-			.pgoff	= offset >> UM_KERN_PAGE_SHIFT,
-			.fd	= phys_fd,
-		};
-		long res = syscall(__NR_pidfd_mmap, mm_idp->stub_pidfd, &args, 0);
-
-		return res < 0 ? -errno : 0;
-	}
+	struct pidfd_mmap_args args = {
+		.size	= sizeof(args),
+		.addr	= virt,
+		.len	= len,
+		.prot	= prot,
+		.flags	= MAP_SHARED | MAP_FIXED,
+		.pgoff	= offset >> UM_KERN_PAGE_SHIFT,
+		.fd	= phys_fd,
+	};
+	long res = syscall(__NR_pidfd_mmap, mm_idp->stub_pidfd, &args, 0);
 
-	/* Compress with previous syscall if that is possible */
-	sc = syscall_stub_get_previous(mm_idp, STUB_SYSCALL_MMAP, virt);
-	if (sc && sc->mem.prot == prot &&
-	    sc->mem.offset == MMAP_OFFSET(offset - sc->mem.length)) {
-		int prev_fd = sc->mem.fd;
-
-		if (using_seccomp)
-			prev_fd = mm_idp->syscall_fd_map[sc->mem.fd];
-
-		if (phys_fd == prev_fd) {
-			sc->mem.length += len;
-			return 0;
-		}
-	}
-
-	phys_fd = get_stub_fd(mm_idp, phys_fd);
-
-	sc = syscall_stub_alloc(mm_idp);
-	sc->syscall = STUB_SYSCALL_MMAP;
-	sc->mem.addr = virt;
-	sc->mem.length = len;
-	sc->mem.prot = prot;
-	sc->mem.fd = phys_fd;
-	sc->mem.offset = MMAP_OFFSET(offset);
-
-	return 0;
+	return res < 0 ? -errno : 0;
 }
 
 int unmap(struct mm_id *mm_idp, unsigned long addr, unsigned long len)
 {
-	struct stub_syscall *sc;
-
-	if (using_seccomp) {
-		long res = syscall(__NR_pidfd_munmap, mm_idp->stub_pidfd,
-				   addr, len);
-
-		return res < 0 ? -errno : 0;
-	}
-
-	/* Compress with previous syscall if that is possible */
-	sc = syscall_stub_get_previous(mm_idp, STUB_SYSCALL_MUNMAP, addr);
-	if (sc) {
-		sc->mem.length += len;
-		return 0;
-	}
-
-	sc = syscall_stub_alloc(mm_idp);
-	sc->syscall = STUB_SYSCALL_MUNMAP;
-	sc->mem.addr = addr;
-	sc->mem.length = len;
+	long res = syscall(__NR_pidfd_munmap, mm_idp->stub_pidfd, addr, len);
 
-	return 0;
+	return res < 0 ? -errno : 0;
 }
diff --git a/arch/um/os-Linux/skas/process.c b/arch/um/os-Linux/skas/process.c
index 3dd97ca7999a..cd6e40451576 100644
--- a/arch/um/os-Linux/skas/process.c
+++ b/arch/um/os-Linux/skas/process.c
@@ -157,39 +157,7 @@ void wait_stub_done_seccomp(struct mm_id *mm_idp, int running, int wait_sigsys)
 	int ret;
 
 	do {
-		const char byte = 0;
-		struct iovec iov = {
-			.iov_base = (void *)&byte,
-			.iov_len = sizeof(byte),
-		};
-		union {
-			char data[CMSG_SPACE(sizeof(mm_idp->syscall_fd_map))];
-			struct cmsghdr align;
-		} ctrl;
-		struct msghdr msgh = {
-			.msg_iov = &iov,
-			.msg_iovlen = 1,
-		};
-
 		if (!running) {
-			if (mm_idp->syscall_fd_num) {
-				unsigned int fds_size =
-					sizeof(int) * mm_idp->syscall_fd_num;
-				struct cmsghdr *cmsg;
-
-				msgh.msg_control = ctrl.data;
-				msgh.msg_controllen = CMSG_SPACE(fds_size);
-				cmsg = CMSG_FIRSTHDR(&msgh);
-				cmsg->cmsg_level = SOL_SOCKET;
-				cmsg->cmsg_type = SCM_RIGHTS;
-				cmsg->cmsg_len = CMSG_LEN(fds_size);
-				memcpy(CMSG_DATA(cmsg), mm_idp->syscall_fd_map,
-				       fds_size);
-
-				CATCH_EINTR(syscall(__NR_sendmsg, mm_idp->sock,
-						&msgh, 0));
-			}
-
 			data->signal = 0;
 			data->futex = FUTEX_IN_CHILD;
 			CATCH_EINTR(syscall(__NR_futex, &data->futex,
@@ -482,15 +450,13 @@ int start_userspace(struct mm_id *mm_id)
 		proc_data->futex = FUTEX_IN_CHILD;
 
 	/*
-	 * In SECCOMP mode, acquire a pidfd to the stub via CLONE_PIDFD (it is
-	 * returned through the legacy-clone parent_tid argument). The monitor
-	 * installs guest mappings into the stub's mm directly via pidfd_mmap(),
-	 * so the stub itself never needs the mmap capability. The ptrace mode
-	 * does not use it and drives the stub directly.
+	 * Acquire a pidfd to the stub via CLONE_PIDFD (it is returned through
+	 * the legacy-clone parent_tid argument). The monitor installs guest
+	 * mappings into the stub's mm directly via pidfd_mmap() in both seccomp
+	 * and ptrace modes, so it needs the pidfd regardless of mode.
 	 */
 	mm_id->pid = clone(userspace_tramp, (void *) sp,
-		    CLONE_VFORK | CLONE_VM | (using_seccomp ? CLONE_PIDFD : 0) |
-		    SIGCHLD,
+		    CLONE_VFORK | CLONE_VM | CLONE_PIDFD | SIGCHLD,
 		    (void *)&tramp_data, &stub_pidfd);
 	if (mm_id->pid < 0) {
 		err = -errno;
@@ -498,8 +464,7 @@ int start_userspace(struct mm_id *mm_id)
 		       __func__, errno);
 		goto out_close;
 	}
-	if (using_seccomp)
-		mm_id->stub_pidfd = stub_pidfd;
+	mm_id->stub_pidfd = stub_pidfd;
 
 	if (using_seccomp) {
 		wait_stub_done_seccomp(mm_id, 1, 1);
@@ -539,10 +504,7 @@ int start_userspace(struct mm_id *mm_id)
 	}
 
 	close(tramp_data.sockpair[0]);
-	if (using_seccomp)
-		mm_id->sock = tramp_data.sockpair[1];
-	else
-		close(tramp_data.sockpair[1]);
+	close(tramp_data.sockpair[1]);
 
 	return 0;
 
@@ -620,28 +582,15 @@ void userspace(struct uml_pt_regs *regs)
 				fatal_sigsegv();
 			}
 
-			/* Must have been reset by the syscall caller */
-			if (proc_data->restart_wait != 0)
-				panic("Programming error: Flag to only run syscalls in child was not cleared!");
-
-			/* Mark pending syscalls for flushing */
-			proc_data->syscall_data_len = mm_id->syscall_data_len;
-
+			/*
+			 * SECCOMP mode no longer batches stub syscalls (mmap is
+			 * installed by the monitor via pidfd_mmap()); this only
+			 * relays the guest trap to the monitor.
+			 */
 			wait_stub_done_seccomp(mm_id, 0, 0);
 
 			sig = proc_data->signal;
 
-			if (sig == SIGTRAP && proc_data->err != 0) {
-				printk(UM_KERN_ERR "%s - Error flushing stub syscalls",
-				       __func__);
-				syscall_stub_dump_error(mm_id);
-				mm_id->syscall_data_len = proc_data->err;
-				fatal_sigsegv();
-			}
-
-			mm_id->syscall_data_len = 0;
-			mm_id->syscall_fd_num = 0;
-
 			err = get_stub_state(regs, proc_data, NULL);
 			if (err) {
 				printk(UM_KERN_ERR "%s - failed to get regs: %d",
@@ -667,17 +616,6 @@ void userspace(struct uml_pt_regs *regs)
 		} else {
 			int pid = mm_id->pid;
 
-			/* Flush out any pending syscalls */
-			err = syscall_stub_flush(mm_id);
-			if (err) {
-				if (err == -ENOMEM)
-					report_enomem();
-
-				printk(UM_KERN_ERR "%s - Error flushing stub syscalls: %d",
-					__func__, -err);
-				fatal_sigsegv();
-			}
-
 			/*
 			 * This can legitimately fail if the process loads a
 			 * bogus value into a segment register.  It will
diff --git a/arch/um/os-Linux/start_up.c b/arch/um/os-Linux/start_up.c
index 054ac03bbf5e..518107622a2d 100644
--- a/arch/um/os-Linux/start_up.c
+++ b/arch/um/os-Linux/start_up.c
@@ -311,9 +311,11 @@ static bool __init init_seccomp(void)
 				      PROT_READ | PROT_WRITE,
 				      MAP_SHARED | MAP_ANON, 0, 0);
 
-	/* Use the syscall data area as stack, we just need something */
-	sp = (unsigned long)&seccomp_test_stub_data->syscall_data +
-	     sizeof(seccomp_test_stub_data->syscall_data) -
+	/*
+	 * We just need some valid stack; use the first page of the shared
+	 * stub_data region (sigstack occupies the following page).
+	 */
+	sp = (unsigned long)&seccomp_test_stub_data->sigstack[0] -
 	     sizeof(void *);
 	pid = clone(seccomp_helper, (void *)sp, CLONE_VFORK | CLONE_VM, NULL);
 
-- 
2.43.0


^ permalink raw reply	[flat|nested] 7+ messages in thread

* [RFC PATCH 6/6] selftests/pidfd: add pidfd_mmap()/pidfd_munmap() tests
  2026-07-10 20:53 [RFC PATCH 0/6] um: introduce pidfd_mmap()/pidfd_munmap() syscalls Cong Wang
                   ` (4 preceding siblings ...)
  2026-07-10 20:53 ` [RFC PATCH 5/6] um: install guest mappings via pidfd_mmap() in both modes Cong Wang
@ 2026-07-10 20:53 ` Cong Wang
  5 siblings, 0 replies; 7+ messages in thread
From: Cong Wang @ 2026-07-10 20:53 UTC (permalink / raw)
  To: Richard Weinberger, Anton Ivanov, Johannes Berg
  Cc: Benjamin Berg, linux-um, linux-kernel, Cong Wang

From: Cong Wang <cwang@multikernel.io>

Exercise the new syscalls against a forked target reached by pidfd:

 - install_and_read: map a parent-owned memfd into the target, verify the
   target's new mapping shows the memfd contents (read via
   process_vm_readv), that MAP_SHARED writes propagate, and that
   pidfd_munmap() removes it. This also covers the key semantic that the
   backing fd is resolved in the *caller's* fd table (the target never
   sees the memfd).
 - anonymous: MAP_ANONYMOUS install + write-back round-trip.
 - bad_args: __spare != 0, undersized @size, non-zero reserved flags, and
   a bad backing fd are all rejected.
 - no_ptrace_access: a non-dumpable target (no ptrace access) yields
   -EPERM, matching the ptrace_may_access() gate.

The suite skips cleanly (TAP "SKIP") when the kernel lacks the syscall.
The pidfd_mmap ABI definitions are kept in pidfd.h alongside the existing
local uapi copies (struct pidfd_info, etc.).

Assisted-by: Claude:claude-opus-4.8
Signed-off-by: Cong Wang <cwang@multikernel.io>
---
 tools/testing/selftests/pidfd/Makefile        |   3 +-
 tools/testing/selftests/pidfd/pidfd.h         |  34 +++
 .../testing/selftests/pidfd/pidfd_mmap_test.c | 234 ++++++++++++++++++
 3 files changed, 270 insertions(+), 1 deletion(-)
 create mode 100644 tools/testing/selftests/pidfd/pidfd_mmap_test.c

diff --git a/tools/testing/selftests/pidfd/Makefile b/tools/testing/selftests/pidfd/Makefile
index 4211f91e9af8..f1c421bf725a 100644
--- a/tools/testing/selftests/pidfd/Makefile
+++ b/tools/testing/selftests/pidfd/Makefile
@@ -4,7 +4,8 @@ CFLAGS += -g $(KHDR_INCLUDES) $(TOOLS_INCLUDES) -pthread -Wall
 TEST_GEN_PROGS := pidfd_test pidfd_fdinfo_test pidfd_open_test \
 	pidfd_poll_test pidfd_wait pidfd_getfd_test pidfd_setns_test \
 	pidfd_file_handle_test pidfd_bind_mount pidfd_info_test \
-	pidfd_xattr_test pidfd_setattr_test pidfd_autoreap_test
+	pidfd_xattr_test pidfd_setattr_test pidfd_autoreap_test \
+	pidfd_mmap_test
 
 TEST_GEN_PROGS_EXTENDED := pidfd_exec_helper
 
diff --git a/tools/testing/selftests/pidfd/pidfd.h b/tools/testing/selftests/pidfd/pidfd.h
index 5a4e78c10f43..91e65283d5fe 100644
--- a/tools/testing/selftests/pidfd/pidfd.h
+++ b/tools/testing/selftests/pidfd/pidfd.h
@@ -203,6 +203,28 @@ struct pidfd_info {
 	__u64 supported_mask;
 };
 
+#ifndef __NR_pidfd_mmap
+#define __NR_pidfd_mmap 472
+#endif
+
+#ifndef __NR_pidfd_munmap
+#define __NR_pidfd_munmap 473
+#endif
+
+#ifndef PIDFD_MMAP_ARGS_SIZE_VER0
+struct pidfd_mmap_args {
+	__u64 size;
+	__u64 addr;
+	__u64 len;
+	__u64 prot;
+	__u64 flags;
+	__u64 pgoff;
+	__s32 fd;
+	__u32 __spare;
+};
+#define PIDFD_MMAP_ARGS_SIZE_VER0	56
+#endif
+
 /*
  * The kernel reserves 300 pids via RESERVED_PIDS in kernel/pid.c
  * That means, when it wraps around any pid < 300 will be skipped.
@@ -267,6 +289,18 @@ static inline int sys_pidfd_getfd(int pidfd, int fd, int flags)
 	return syscall(__NR_pidfd_getfd, pidfd, fd, flags);
 }
 
+static inline long sys_pidfd_mmap(int pidfd, struct pidfd_mmap_args *args,
+				  unsigned int flags)
+{
+	return syscall(__NR_pidfd_mmap, pidfd, args, flags);
+}
+
+static inline long sys_pidfd_munmap(int pidfd, unsigned long addr,
+				    unsigned long len)
+{
+	return syscall(__NR_pidfd_munmap, pidfd, addr, len);
+}
+
 static inline int sys_memfd_create(const char *name, unsigned int flags)
 {
 	return syscall(__NR_memfd_create, name, flags);
diff --git a/tools/testing/selftests/pidfd/pidfd_mmap_test.c b/tools/testing/selftests/pidfd/pidfd_mmap_test.c
new file mode 100644
index 000000000000..39d623ce7b9d
--- /dev/null
+++ b/tools/testing/selftests/pidfd/pidfd_mmap_test.c
@@ -0,0 +1,234 @@
+// SPDX-License-Identifier: GPL-2.0
+
+#define _GNU_SOURCE
+#include <errno.h>
+#include <fcntl.h>
+#include <sched.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <syscall.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <sys/prctl.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <sys/wait.h>
+
+#include "pidfd.h"
+#include "kselftest_harness.h"
+
+#define UID_NOBODY 65535
+#define PATTERN 0xab
+#define MAP_LEN (4 * 1024)
+
+static struct pidfd_mmap_args mmap_args(int fd)
+{
+	struct pidfd_mmap_args args = {
+		.size	= sizeof(args),
+		.addr	= 0,		/* let the kernel choose */
+		.len	= MAP_LEN,
+		.prot	= PROT_READ | PROT_WRITE,
+		.flags	= MAP_SHARED,
+		.pgoff	= 0,
+		.fd	= fd,
+	};
+
+	return args;
+}
+
+static int read_remote(pid_t pid, unsigned long addr, void *buf, size_t len)
+{
+	struct iovec local = { .iov_base = buf, .iov_len = len };
+	struct iovec remote = { .iov_base = (void *)addr, .iov_len = len };
+
+	return process_vm_readv(pid, &local, 1, &remote, 1, 0) == (ssize_t)len ?
+	       0 : -1;
+}
+
+static int target(int sk)
+{
+	char buf;
+	int ret;
+
+	prctl(PR_SET_PDEATHSIG, SIGKILL);
+
+	if (send(sk, "R", 1, 0) != 1)		/* ready */
+		return -1;
+
+	while ((ret = recv(sk, &buf, sizeof(buf), 0)) > 0) {
+		if (buf == 'P' && prctl(PR_SET_DUMPABLE, 0) < 0)
+			return -1;
+		if (send(sk, &buf, 1, 0) != 1)
+			return -1;
+	}
+
+	return ret < 0 ? -1 : 0;
+}
+
+FIXTURE(pidfd_mmap)
+{
+	pid_t pid;
+	int pidfd;
+	int sk;		/* parent side of the socketpair */
+	int memfd;	/* lives in the PARENT fd table */
+	void *local;	/* the parent's own mapping of memfd */
+};
+
+FIXTURE_SETUP(pidfd_mmap)
+{
+	struct pidfd_mmap_args probe;
+	int sk_pair[2];
+	char c;
+
+	/*
+	 * The backing fd is resolved in the *caller's* (parent's) fd table,
+	 * so the target never needs to see it.
+	 */
+	self->memfd = sys_memfd_create("pidfd_mmap", 0);
+	ASSERT_GE(self->memfd, 0);
+	ASSERT_EQ(0, ftruncate(self->memfd, MAP_LEN));
+
+	self->local = mmap(NULL, MAP_LEN, PROT_READ | PROT_WRITE, MAP_SHARED,
+			   self->memfd, 0);
+	ASSERT_NE(MAP_FAILED, self->local);
+	memset(self->local, PATTERN, MAP_LEN);
+
+	ASSERT_EQ(0, socketpair(PF_LOCAL, SOCK_SEQPACKET, 0, sk_pair));
+	self->sk = sk_pair[0];
+
+	self->pid = fork();
+	ASSERT_GE(self->pid, 0);
+	if (self->pid == 0) {
+		close(sk_pair[0]);
+		_exit(target(sk_pair[1]) ? EXIT_FAILURE : EXIT_SUCCESS);
+	}
+	close(sk_pair[1]);
+
+	self->pidfd = sys_pidfd_open(self->pid, 0);
+	ASSERT_GE(self->pidfd, 0);
+
+	ASSERT_EQ(1, recv(self->sk, &c, 1, 0));		/* wait for "R" */
+
+	/* Skip the whole suite if the kernel does not have pidfd_mmap(). */
+	probe = mmap_args(self->memfd);
+	sys_pidfd_mmap(self->pidfd, &probe, 1);		/* reserved flags -> EINVAL */
+	if (errno == ENOSYS)
+		SKIP(return, "pidfd_mmap() is not supported");
+}
+
+FIXTURE_TEARDOWN(pidfd_mmap)
+{
+	EXPECT_EQ(0, close(self->pidfd));
+	EXPECT_EQ(0, close(self->sk));		/* tells the target to exit */
+	munmap(self->local, MAP_LEN);
+	close(self->memfd);
+	EXPECT_EQ(0, wait_for_pid(self->pid));
+}
+
+TEST_F(pidfd_mmap, install_and_read)
+{
+	struct pidfd_mmap_args args = mmap_args(self->memfd);
+	char buf[MAP_LEN];
+	long addr;
+	int i;
+
+	addr = sys_pidfd_mmap(self->pidfd, &args, 0);
+	ASSERT_GE(addr, 0);
+	ASSERT_EQ(0, addr & (sysconf(_SC_PAGESIZE) - 1));	/* page aligned */
+
+	/* The target's new mapping must show the memfd contents. */
+	ASSERT_EQ(0, read_remote(self->pid, addr, buf, MAP_LEN));
+	for (i = 0; i < MAP_LEN; i++)
+		ASSERT_EQ((unsigned char)PATTERN, (unsigned char)buf[i]);
+
+	/* MAP_SHARED: a write the parent makes to memfd is visible remotely. */
+	memset(self->local, 0xcd, MAP_LEN);
+	ASSERT_EQ(0, read_remote(self->pid, addr, buf, sizeof(buf)));
+	ASSERT_EQ((unsigned char)0xcd, (unsigned char)buf[0]);
+
+	/* pidfd_munmap() removes it. */
+	ASSERT_EQ(0, sys_pidfd_munmap(self->pidfd, addr, MAP_LEN));
+	EXPECT_EQ(-1, read_remote(self->pid, addr, buf, MAP_LEN));
+}
+
+TEST_F(pidfd_mmap, anonymous)
+{
+	struct pidfd_mmap_args args = mmap_args(-1);
+	struct iovec local, remote;
+	char in[64], out[64];
+	long addr;
+
+	args.flags = MAP_PRIVATE | MAP_ANONYMOUS;
+
+	addr = sys_pidfd_mmap(self->pidfd, &args, 0);
+	ASSERT_GE(addr, 0);
+
+	memset(out, 0x5a, sizeof(out));
+	local.iov_base = out;
+	local.iov_len = sizeof(out);
+	remote.iov_base = (void *)addr;
+	remote.iov_len = sizeof(out);
+	ASSERT_EQ((ssize_t)sizeof(out),
+		  process_vm_writev(self->pid, &local, 1, &remote, 1, 0));
+
+	ASSERT_EQ(0, read_remote(self->pid, addr, in, sizeof(in)));
+	ASSERT_EQ((unsigned char)0x5a, (unsigned char)in[0]);
+
+	ASSERT_EQ(0, sys_pidfd_munmap(self->pidfd, addr, MAP_LEN));
+}
+
+TEST_F(pidfd_mmap, bad_args)
+{
+	struct pidfd_mmap_args args;
+
+	/* The reserved padding must be zero. */
+	args = mmap_args(self->memfd);
+	args.__spare = 1;
+	EXPECT_EQ(-1, sys_pidfd_mmap(self->pidfd, &args, 0));
+	EXPECT_EQ(EINVAL, errno);
+
+	/* @size below the first published version is rejected. */
+	args = mmap_args(self->memfd);
+	args.size = 8;
+	EXPECT_EQ(-1, sys_pidfd_mmap(self->pidfd, &args, 0));
+	EXPECT_EQ(EINVAL, errno);
+
+	/* The reserved flags argument must be zero. */
+	args = mmap_args(self->memfd);
+	EXPECT_EQ(-1, sys_pidfd_mmap(self->pidfd, &args, 1));
+	EXPECT_EQ(EINVAL, errno);
+
+	/* A bad backing fd (resolved in the caller's table) is -EBADF. */
+	args = mmap_args(-1);
+	EXPECT_EQ(-1, sys_pidfd_mmap(self->pidfd, &args, 0));
+	EXPECT_EQ(EBADF, errno);
+
+	/* A bad pidfd fails. */
+	args = mmap_args(self->memfd);
+	EXPECT_EQ(-1, sys_pidfd_mmap(-1, &args, 0));
+}
+
+TEST_F(pidfd_mmap, no_ptrace_access)
+{
+	struct pidfd_mmap_args args = mmap_args(self->memfd);
+	int uid = getuid();
+	char c;
+
+	/* Drop privilege so CAP_SYS_PTRACE can't bypass the dumpable check. */
+	if (uid == 0)
+		ASSERT_EQ(0, seteuid(UID_NOBODY));
+
+	ASSERT_EQ(1, send(self->sk, "P", 1, 0));	/* target: PR_SET_DUMPABLE 0 */
+	ASSERT_EQ(1, recv(self->sk, &c, 1, 0));
+
+	EXPECT_EQ(-1, sys_pidfd_mmap(self->pidfd, &args, 0));
+	EXPECT_EQ(EPERM, errno);
+
+	if (uid == 0)
+		ASSERT_EQ(0, seteuid(0));
+}
+
+TEST_HARNESS_MAIN
-- 
2.43.0


^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2026-07-10 20:54 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-07-10 20:53 [RFC PATCH 0/6] um: introduce pidfd_mmap()/pidfd_munmap() syscalls Cong Wang
2026-07-10 20:53 ` [RFC PATCH 1/6] pidfd: add " Cong Wang
2026-07-10 20:53 ` [RFC PATCH 2/6] um: acquire a stub pidfd via CLONE_PIDFD in seccomp mode Cong Wang
2026-07-10 20:53 ` [RFC PATCH 3/6] um: install guest mappings via pidfd_mmap() " Cong Wang
2026-07-10 20:53 ` [RFC PATCH 4/6] um: forbid mmap/munmap in the stub seccomp filter Cong Wang
2026-07-10 20:53 ` [RFC PATCH 5/6] um: install guest mappings via pidfd_mmap() in both modes Cong Wang
2026-07-10 20:53 ` [RFC PATCH 6/6] selftests/pidfd: add pidfd_mmap()/pidfd_munmap() tests Cong Wang

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox