From: Cong Wang <xiyou.wangcong@gmail.com>
To: Andy Lutomirski <luto@amacapital.net>
Cc: Kees Cook <kees@kernel.org>,
linux-kernel@vger.kernel.org, Will Drewry <wad@chromium.org>,
Christian Brauner <brauner@kernel.org>,
Andrew Morton <akpm@linux-foundation.org>,
linux-mm@kvack.org, Cong Wang <cwang@multikernel.io>
Subject: [PATCH v5 2/7] seccomp: introduce SECCOMP_IOCTL_NOTIF_PIN_INSTALL
Date: Sat, 4 Jul 2026 16:18:26 -0700 [thread overview]
Message-ID: <20260704231831.354543-3-xiyou.wangcong@gmail.com> (raw)
In-Reply-To: <20260704231831.354543-1-xiyou.wangcong@gmail.com>
From: Cong Wang <cwang@multikernel.io>
SECCOMP_IOCTL_NOTIF_PIN_INSTALL maps a supervisor-owned @memfd at
@target_addr in the trapped task's mm via vm_mmap_remote(),
PROT_READ, MAP_SHARED, MAP_FIXED_NOREPLACE and VM_SEALED. Because the
mapping is sealed, neither the target nor a CLONE_VM peer can munmap,
mremap, mprotect or MAP_FIXED-stomp it; its contents are immutable from
the target's side while the supervisor retains write access through its
own mapping of the same memfd. The install needs no target-side
cooperation, which is what makes the feature usable for fork+execve
sandbox wrappers (Sandlock, Firejail, Bubblewrap-style) that have no
trusted post-exec window to install their own mappings.
The pin is just a sealed VMA owned by the target's mm: it persists until
the task execve()s or exits (a sealed VMA cannot be unmapped piecemeal),
and the kernel keeps no per-pin bookkeeping. A supervisor reuses one
region across many redirects.
Assisted-by: Claude:claude-opus-4.8
Signed-off-by: Cong Wang <cwang@multikernel.io>
---
include/linux/seccomp.h | 5 ++
include/uapi/linux/seccomp.h | 34 ++++++++++
kernel/seccomp.c | 121 +++++++++++++++++++++++++++++++++++
3 files changed, 160 insertions(+)
diff --git a/include/linux/seccomp.h b/include/linux/seccomp.h
index 9b959972bf4a..a91d1fc8a2b8 100644
--- a/include/linux/seccomp.h
+++ b/include/linux/seccomp.h
@@ -16,6 +16,11 @@
#define SECCOMP_NOTIFY_ADDFD_SIZE_VER0 24
#define SECCOMP_NOTIFY_ADDFD_SIZE_LATEST SECCOMP_NOTIFY_ADDFD_SIZE_VER0
+/* sizeof() the first published struct seccomp_notif_pin_install */
+#define SECCOMP_NOTIFY_PIN_INSTALL_SIZE_VER0 32 /* up to @size */
+#define SECCOMP_NOTIFY_PIN_INSTALL_SIZE_VER1 40 /* adds @offset */
+#define SECCOMP_NOTIFY_PIN_INSTALL_SIZE_LATEST SECCOMP_NOTIFY_PIN_INSTALL_SIZE_VER1
+
#ifdef CONFIG_SECCOMP
#include <linux/thread_info.h>
diff --git a/include/uapi/linux/seccomp.h b/include/uapi/linux/seccomp.h
index dbfc9b37fcae..d3249294788b 100644
--- a/include/uapi/linux/seccomp.h
+++ b/include/uapi/linux/seccomp.h
@@ -137,6 +137,37 @@ struct seccomp_notif_addfd {
__u32 newfd_flags;
};
+/**
+ * struct seccomp_notif_pin_install - have the kernel install a sealed
+ * MAP_SHARED mapping of @memfd into the trapped task's mm at @target_addr.
+ *
+ * The supervisor owns @memfd and the kernel installs the mapping without
+ * target-side cooperation. It is read-only and VM_SEALED, so the target and
+ * any CLONE_VM peer cannot munmap, mremap, mprotect or MAP_FIXED-stomp it.
+ * @memfd must be write-sealed (F_SEAL_WRITE or F_SEAL_FUTURE_WRITE, -EINVAL
+ * otherwise) so its bytes cannot be rewritten through any other reference to
+ * the same memfd.
+ *
+ * @id: The ID of an active seccomp notification on this listener,
+ * identifying the trapped task whose mm receives the pin.
+ * @flags: Reserved, must be 0.
+ * @memfd: Supervisor-side fd for the backing memfd. Must be write-sealed.
+ * @target_addr: Page-aligned address in the trapped task's mm to install at.
+ * If non-zero it is MAP_FIXED (no existing mapping may overlap
+ * [@target_addr, @target_addr + @size)); if zero the kernel
+ * picks a free area. The actual address is written back here.
+ * @size: Size of the pin in bytes. Must be page-aligned.
+ * @offset: Page-aligned byte offset into @memfd to map from.
+ */
+struct seccomp_notif_pin_install {
+ __u64 id;
+ __u32 flags;
+ __u32 memfd;
+ __u64 target_addr;
+ __u64 size;
+ __u64 offset;
+};
+
#define SECCOMP_IOC_MAGIC '!'
#define SECCOMP_IO(nr) _IO(SECCOMP_IOC_MAGIC, nr)
#define SECCOMP_IOR(nr, type) _IOR(SECCOMP_IOC_MAGIC, nr, type)
@@ -154,4 +185,7 @@ struct seccomp_notif_addfd {
#define SECCOMP_IOCTL_NOTIF_SET_FLAGS SECCOMP_IOW(4, __u64)
+#define SECCOMP_IOCTL_NOTIF_PIN_INSTALL SECCOMP_IOWR(5, \
+ struct seccomp_notif_pin_install)
+
#endif /* _UAPI_LINUX_SECCOMP_H */
diff --git a/kernel/seccomp.c b/kernel/seccomp.c
index 066909393c38..1c0b3bb71379 100644
--- a/kernel/seccomp.c
+++ b/kernel/seccomp.c
@@ -37,12 +37,19 @@
#ifdef CONFIG_SECCOMP_FILTER
#include <linux/file.h>
#include <linux/filter.h>
+#include <linux/memfd.h>
#include <linux/pid.h>
#include <linux/ptrace.h>
#include <linux/capability.h>
#include <linux/uaccess.h>
#include <linux/anon_inodes.h>
#include <linux/lockdep.h>
+#include <linux/mm.h>
+#include <linux/mman.h>
+#include <linux/mmap_lock.h>
+#include <linux/sched/mm.h>
+#include <linux/task_work.h>
+#include <uapi/asm-generic/mman-common.h>
/*
* When SECCOMP_IOCTL_NOTIF_ID_VALID was first introduced, it had the
@@ -1823,6 +1830,117 @@ static long seccomp_notify_addfd(struct seccomp_filter *filter,
return ret;
}
+static unsigned long seccomp_install_pin(struct task_struct *target,
+ struct file *memfd_file,
+ unsigned long target_addr, size_t size,
+ unsigned long offset)
+{
+ struct mm_struct *mm;
+ unsigned long ret;
+
+ if (!VM_SEALED)
+ return -EOPNOTSUPP;
+
+ mm = get_task_mm(target);
+ if (!mm)
+ return -ESRCH;
+
+ /*
+ * Install a sealed, read-only mapping. A fixed request (@target_addr
+ * != 0) is MAP_FIXED_NOREPLACE: an existing mapping yields -EEXIST
+ * rather than being silently clobbered. A request of 0 lets the kernel
+ * pick a free area in the target mm.
+ */
+ ret = vm_mmap_remote(mm, memfd_file, target_addr, size, PROT_READ,
+ MAP_SHARED | MAP_FIXED_NOREPLACE,
+ offset >> PAGE_SHIFT, VM_SEALED);
+ mmput(mm);
+ if (IS_ERR_VALUE(ret))
+ return ret;
+ if (target_addr && ret != target_addr)
+ return -ENOMEM;
+ return ret;
+}
+
+static long seccomp_notify_pin_install(struct seccomp_filter *filter,
+ struct seccomp_notif_pin_install __user *upin,
+ unsigned int size)
+{
+ struct seccomp_notif_pin_install pin;
+ struct seccomp_knotif *knotif;
+ struct task_struct *target;
+ struct file *memfd_file;
+ unsigned long addr;
+ int seals;
+ long ret;
+
+ BUILD_BUG_ON(sizeof(pin) < SECCOMP_NOTIFY_PIN_INSTALL_SIZE_VER0);
+ BUILD_BUG_ON(sizeof(pin) != SECCOMP_NOTIFY_PIN_INSTALL_SIZE_LATEST);
+
+ if (size < SECCOMP_NOTIFY_PIN_INSTALL_SIZE_VER0 || size >= PAGE_SIZE)
+ return -EINVAL;
+
+ ret = copy_struct_from_user(&pin, sizeof(pin), upin, size);
+ if (ret)
+ return ret;
+
+ if (pin.flags)
+ return -EINVAL;
+ if (!pin.size || !IS_ALIGNED(pin.target_addr, PAGE_SIZE) ||
+ !IS_ALIGNED(pin.size, PAGE_SIZE) || !IS_ALIGNED(pin.offset, PAGE_SIZE))
+ return -EINVAL;
+ if (pin.target_addr + pin.size < pin.target_addr)
+ return -EINVAL;
+ if (pin.offset + pin.size < pin.offset)
+ return -EINVAL;
+
+ memfd_file = fget(pin.memfd);
+ if (!memfd_file)
+ return -EBADF;
+
+ seals = memfd_get_seals(memfd_file);
+ if (seals < 0 || !(seals & (F_SEAL_WRITE | F_SEAL_FUTURE_WRITE))) {
+ ret = -EINVAL;
+ goto out_fput;
+ }
+
+ ret = mutex_lock_interruptible(&filter->notify_lock);
+ if (ret < 0)
+ goto out_fput;
+
+ knotif = find_notification(filter, pin.id);
+ if (!knotif) {
+ ret = -ENOENT;
+ goto out_unlock;
+ }
+ if (knotif->state != SECCOMP_NOTIFY_SENT) {
+ ret = -EINPROGRESS;
+ goto out_unlock;
+ }
+
+ target = knotif->task;
+ get_task_struct(target);
+ mutex_unlock(&filter->notify_lock);
+
+ addr = seccomp_install_pin(target, memfd_file, pin.target_addr,
+ pin.size, pin.offset);
+ put_task_struct(target);
+ if (IS_ERR_VALUE(addr))
+ ret = addr;
+ else if (put_user(addr, &upin->target_addr))
+ /* Pin is installed and sealed; we just can't report where. */
+ ret = -EFAULT;
+ else
+ ret = 0;
+ goto out_fput;
+
+out_unlock:
+ mutex_unlock(&filter->notify_lock);
+out_fput:
+ fput(memfd_file);
+ return ret;
+}
+
static long seccomp_notify_ioctl(struct file *file, unsigned int cmd,
unsigned long arg)
{
@@ -1847,6 +1965,9 @@ static long seccomp_notify_ioctl(struct file *file, unsigned int cmd,
switch (EA_IOCTL(cmd)) {
case EA_IOCTL(SECCOMP_IOCTL_NOTIF_ADDFD):
return seccomp_notify_addfd(filter, buf, _IOC_SIZE(cmd));
+ case EA_IOCTL(SECCOMP_IOCTL_NOTIF_PIN_INSTALL):
+ return seccomp_notify_pin_install(filter, buf,
+ _IOC_SIZE(cmd));
default:
return -EINVAL;
}
--
2.43.0
next prev parent reply other threads:[~2026-07-04 23:19 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-04 23:18 [PATCH v5 0/7] seccomp: non-cooperative pinned-memfd argument redirect Cong Wang
2026-07-04 23:18 ` [PATCH v5 1/7] mm: add __do_mmap() and vm_mmap_remote()/vm_munmap_remote() Cong Wang
2026-07-04 23:18 ` Cong Wang [this message]
2026-07-04 23:18 ` [PATCH v5 3/7] seccomp: add __NR_seccomp_* aliases for rt_sigreturn and clone/fork Cong Wang
2026-07-04 23:18 ` [PATCH v5 4/7] seccomp: add kernel-installed pinned-memfd redirect Cong Wang
2026-07-04 23:18 ` [PATCH v5 5/7] seccomp: re-validate a redirected syscall against outer filters Cong Wang
2026-07-04 23:18 ` [PATCH v5 6/7] docs/seccomp: document pinned-memfd redirect ioctls Cong Wang
2026-07-04 23:18 ` [PATCH v5 7/7] selftests/seccomp: cover non-cooperative pinned-memfd install Cong Wang
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260704231831.354543-3-xiyou.wangcong@gmail.com \
--to=xiyou.wangcong@gmail.com \
--cc=akpm@linux-foundation.org \
--cc=brauner@kernel.org \
--cc=cwang@multikernel.io \
--cc=kees@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=luto@amacapital.net \
--cc=wad@chromium.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox