[RFC PATCH 1/6] pidfd: add pidfd_mmap()/pidfd_munmap() syscalls
Cong Wang
xiyou.wangcong at gmail.com
Fri Jul 10 13:53:19 PDT 2026
From: Cong Wang <cwang at 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 at 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
More information about the linux-um
mailing list