Kernel exec confinement on Linux is a Landlock layer inside the envelope
Amended 2026-10-06. The parent builds the ruleset, by raw syscalls in place of the
landlockcrate, which can neither hand a ruleset over nor adopt one, and lends it to the payload at a fixed descriptor; the payload probes nothing and enters what it is given. ral’s own binary is admitted by its boot-pinned descriptor, the inode, never by name. Finding 2 stands with one change of hands:Referon/is the one rule the payload adds before entering, because the envelope’s root exists only inside, and grantingReferon every mounted handle instead would leave/tmp,/dev/shmand the cwd stand-in, tmpfs the parent never sees, underEXDEV. The floor fails closed: a grant that limits which programs may run is refused on a kernel without Landlock, and a payload promised a ruleset that never arrived refuses to run; only an unrestricted exec projection launches with nothing to enter. The rename test reads the inode, notmv’s exit: uutilsmv(Fedora 42 and later) hidesEXDEVby copying and unlinking, so a refused rename passes any test that asks only whether the file moved.Amended 2026-10-06: blocks hold in the kernel. Finding 1 no longer leaves a deny inside an admit to the gate. The parent reads the kernel list back as a table (
ExecRules::from_kernel) and admits each allowed directory with nothing blocked beneath it as one hierarchy rule; one holding a deny dir, a deny file or a walked veto is listed along the spine to each block only, every entry judged byTable::verdict, so a deny by another spelling holds as in the guard, and each subdirectory in turn. That is a snapshot: a program added to an expanded directory after launch is denied until the next launch, where Seatbelt admits it at once, the fail-closed side of a declared asymmetry; a directory with no block beneath it,target/underexec: cwd:/, is never expanded. A veto is walked only where no trusted write reaches (write_reach), since under a trusted admit the child can author a renamed copy and the veto is advisory (a-veto-freezes-what-a-write-covers); walking the user’s own tree per launch would buy nothing for it. What stays with the gate: those vetoes, programs added after launch in an expanded directory, and anything beneath a directory the walk cannot read, which is admitted nowhere. A hard link aliases an inode, as Seatbelt’sliteraldoes.
Closes linux-exec-confinement. A
confined payload now enters a Landlock domain of its own
(core/src/sandbox/linux/landlock.rs) rendering ExecProjection::Restricted,
so a descendant’s sh -c or find -exec is gated by the kernel and not only
by ral’s dispatch.
The layer goes on inside bwrap, entered by the payload itself. A Landlock
domain that handles any filesystem right forbids mount(2), so a layer applied
before the envelope kills bwrap at its first act (measured: bwrap: Failed to make / slave). The order is therefore bwrap, then the payload’s own
restrict_self, and never the reverse.
Four findings shape it.
-
Landlock is allow-list only. It cannot express “everything but this”, so the filesystem half stays with bwrap’s mounts, and on the exec half a deny outside every admit is already absence, while a deny inside an admit is rendered by subtraction, the admit’s entries the table still allows (see the second amendment). Where Seatbelt carries a deny into the kernel as a rule, Landlock carries it as an absence over the tree at launch.
-
A layer restricts only what it handles — except
Refer. Any domain refuses every cross-directory rename and link withEXDEVunless it both handlesLANDLOCK_ACCESS_FS_REFERand grants it, which under ABI 1 no domain can, the right not existing there to grant. From ABI 2 it does, so the layer handlesExecute | Referand grantsReferon/to restore them — restored, not restricted, though non-escalation still givesEXDEVfor a rename or link that would gain the file rights its new parent has and its old one did not, such as moving an unadmitted file into anExecute-admitted directory. -
execveof a dynamic binary needsExecuteon the binary and itsPT_INTERPonly — and of a#!script, on its interpreter, which exec-carriers admits as a carrier. The shared libraries the loader then maps need no right from this layer. The platform base is therefore the linker files themselves —ld*.so*under the/liband/usr/libvariants and one level down for Debian multiarch — and never a command directory, since/usr/binin the base would be a layer that denies nothing. A missing linker shows up asEACCESon 7.1, notENOENT. Admitting the loaders bounds what the layer is: a gate on which paths may beexecved, never on which code a process runs. An admitted loader handed an unadmitted binary as an argument (ld-linux-x86-64.so.2 ./payload), an admitted interpreter handed a script by path, andmmap(PROT_EXEC)of file contents all run code the layer never sees, and only an fs layer handlingReadFile— costed and declined below — would see them. -
The signal scope is an envelope invariant, not a projection.
LANDLOCK_SCOPE_SIGNAL(ABI 6, Linux 6.12) is applied on every launch, including an unrestricted exec projection, and givesEPERMwhen a confined child signals a host process. That closes the same-uidkillhole on hosts where the container runtime denies a pid namespace, with no namespace needed.
Two floors, both probed by the syscall rather than read off uname: ABI 1 for
exec confinement — where a confined payload also loses cross-directory rename
and link, Refer being ungrantable there — and ABI 6 for the scope. A kernel
with no Landlock at all (ENOSYS, EOPNOTSUPP) enters nothing when exec is
unrestricted, HostEnvelope recording each invariant as unheld, and refuses a
grant that limits which programs may run (plan); any other errno from the probe
is a host that cannot be read rather than one without the feature, and refuses
the launch with confinement_unavailable.
A caution for anyone reconsidering an fs layer here: once any layer handles
ReadFile, execve needs ReadFile on the binary and the loader needs it on
the whole library closure (measured) — a far larger rule set than exec alone.
Cost is not the obstacle: rules run about 1 µs each with no ceiling observed at
20 000.
See also core, grant, two enforcers.