[RFC PATCH 1/6] pidfd: add pidfd_mmap()/pidfd_munmap() syscalls
Cong Wang <[email protected]> Fri, 10 Jul 2026 13:53:19 -0700
| Newsgroups | org.infradead.lists.linux-um,org.kernel.vger.linux-kernel |
|---|---|
| Message-ID | <[email protected]> |
From: Cong Wang <[email protected]> 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 <[email protected]> --- 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