[PATCH v5 2/7] seccomp: introduce SECCOMP_IOCTL_NOTIF_PIN_INSTALL
From: Cong Wang
Date: Sat Jul 04 2026 - 19:19:36 EST
From: Cong Wang <cwang@xxxxxxxxxxxxxx>
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@xxxxxxxxxxxxxx>
---
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