Linux-mm Archive on lore.kernel.org
 help / color / mirror / Atom feed
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 0/7] seccomp: non-cooperative pinned-memfd argument redirect
Date: Sat,  4 Jul 2026 16:18:24 -0700	[thread overview]
Message-ID: <20260704231831.354543-1-xiyou.wangcong@gmail.com> (raw)

From: Cong Wang <cwang@multikernel.io>

The seccomp user-notification SECCOMP_USER_NOTIF_FLAG_CONTINUE response
carries an inherent TOCTOU: once the supervisor decides to let a syscall
continue, the target (or a CLONE_VM peer) can rewrite the memory behind a
pointer argument before the kernel reads it. This is documented in the
UAPI header and is why the notifier "cannot be used to implement a
security policy" today.

The cooperative way around this is for the target to map a shared memfd
and mseal() it during a trusted setup window, so the supervisor can hand
the kernel an immutable buffer. That window does not exist for the common
fork()+execve() sandbox model, where the supervisor wants to confine an
uncooperative (or legacy) binary it did not write.

This series lets the supervisor close the TOCTOU without any target-side
cooperation:

  - The kernel installs a sealed, read-only, MAP_SHARED mapping of a
    supervisor-owned memfd directly into the trapped task's mm
    (SECCOMP_IOCTL_NOTIF_PIN_INSTALL). The mapping is VM_SEALED at
    creation, so neither the target nor a CLONE_VM peer can unmap,
    remap, mprotect or MAP_FIXED-stomp it. The backing memfd must be
    write-sealed (F_SEAL_WRITE / F_SEAL_FUTURE_WRITE), so its bytes
    cannot be rewritten through any other reference either; the
    supervisor stages the argument data through its own pre-seal mapping.

  - The supervisor then resumes the syscall with selected argument
    registers rewritten (SECCOMP_IOCTL_NOTIF_SEND_REDIRECT), the pointer
    ones aimed into a pin. Each pointer substitution is validated so the
    whole access [ptr, ptr+len) lies inside a sealed, read-only pin of
    the supervisor's memfd that still lives in the target's current mm;
    original registers are restored at syscall exit for ABI compliance.

Because the data the kernel acts on lives in an immutable pin, the
target can no longer win the race. execve() is handled as a first-class
case: its pathname is copied from the pin before the old mm is torn
down, and the register-restore is skipped once the program image has
been replaced (detected via self_exec_id).

A redirected syscall is re-validated against the outer filters in the
target's filter chain, so an inner notifier cannot use a redirect to
smuggle a syscall past a policy an outer filter enforces (e.g. redirect
to a blocked unshare()); see patch 5.

sandlock [1], a non-cooperative seccomp sandbox supervisor, will use
this to enforce argument-level policy on uncooperative targets.

[1] https://github.com/multikernel/sandlock

Patch 1 adds the mm plumbing: __do_mmap(), a variant of do_mmap() that
targets a caller-supplied mm (do_mmap() stays a current->mm wrapper, so
no existing caller changes), and vm_mmap_remote()/vm_munmap_remote(),
high-level helpers for installing and removing the sealed pin. Patch 2
adds PIN_INSTALL, patch 3 adds the __NR_seccomp_* syscall aliases, patch
4 adds SEND_REDIRECT, patch 5 adds the outer-filter re-validation, patch
6 documents the ABI, and patch 7 adds selftests.

Changes since v4:
  - Fixed READ_IMPLIES_EXEC and VM warning in patch 1
  - Fixed TRACE re-validation case in patch 5
  - New patch 3: split the __NR_seccomp_* syscall aliases (arch-
    overridable, covering rt_sigreturn and the clone/fork family) into
    their own patch; x86 maps the compat (ia32) numbers.
  - SEND_REDIRECT now also refuses the clone/fork family (clone, clone3,
    fork, vfork) with -EOPNOTSUPP, not just rt_sigreturn: rewriting a
    task-creating syscall's registers has no use case and is unsafe.
  - Renamed vm_mmap_seal_remote() to vm_mmap_remote() and added the
    vm_munmap_remote() counterpart (patch 1).
  - vm_mmap_remote() now bounds the mapping within the target mm's own
    address space with an overflow-safe task_size check, including the
    caller-supplied fixed-address path.
  - Compat: for SECCOMP_ARCH_COMPAT targets, redirected pointer arguments
    are truncated to 32 bits before the pin range is validated.
  - Selftests: two new tests -- redirect_denied_syscalls (rt_sigreturn
    and the clone/fork family are all rejected with -EOPNOTSUPP) and
    redirect_revalidate_chain (the re-validation walks the entire outer
    filter stack, not just the nearest filter); plus error-path hardening
    so a broken kernel fails or skips rather than hanging.

Changes since v3:
  - Split the single seccomp patch into PIN_INSTALL (patch 2) and
    SEND_REDIRECT (patch 4) for reviewability.
  - New patch 5: re-validate a redirected syscall against the outer
    filters in the stack, closing the bypass Andy described (an inner
    notifier redirecting to a syscall an outer filter blocks, e.g.
    unshare()).
  - Signals: the argument restore now runs before signal/restart
    processing. It is queued as task_work with TWA_RESUME -- not the
    TWA_SIGNAL discussed on-list, which makes signal_pending() true for
    the whole redirected syscall and livelocks an interruptible one.
    TWA_RESUME still runs the restore at the top of get_signal(), before
    the signal frame is built and before any -ERESTART* rewind; on a
    restart the syscall re-traps seccomp and the supervisor is notified
    again. rt_sigreturn is refused (-EOPNOTSUPP).
  - At most one redirect-capable notifier may exist in a filter chain
    (-EBUSY); ordinary notifiers are unconstrained. Syscalls with
    complex signal/restart behaviour (nanosleep, futex(FUTEX_WAIT), ...)
    are out of scope and should not have their arguments redirected.
  - PIN_INSTALL: target_addr == 0 lets the kernel pick a free address in
    the target mm (avoids a racy userspace /proc/<pid>/maps scan), and a
    new offset field lets one memfd back several disjoint pins.
  - New patch 6: Documentation/userspace-api/seccomp_filter.rst.
  - Selftests expanded: install into a fresh post-execve mm, stateless
    churn, outer-filter re-validation, ABI/versioning, and a
    signal-ordering regression test.

Changes since v2:
  v3 was a redesign that dropped the v2 SECCOMP_IOCTL_NOTIF_INJECT
  approach (an in-kernel reimplementation of a syscall whitelist) in
  favour of redirecting the real syscall into a sealed pin, as suggested
  by Andy.

Changes since v1:
  v2 was a redesign that dropped the v1 SECCOMP_IOCTL_NOTIF_PIN_ARGS
  approach.

All pinned-memfd and redirect selftests pass (seccomp_bpf: 119/119).

Cong Wang (7):
  mm: add __do_mmap() and vm_mmap_remote()/vm_munmap_remote()
  seccomp: introduce SECCOMP_IOCTL_NOTIF_PIN_INSTALL
  seccomp: add __NR_seccomp_* aliases for rt_sigreturn and clone/fork
  seccomp: add kernel-installed pinned-memfd redirect
  seccomp: re-validate a redirected syscall against outer filters
  docs/seccomp: document pinned-memfd redirect ioctls
  selftests/seccomp: cover non-cooperative pinned-memfd install

 .../userspace-api/seccomp_filter.rst          |  109 ++
 arch/x86/include/asm/seccomp.h                |    6 +
 include/asm-generic/seccomp.h                 |   45 +
 include/linux/mm.h                            |    5 +
 include/linux/seccomp.h                       |   12 +-
 include/uapi/linux/seccomp.h                  |   87 ++
 kernel/seccomp.c                              |  511 ++++++-
 mm/internal.h                                 |    8 +
 mm/mmap.c                                     |   87 +-
 mm/nommu.c                                    |   22 +-
 mm/util.c                                     |   97 ++
 mm/vma.c                                      |   35 +-
 mm/vma.h                                      |    6 +-
 tools/testing/selftests/seccomp/seccomp_bpf.c | 1251 +++++++++++++++++
 14 files changed, 2231 insertions(+), 50 deletions(-)


base-commit: 87320be9f0d24fce67631b7eef919f0b79c3e45c
-- 
2.43.0



             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 Cong Wang [this message]
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 ` [PATCH v5 2/7] seccomp: introduce SECCOMP_IOCTL_NOTIF_PIN_INSTALL Cong Wang
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-1-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