Re: [PATCH RFC 0/3] landlock: Add access rights on created files

From
Günther Noack <hidden>
Date
2026-10-26 07:22:37
Also in
linux-doc, linux-kselftest, lkml
Reply to
Farid Zakaria, 2026-10-25
On Sun, Oct 25, 2026 at 10:52:14AM -0700, Farid Zakaria wrote:
On Sun Oct 25, 2026 at 7:57 AM PDT, Günther Noack wrote:
quoted
On Sun, Oct 25, 2026 at 12:07:10PM +0100, Günther Noack wrote:
quoted
Hello Farid!

On Sat, Oct 24, 2026 at 01:39:16PM -0700, Farid Zakaria wrote:
quoted
This is a proposal for an idea to add support for landlock to work
better with build systems such as Nix where they want to apply
restrictions but the paths may not yet exist.

TL;DR;
========

Landlock rules attach to inodes that exist when a ruleset is built.
That makes a directory shared between trusted and sandboxed tasks hard
to confine: the sandbox may create entries there with
LANDLOCK_ACCESS_FS_MAKE_*, but no rule can name those entries ahead of
time, and granting read access on the directory to cover them exposes
every other entry as well. This series adds a rule flag that grants
access rights on the entries a domain creates in a directory, instead
of on the directory.

Motivation
==========

The motivating user is the Nix build sandbox. Every Nix build runs in
its own sandbox, and a build may only read its declared inputs in
/nix/store and must be able to write and read back the outputs it
creates there. Nix enforces this with a private mount namespace: it
creates an empty store directory and bind-mounts each input path into
it, one mount per input. Nix has paid this per-input cost since the
sandbox was introduced [1].

The cost is paid per build and grows with the size of the input
closure. Nix is moving towards much finer-grained builds (many small
derivations instead of a few large ones), so the number of sandboxes
grows while each build's own work shrinks, and the sandbox setup is
becoming the bottleneck. A typical build has hundreds of inputs; large
ones have thousands.

A microbenchmark sets up and tears down a Nix-like sandbox (user,
mount, PID, IPC, UTS and network namespaces, pivot_root into a chroot,
removing the chroot afterwards). It compares one bind mount per input
with a single bind mount of the whole store plus one Landlock rule per
input. Median latency per sandbox, one sandbox at a time, Linux 7.1,
ext4, 16 cores:

  inputs   bind mount per input   one bind mount + Landlock   speedup
      10         3.2 ms                    2.3 ms               1.4x
     100        11.6 ms                    2.8 ms               4.1x
    1000        79.3 ms                    6.4 ms              12.4x
    5000       400.1 ms                   24.4 ms              16.4x

With Landlock, an input costs an O_PATH open and a landlock_add_rule()
call: there is a single mount, and no mountpoint directory to create
before the build and remove after it (removing the 1000 empty
mountpoints alone took 17 ms).

The single bind mount is only sound if the store listing and the
undeclared store paths stay unreadable while the build can still use
its outputs. Without this series, covering the outputs requires a rule
on /nix/store itself, which makes every store path readable again.

[1] https://github.com/NixOS/nix/issues/179
I have not done a thorough code review yet, and will focus on the
general approach.

I am generally in favor of this approach where we keep track of the
directory inode plus the file name.
Apologies, I think I was jumping to conclusions here when I read this
too superficially - I realize that you have implemented something
different;

If I understand correctly, your proposed patch set is granting the
access rights on every file that you are creating freshly, but not on
files that were created by other processes in the same directory.  The
semantics, from what I can see, seem like a Landlock variant to what
the "sticky bit" does in /tmp -- you have access to what you create
yourself, but not to the files created by other processes.


## Alternative suggestion

I have been discussing the other approach with Mickaël a while back.
This would also work in use cases where we want to express a rule on a
path whose underlying file inode might change, with examples being
/etc/resolv.conf (recreated by a DHCP client) or Pathname Unix Domain
Sockets (commonly recreated when services restart).

In that approach, we'd add a rule like

/* pseudocode; name and exact layout TBD */
struct landlock_path_beneath2_attr {
	__u64 allowed_access;
        __s32 parent_fd;
        char  *basename;
}

During the path walk when checking for an access right, it would match
if the parent_fd matches an inode and the name of the next path
component is basename.  For example, to access a (potentially
changing) /etc/resolv.conf:

struct landlock_path_beneath_attr2 attr = {
	.allowed_access = READ_ONLY_ACCESSES,
        .parent_fd      = open("/etc", O_PATH|O_CLOEXEC),
        .basename       = "resolv.conf",
}

Or to allow the creation of an output file:

struct landlock_path_beneath_attr2 attr = {
	.allowed_access = READ_WRITE_ACCESSES | LANDLOCK_ACCESS_FS_MAKE_REG,
        .parent_fd      = open("/output/directory", O_PATH|O_CLOEXEC),
        .basename       = "mymodule.o",
}

Would that be a model that would work for your Nix use case as well?
I'm imagining that you are declaring the exact list of output files,
is that correct?

–Günther
Gunther,

From my reading of your suggestion and the code, I think that would work
for Nix and maybe a little simpler. Nix does know ahead of time the
output directory (/nix/store/<hash>-<name>, one per output), so
a parent_fd and a basename that we walk uptowards would be sufficient
without creating inodes.

This sounds like a good approach to me and my research upon your
suggestion is that it would simplify the proposal as well (drop
a patch). Should I try to implement this and send a new patch series?
(Disclosure: Will use AI to help me write the code, but will review and
test it myself.)
I'm on board with this approach, but I'd recommend briefly waiting for
Mickaël's ack, because he is the one who gives final approval on these
patches.  (In the interest of everyone's time, might be better to
first figure out which of these approaches is viable, before we jump
into more implementation details.)

Mickaël, can you please double check and confirm that the approach I
described above is in line with the one you also had in mind when we
discussed it at LSS?


The use of AI is fine in Landlock, I've used it myself for patches in
the past.  (The Linux-wide rules apply of course, as documented in [1]
-- Take full responsibility for the contribution and make sure you
understand it the same as if you had written it by hand.)

–Günther


[1] https://docs.kernel.org/process/coding-assistants.html

P.S.: The requirement for the "Assisted-by" tag has been dropped in
commit 1e035297ffda ("docs: remove Assisted-by references") now.
Thread (17 messages) 17 messages, 5 authors, last reply 2d ago
Keyboard shortcuts
hback out one level
jnext message in thread / next series-diff position
kprevious message in thread / previous series-diff position
ldrill in
Escclose help / fold thread tree
?toggle this help