All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC] Null Namespaces
@ 2026-06-24 22:51 John Ericson
  2026-06-24 23:06 ` Andy Lutomirski
                   ` (3 more replies)
  0 siblings, 4 replies; 38+ messages in thread
From: John Ericson @ 2026-06-24 22:51 UTC (permalink / raw)
  To: Li Chen, Cong Wang, Christian Brauner, linux-arch
  Cc: linux-kernel, linux-fsdevel, linux-api, Arnd Bergmann,
	Andy Lutomirski, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
	Dave Hansen, H. Peter Anvin, Jan Kara, Jonathan Corbet,
	Shuah Khan, Alexander Viro, Kees Cook, Sergei Zimmerman,
	Farid Zakaria

Hello, I am hoping to discuss an idea I've had for a while, that I am
calling "null namespaces" that has become more relevant with some recent
other discussions. First I'll discuss null namespaces in general terms,
and then I'll link those recent discussions and relate null namespaces
to them.

### Null namespaces

The essence of null namespaces is trying to give processes as little
ambient authority as possible, so they are lighter weight and allowed to
do even less than fully unshared processes today.

Namespaces as they exist today are frequently described as an isolation
mechanism, but I think this is the conflation of two different things.
*Removing* a new process from its parent's namespaces unquestionably is
increasing isolation --- no disagreement there. But putting the process
in new namespaces is something else; I would call it supporting
"delusions of grandeur" of that process. For example, namespaces allow a
process to do mounts, have `CAP_SYS_ADMIN`, create network interfaces,
look up other processes by PID, etc.

Conceptually, to remove a process from one ambient authority scope (the
very name "namespaces" indicates they are about ambient authority)
should not require putting it in some ambient authority scope. Just
because, for example, the process cannot see one mount tree, doesn't
mean it needs to see another.

Here's what I am thinking would happen concretely:

First, the simpler cases:

#### Null mount namespace

- requires:

  - null root file system: absolute paths don't work.

  - null current working directory: relative paths with traditional,
    non-`*at` system calls (and `*at` ones using `AT_FDCWD`) don't work.

- All operations relating to the "ambient" mount tree don't work.

- `*at` operations with a file descriptor do work.

- The new fd-based mount APIs with detached mounts do work, modulo
  the calling process having enough permissions (as usual).

#### Null network namespace

- No network interfaces

- No abstract Unix sockets

#### Null IPC namespace

- cannot create or look up either type of message queue

#### Null UTS namespace

- no hostname or domainname: `uname`, `gethostname`/`sethostname`, and the
  related `/proc/sys/kernel` sysctls all fail.

#### Null user namespace

- Process has no user or group ids

- All uid/gid-based authorization lookups return "denied"

- -1 / "nobody" IDs for operations we don't want to fail (like `fstat`)
  can be used.

Note how in each of these, the notion of there "existing" a "single"
null namespace or not is degenerate --- every process with a null
namespace field is as isolated from one another (in terms of the axis
that namespace regulates) as they are from processes that are in other
namespaces. It is truly a minimal permission level, and (as we shall
see) cheap too, because it is just a null pointer in `task_struct`.

Then for the nested ones --- PID and cgroup --- we cannot have quite a
null namespace in the same sense, because it is an important property
that these namespaces are hierarchical up to the root namespaces.
Instead of having a disjoint null namespace, we need a null namespace
with a parent.

#### Null PID namespace

- cannot look up other processes by PID

- current process ID lookup fails

- current process's parent process ID lookup fails

- current process still assigned IDs in parent PID namespaces, per usual

#### Null cgroup namespace

- Process still can have resources restricted according to parent cgroup

- Process unaware of cgroup hierarchy though --- blind to who/how it is
  constrained

In these cases, we cannot just implement with a null pointer, because we
still need a valid parent namespace. However, we shouldn't need any info
*but* the parent namespace. A pair of a pointer and a bool indicating
null namespace with parent namespace or actual namespace membership,
with some sort of helper to get the parent namespace in either case
(since the actual namespace has its parent), should implement this.

Finally there is the time namespace. Conceptually a null time namespace
is simple enough --- you cannot look up the time! --- but the
implementation is a bit more complex to get right because of the vDSO
for certain timing operations.

### General Motivation

Why am I so interested in this stuff?

Firstly it is because I have always been interested in a more strictly
object-capability-based userland, and projects like
Capsicum/CloudABI/WASI. I think going all in on file descriptors is
generally the direction that Linux has been going in, and it creates a
genuinely better programming model than the traditional Unix one with
all its ambient authority, and the TOCTOU and other issues that attend
it.

Today's container idioms and the "delusions of grandeur" that namespaces
provide are great for retrofitting existing software to run in a more
isolated environment. But I don't want that to be the ceiling of our
ambitions. Especially in this age of LLM refactoring, it is very easy to
get both new and existing software to abide by the more limited set of
allowed operations that null-namespace processes allow. And the
modifications that that entails (more `openat`, more socket activation,
etc.) make that software (in my view) simply *better* --- I would want
it to work that way with or without these constraints forcing the issue.

Secondly, and more concretely/imminently as a Nix developer, I am very
interested in the performance and overhead of process isolation. It is
very much my ambition to move Nix into the Bazel/Buck space of ever more
numerous and fine-grained atomic build steps (i.e. small compilation
units, not "packages"), but to do this *without* sacrificing Nix's
strong sandboxing guarantees that make our build plans so self-contained
and thus the ease of onboarding new Nix users.

I think this "null namespace" sandboxing will likely be simpler and more
performant than creating and destroying a bunch of regular namespaces
for each compilation unit. And while it will no doubt take some compiler
/ other tool patching to fix up any assumptions that get in the way of
running processes with so few permissions, I am happy to take a stab at
that too. Nix is, after all, for "tool-assisted yak shaves" as one put
it --- patching GCC / Clang / whatever and then rebuilding the world is
something we are quite good at.

Lastly, I'll add that the traditional way people have thought about
things like Capsicum/CloudABI is custom personalities/seccomp rules, but
IMO trying to tackle the massive UAPI surface area so shallowly is ugly
and unmaintainable. Nulling out namespace fields in `task_struct`,
conversely, attacks the problem at its core, much more elegantly, and
makes it easy to handle both current *and future* syscalls in a
minimally invasive and maintainable manner.

### Null namespaces and process spawning

Why bring this up now?

Recently [1], Li Chen took a stab at the venerable old goal of making a
better process spawning UAPI than fork/clone + exec. I am quite excited
to see this happen, as it generally dovetails very nicely with the
object capability goals I have above. (E.g. making it performant and
idiomatic to opt-in, rather than opt-out of sharing file descriptors
with a child process is very good for a world where all
resource/privilege sharing is done with file descriptors.)

One problem with clone that didn't yet come up is that its defaults are
not good from a security perspective: sharing by default, and unsharing
as the opt in means that one must remember and take active measures to
ensure that child processes get *less* privileges. This is very bad ---
secure practices mean that the "lazy programmer" and the "smallest
program" must always err on the side of giving the child process *less*
privileges. This is the only way economics and the "principle of least
privilege" will work together, rather than against each other (and
economics is quite likely to win when they are working against each
other).

The reason that clone *doesn't* work that way is, of course,
performance: it would be wasteful to unshare and create new namespaces
when they are just going to be thrown away because the user wants to
share after all.

Null namespaces I think elegantly work around this performance/security
trade-off, while also avoiding the need for gazillion-parameter syscalls
like clone. This is because, as the most secure option, and a cheap
option, they are the rightful default for a new process creation API.

1. When an "embryonic" (under construction, not yet ready to be
   scheduled) task is first created, it should have all null namespaces.

2. Separate syscalls (`io_uring` exists for batching, we don't need to
   reinvent an ad-hoc batch solution) can exist for setting the
   namespaces on the process, where either "sharing" (use parent process
   namespace) or "unsharing" (use fresh namespace, usually derived from
   the parent process namespace but perhaps derived from a different
   one) are choices that can be opted into instead of the null namespace
   default.

3. After all state is initialized (arguments, environment variables,
   file descriptors, namespaces, etc.), the process can be "birthed",
   and submitted as ready to be scheduled.

This design is very natural to me, but its full naturality is *only*
available with the null namespace option. Otherwise we are stuck in a
place of no good defaults, and the "builder pattern" seems more awkward.

Also in [2], I bring up a design for unix sockets without the file
system or the "abstract" socket namespace, and how I want to avoid both
in order to firmly rule out TOCTOU and other ambient authority issues. I
think those arguments stand on their own, but the possibility of a null
network namespace sharpens the issue: it forces the `O_PATH` FD stuff I
discuss to be the only viable option.

### Implementation

I've "LLM'd" out some draft patches [3] for this. I'm not submitting
them because I still need to review and test them, and I don't want
(currently, pre those steps) low-quality slop to tarnish this proposal.
What this initial exploration did, however, confirm for me is that these
changes should be quite lightweight to implement. (Also, what I propose
is slightly different from my implementation draft in a few cases where
I think the design I proposed here is better than my draft
implementation.)

If the discussion here starts moving towards consensus, I'll clean up
and rework those patches along the lines of the consensus. Ideally I
would submit them one at a time, I figure, since the implementations for
different namespaces are necessarily changes to different subsystems.

Cheers!

John

[1]: https://lore.kernel.org/all/20260528095235.2491226-1-me@linux.beauty/

[2]: https://lore.kernel.org/all/455281ec-3ee1-4f27-989b-c239f0690d8b@app.fastmail.com/

[3]: https://github.com/Ericson2314/linux/commits/null-namespace

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [RFC] Null Namespaces
  2026-06-24 22:51 [RFC] Null Namespaces John Ericson
@ 2026-06-24 23:06 ` Andy Lutomirski
  2026-06-24 23:20   ` Andy Lutomirski
  2026-06-24 23:12 ` Al Viro
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 38+ messages in thread
From: Andy Lutomirski @ 2026-06-24 23:06 UTC (permalink / raw)
  To: John Ericson
  Cc: Li Chen, Cong Wang, Christian Brauner, linux-arch, linux-kernel,
	linux-fsdevel, linux-api, Arnd Bergmann, Andy Lutomirski,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	H. Peter Anvin, Jan Kara, Jonathan Corbet, Shuah Khan,
	Alexander Viro, Kees Cook, Sergei Zimmerman, Farid Zakaria

On Wed, Jun 24, 2026 at 3:52 PM John Ericson <mail@johnericson.me> wrote:
>
> Hello, I am hoping to discuss an idea I've had for a while, that I am
> calling "null namespaces" that has become more relevant with some recent
> other discussions. First I'll discuss null namespaces in general terms,
> and then I'll link those recent discussions and relate null namespaces
> to them.
>
> ### Null namespaces
>
> The essence of null namespaces is trying to give processes as little
> ambient authority as possible, so they are lighter weight and allowed to
> do even less than fully unshared processes today.
>
> Namespaces as they exist today are frequently described as an isolation
> mechanism, but I think this is the conflation of two different things.
> *Removing* a new process from its parent's namespaces unquestionably is
> increasing isolation --- no disagreement there. But putting the process
> in new namespaces is something else; I would call it supporting
> "delusions of grandeur" of that process. For example, namespaces allow a
> process to do mounts, have `CAP_SYS_ADMIN`, create network interfaces,
> look up other processes by PID, etc.
>
> Conceptually, to remove a process from one ambient authority scope (the
> very name "namespaces" indicates they are about ambient authority)
> should not require putting it in some ambient authority scope. Just
> because, for example, the process cannot see one mount tree, doesn't
> mean it needs to see another.

I think I like this, but some comments:

>
> Here's what I am thinking would happen concretely:
>
> First, the simpler cases:
>
> #### Null mount namespace
>
> - requires:
>
>   - null root file system: absolute paths don't work.
>
>   - null current working directory: relative paths with traditional,
>     non-`*at` system calls (and `*at` ones using `AT_FDCWD`) don't work.

It's perfectly valid to cd to a directory that does not belong to
one's namespace.  We have fchdir.  What's wrong with letting it
continue working?

Regardless of that, the current directory either needs to be a
directory or to be nothing at all, and if we support the latter, we
need to figure out what /proc will show.

> #### Null user namespace

A user namespace is kind of about how *non-current* uids and gids work
for the process and how it perceives its own uid and gid and not so
much about what uid and gid it has when accessing outside resources.
So...

>
> - Process has no user or group ids

What does that mean?  What does ps show?



Maybe the way to go is to implement the ones that have clearer
semantics and to defer the others.

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [RFC] Null Namespaces
  2026-06-24 22:51 [RFC] Null Namespaces John Ericson
  2026-06-24 23:06 ` Andy Lutomirski
@ 2026-06-24 23:12 ` Al Viro
  2026-06-25 21:00   ` H. Peter Anvin
  2026-06-29 11:45 ` Christian Brauner
  2026-07-04 13:20 ` Li Chen
  3 siblings, 1 reply; 38+ messages in thread
From: Al Viro @ 2026-06-24 23:12 UTC (permalink / raw)
  To: John Ericson
  Cc: Li Chen, Cong Wang, Christian Brauner, linux-arch, linux-kernel,
	linux-fsdevel, linux-api, Arnd Bergmann, Andy Lutomirski,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	H. Peter Anvin, Jan Kara, Jonathan Corbet, Shuah Khan, Kees Cook,
	Sergei Zimmerman, Farid Zakaria

On Wed, Jun 24, 2026 at 06:51:47PM -0400, John Ericson wrote:

> #### Null mount namespace
> 
> - requires:
> 
>   - null root file system: absolute paths don't work.
> 
>   - null current working directory: relative paths with traditional,
>     non-`*at` system calls (and `*at` ones using `AT_FDCWD`) don't work.
> 
> - All operations relating to the "ambient" mount tree don't work.
> 
> - `*at` operations with a file descriptor do work.

Huh?  The last bit looks contradicts the previous one - if you have
an opened directory in a mount from some namespace, those `*at` operations
with that descriptor *will* be seeing the mount tree of that namespace,
whatever the hell is "ambient" supposed to mean.  Either that, or you
will be exposing whatever's overmounted in that mount, which is a huge
can of worms.

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [RFC] Null Namespaces
  2026-06-24 23:06 ` Andy Lutomirski
@ 2026-06-24 23:20   ` Andy Lutomirski
  2026-06-24 23:53     ` John Ericson
  0 siblings, 1 reply; 38+ messages in thread
From: Andy Lutomirski @ 2026-06-24 23:20 UTC (permalink / raw)
  To: Andy Lutomirski
  Cc: John Ericson, Li Chen, Cong Wang, Christian Brauner, linux-arch,
	linux-kernel, linux-fsdevel, linux-api, Arnd Bergmann,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	H. Peter Anvin, Jan Kara, Jonathan Corbet, Shuah Khan,
	Alexander Viro, Kees Cook, Sergei Zimmerman, Farid Zakaria

On Wed, Jun 24, 2026 at 4:06 PM Andy Lutomirski <luto@kernel.org> wrote:
>
> On Wed, Jun 24, 2026 at 3:52 PM John Ericson <mail@johnericson.me> wrote:

> >   - null current working directory: relative paths with traditional,
> >     non-`*at` system calls (and `*at` ones using `AT_FDCWD`) don't work.
>
> It's perfectly valid to cd to a directory that does not belong to
> one's namespace.  We have fchdir.  What's wrong with letting it
> continue working?
>
> Regardless of that, the current directory either needs to be a
> directory or to be nothing at all, and if we support the latter, we
> need to figure out what /proc will show.

Thinking about this more: I think that handling CWD might actually be
a prerequisite for the series and has little to do with namespaces.
Maybe try adding, as a standalone feature, the ability to have a null
CWD.  Define semantics and see what the implementation looks like.

Then, if you add null namespaces, you could optionally make
transitioning to a null namespace set a null CWD.  Or those features
could be orthogonal.

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [RFC] Null Namespaces
  2026-06-24 23:20   ` Andy Lutomirski
@ 2026-06-24 23:53     ` John Ericson
  2026-06-25  1:10       ` Al Viro
  0 siblings, 1 reply; 38+ messages in thread
From: John Ericson @ 2026-06-24 23:53 UTC (permalink / raw)
  To: Andy Lutomirski
  Cc: Li Chen, Cong Wang, Christian Brauner, linux-arch, LKML,
	linux-fsdevel, linux-api, Arnd Bergmann, Thomas Gleixner,
	Ingo Molnar, Borislav Petkov, Dave Hansen, H. Peter Anvin,
	Jan Kara, Jonathan Corbet, Shuah Khan, Al Viro, Kees Cook,
	Sergei Zimmerman, Farid Zakaria

On Wed, Jun 24, 2026, at 7:20 PM, Andy Lutomirski wrote:
> I think I like this, but some comments:

Thanks, that's really nice to hear!

While arguably this is just the culmination of a direction Linux has
been going in for a while, it could also be seen as a very "out there"
idea. That at least one person likes the rough sound of things makes me
feel a lot better!

> On Wed, Jun 24, 2026 at 4:06 PM Andy Lutomirski <luto@kernel.org> wrote:
> >
> > On Wed, Jun 24, 2026 at 3:52 PM John Ericson <mail@johnericson.me> wrote:
>
> > >   - null current working directory: relative paths with traditional,
> > >     non-`*at` system calls (and `*at` ones using `AT_FDCWD`) don't work.
> >
> > It's perfectly valid to cd to a directory that does not belong to
> > one's namespace.  We have fchdir.  What's wrong with letting it
> > continue working?
> >
> > Regardless of that, the current directory either needs to be a
> > directory or to be nothing at all, and if we support the latter, we
> > need to figure out what /proc will show.
>
> Thinking about this more: I think that handling CWD might actually be
> a prerequisite for the series and has little to do with namespaces.
> Maybe try adding, as a standalone feature, the ability to have a null
> CWD.  Define semantics and see what the implementation looks like.
>
> Then, if you add null namespaces, you could optionally make
> transitioning to a null namespace set a null CWD.  Or those features
> could be orthogonal.

Hehe, I had the same thought after working on the filesystem patches,
along with the analogous thought for the root filesystem. It had been so
long since I had done a `chroot` without also doing a mount namespace
`unshare` --- despite the former being much older --- that I had
forgotten this separation of concerns.

My apologies for forgetting to include this insight in the original
email.

> Maybe the way to go is to implement the ones that have clearer
> semantics and to defer the others.

I would much prefer this, actually.

I wanted to discuss a bit about each type of namespace to indicate that
this is a concept I think works across the board --- it wouldn't be such
a good solution for the process spawning API if it was only applicable
to some but not all namespace types. But the truth is that I have
thought about the FS cases the most, as I think you have picked up on.

If there is interest in landing

  1. null CWD
  2. null root fs
  3. null mount namespace

in isolation, and then returning to the other namespaces to iron out
their details, that would be fantastic. It would be much nicer for me to
get some momentum that way, without having to design everything all at
once first before getting to implement anything.

John

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [RFC] Null Namespaces
  2026-06-24 23:53     ` John Ericson
@ 2026-06-25  1:10       ` Al Viro
  2026-06-25  3:41         ` John Ericson
  0 siblings, 1 reply; 38+ messages in thread
From: Al Viro @ 2026-06-25  1:10 UTC (permalink / raw)
  To: John Ericson
  Cc: Andy Lutomirski, Li Chen, Cong Wang, Christian Brauner,
	linux-arch, LKML, linux-fsdevel, linux-api, Arnd Bergmann,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	H. Peter Anvin, Jan Kara, Jonathan Corbet, Shuah Khan, Kees Cook,
	Sergei Zimmerman, Farid Zakaria

On Wed, Jun 24, 2026 at 07:53:53PM -0400, John Ericson wrote:
> I wanted to discuss a bit about each type of namespace to indicate that
> this is a concept I think works across the board --- it wouldn't be such
> a good solution for the process spawning API if it was only applicable
> to some but not all namespace types. But the truth is that I have
> thought about the FS cases the most, as I think you have picked up on.
> 
> If there is interest in landing
> 
>   1. null CWD
>   2. null root fs
>   3. null mount namespace
> 
> in isolation, and then returning to the other namespaces to iron out
> their details, that would be fantastic. It would be much nicer for me to
> get some momentum that way, without having to design everything all at
> once first before getting to implement anything.

Please, start with explaining what, in your opinion, a mount namespace _is_,
and where does "mount X is attached at path P relative to mount Y" belong.

What's the fundamental difference between CWD and any open descriptor for
a directory?  Why does it make sense to ban the former, but allow the
equivalents done via the latter?

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [RFC] Null Namespaces
  2026-06-25  1:10       ` Al Viro
@ 2026-06-25  3:41         ` John Ericson
  2026-06-25 15:51           ` Andy Lutomirski
  2026-06-26  0:15           ` Al Viro
  0 siblings, 2 replies; 38+ messages in thread
From: John Ericson @ 2026-06-25  3:41 UTC (permalink / raw)
  To: Al Viro
  Cc: Andy Lutomirski, Li Chen, Cong Wang, Christian Brauner,
	linux-arch, LKML, linux-fsdevel, linux-api, Arnd Bergmann,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	H. Peter Anvin, Jan Kara, Jonathan Corbet, Shuah Khan, Kees Cook,
	Sergei Zimmerman, Farid Zakaria

Ah, I started replying to your first email, but this is better, this
gets to the heart of the matter. Please don't mind me responding to your
two questions in reverse.

On Wed, Jun 24, 2026, at 9:10 PM, Al Viro wrote:
> What's the fundamental difference between CWD and any open descriptor
> for a directory?  Why does it make sense to ban the former, but allow
> the equivalents done via the latter?

Yes! These two notions are very close --- but that's the *problem*, not
a reason to not care about the existence of the CWD and root FS. I want
to get rid of CWD in my processes not because it is fundamentally
different (it isn't), but because it is superfluous.

If one is capability-minded like me, it's a bad mistake that we ever had
this "working directory" notion to begin with, and yet another example
of the folks at Bell Labs sticking something in the kernel that was
really only needed by the shell, and that could have just been done in
userland.

The current working directory, roughly, is *just* some global state
holding a directory file descriptor. But I don't want that global state.
If I am writing my userland program (that is not a shell), I would not
create the global variable. I do not appreciate the fact that the kernel
foists that state upon me whether I like it or not.

Now obviously we cannot have a giant breaking change removing the notion
of a current working directory altogether. But we can allow individual
processes which don't want it to opt out, and that is what nulling out
these fields (and updating the path resolution code to cope with that)
allows.

There is no loss of expressive power doing this, because one can (and
should!) just use the `*at` and file descriptors. But there is, however,
the imposition of discipline. The programmer (or coding agent) is
encouraged to do everything with file descriptors rather than path
concatenations etc., because they need to use `*at` anyways, and then
voilà, without browbeating anyone in security seminars or code review, a
bunch of TOCTOU issues disappear simply because doing the right thing is
now the path of least resistance.

> Please, start with explaining what, in your opinion, a mount namespace
> _is_, and where does "mount X is attached at path P relative to mount
> Y" belong.

Let's take a pathological example:

- Process A has `/foo` bind-mounted at `/bar/foo`

- Process B has `/bar` without that bind mount, and `/foo` mounted at
  `/baz/foo`, as is possible because it is in a different mount
  namespace.

If A opens `/bar/foo`, and sends it over (via socket) to B, and then B
does `openat(recv_fd, "..")`, B will get `/bar`, not `/baz`. This is
because `..` is resolved according to the mount referenced in the open
file. (This is, by the way, very good! Directory file descriptors would
be perilous to use if this were not the case!)

The moral of the story is that "mount X is attached at path P relative
to mount Y" is information accessed in the mounts themselves (maybe via
their containing mount namespace, per the `mnt_ns` field, or maybe not,
I am not sure, but it is immaterial). In contrast, the mount namespace
of the *opening* task (`current->nsproxy->mnt_ns`, and current is B)
doesn't matter at all for this purpose.

I am not on a crusade against `struct mnt_namespace` in general; I am
just trying to null out `(struct nsproxy)::mnt_ns` in particular. (This
is just as I am not on a crusade against `struct path`, just `root` and
`pwd` of `struct fs_struct`.)

These days, `current->nsproxy->mnt_ns` is, to me, first and foremost,
there for the legacy mount API. Again, just like our CWD example above,
this is mostly just global state.

The new mount API drastically [^1] reduces the need for it, since it
allows referring to mounts explicitly via file descriptors. That's OK!
The argument is the same as the above --- I am *not* trying to limit
what can be done if one has all the right files open with the right
perms. I am just trying to limit what works out of the box --- to reduce
the default set of privileges, *especially* where the resources involved
are implicit and/or stateful.

[^1]: It doesn't *quite* eliminate the need for `nsproxy->mnt_ns`
    entirely, since (as I understand it, from reading the `move_mount`
    man page) it is still used for some authorization checks, since
    `O_PATH` file descriptors do not grant privileges other than mere
    discoverability. But that's a problem that could be solved later
    with an `O_MOUNT` option analogous to `O_RDONLY` or `O_WRONLY`. In
    the meantime, I am perfectly happy if my processes with null mount
    namespaces get `move_mount` permission errors.

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [RFC] Null Namespaces
  2026-06-25  3:41         ` John Ericson
@ 2026-06-25 15:51           ` Andy Lutomirski
  2026-06-25 18:21             ` John Ericson
  2026-06-26  0:15           ` Al Viro
  1 sibling, 1 reply; 38+ messages in thread
From: Andy Lutomirski @ 2026-06-25 15:51 UTC (permalink / raw)
  To: John Ericson
  Cc: Al Viro, Andy Lutomirski, Li Chen, Cong Wang, Christian Brauner,
	linux-arch, LKML, linux-fsdevel, linux-api, Arnd Bergmann,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	H. Peter Anvin, Jan Kara, Jonathan Corbet, Shuah Khan, Kees Cook,
	Sergei Zimmerman, Farid Zakaria

On Wed, Jun 24, 2026 at 8:41 PM John Ericson <mail@johnericson.me> wrote:
>
> Ah, I started replying to your first email, but this is better, this
> gets to the heart of the matter. Please don't mind me responding to your
> two questions in reverse.
>
> On Wed, Jun 24, 2026, at 9:10 PM, Al Viro wrote:
> > What's the fundamental difference between CWD and any open descriptor
> > for a directory?  Why does it make sense to ban the former, but allow
> > the equivalents done via the latter?
>
> Yes! These two notions are very close --- but that's the *problem*, not
> a reason to not care about the existence of the CWD and root FS. I want
> to get rid of CWD in my processes not because it is fundamentally
> different (it isn't), but because it is superfluous.
>
> If one is capability-minded like me, it's a bad mistake that we ever had
> this "working directory" notion to begin with, and yet another example
> of the folks at Bell Labs sticking something in the kernel that was
> really only needed by the shell, and that could have just been done in
> userland.
>
> The current working directory, roughly, is *just* some global state
> holding a directory file descriptor. But I don't want that global state.
> If I am writing my userland program (that is not a shell), I would not
> create the global variable. I do not appreciate the fact that the kernel
> foists that state upon me whether I like it or not.
>
> Now obviously we cannot have a giant breaking change removing the notion
> of a current working directory altogether. But we can allow individual
> processes which don't want it to opt out, and that is what nulling out
> these fields (and updating the path resolution code to cope with that)
> allows.
>
> There is no loss of expressive power doing this, because one can (and
> should!) just use the `*at` and file descriptors. But there is, however,
> the imposition of discipline. The programmer (or coding agent) is
> encouraged to do everything with file descriptors rather than path
> concatenations etc., because they need to use `*at` anyways, and then
> voilà, without browbeating anyone in security seminars or code review, a
> bunch of TOCTOU issues disappear simply because doing the right thing is
> now the path of least resistance.
>
> > Please, start with explaining what, in your opinion, a mount namespace
> > _is_, and where does "mount X is attached at path P relative to mount
> > Y" belong.
>
> Let's take a pathological example:
>
> - Process A has `/foo` bind-mounted at `/bar/foo`
>
> - Process B has `/bar` without that bind mount, and `/foo` mounted at
>   `/baz/foo`, as is possible because it is in a different mount
>   namespace.
>
> If A opens `/bar/foo`, and sends it over (via socket) to B, and then B
> does `openat(recv_fd, "..")`, B will get `/bar`, not `/baz`. This is
> because `..` is resolved according to the mount referenced in the open
> file. (This is, by the way, very good! Directory file descriptors would
> be perilous to use if this were not the case!)
>
> The moral of the story is that "mount X is attached at path P relative
> to mount Y" is information accessed in the mounts themselves (maybe via
> their containing mount namespace, per the `mnt_ns` field, or maybe not,
> I am not sure, but it is immaterial). In contrast, the mount namespace
> of the *opening* task (`current->nsproxy->mnt_ns`, and current is B)
> doesn't matter at all for this purpose.

It's sort of a combination -- read the data structures :)  Other than
the propagation part, they're really not that bad.

In any event, I think this discussion is sort of immaterial to the
proposed API change.  No one is about to remove the concept of a mount
namespace.  But maybe it makes sense to have a way to have a task that
doesn't actually belong to a mount namespace.  A mount namespace is
certainly going to exist.

There will definitely be subtleties.  For example, what happens if a
task with "no mount namespace" tries to do OPEN_TREE_CLONE?  In some
logical sense it ought to work but it ought to be impossible to
actually mount the resulting tree anywhere, but this risks running
afoul of all kinds of checks.  Maybe you get a whole new mount
namespace (that does not become your current mnt_ns) if you
OPEN_TREE_CLONE?

This stuff is complex and it probably makes more sense to keep changes simple.

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [RFC] Null Namespaces
  2026-06-25 15:51           ` Andy Lutomirski
@ 2026-06-25 18:21             ` John Ericson
  0 siblings, 0 replies; 38+ messages in thread
From: John Ericson @ 2026-06-25 18:21 UTC (permalink / raw)
  To: Andy Lutomirski
  Cc: Al Viro, Li Chen, Cong Wang, Christian Brauner, linux-arch, LKML,
	linux-fsdevel, linux-api, Arnd Bergmann, Thomas Gleixner,
	Ingo Molnar, Borislav Petkov, Dave Hansen, H. Peter Anvin,
	Jan Kara, Jonathan Corbet, Shuah Khan, Kees Cook,
	Sergei Zimmerman, Farid Zakaria

On Thu, Jun 25, 2026, at 11:51 AM, Andy Lutomirski wrote:
> On Wed, Jun 24, 2026 at 8:41 PM John Ericson <mail@johnericson.me> wrote:
>
> It's sort of a combination -- read the data structures :)  Other than
> the propagation part, they're really not that bad.

Are you saying path resolution *does* depend on the mount namespace that
the task belongs to? I certainly hope not! I did look over the data
structures along with my patches and I didn't see an example of this ---
just path resolution depending on the CWD and root directories (as one
would expect it to).

> In any event, I think this discussion is sort of immaterial to the
> proposed API change.  No one is about to remove the concept of a mount
> namespace.  But maybe it makes sense to have a way to have a task that
> doesn't actually belong to a mount namespace.  A mount namespace is
> certainly going to exist.

I am not sure if that is addressed more to Al or me? I certainly do
agree with all that, in any case. Mount namespaces are absolutely here
to stay, and I'm just trying to make a process that does not belong to
one; that's exactly correct. Sorry if my motivation by way of historical
analysis veered off topic.

> There will definitely be subtleties.  For example, what happens if a
> task with "no mount namespace" tries to do OPEN_TREE_CLONE?  In some
> logical sense it ought to work but it ought to be impossible to
> actually mount the resulting tree anywhere, but this risks running
> afoul of all kinds of checks.  Maybe you get a whole new mount
> namespace (that does not become your current mnt_ns) if you
> OPEN_TREE_CLONE?
>
> This stuff is complex and it probably makes more sense to keep changes simple.

Yes it is subtle; I definitely don't claim to fully understand the
permission model with mount namespace modifications yet, for one. Should
we switch gears to just discussing the null CWD and root directories,
then, and return to mount namespaces later?

I have started to rework my patch series accordingly, so I have a new
draft first patch for just that, before changing anything else. I could
(after some testing) submit that next; it's pretty small.

John

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [RFC] Null Namespaces
  2026-06-24 23:12 ` Al Viro
@ 2026-06-25 21:00   ` H. Peter Anvin
  2026-06-25 21:50     ` John Ericson
  0 siblings, 1 reply; 38+ messages in thread
From: H. Peter Anvin @ 2026-06-25 21:00 UTC (permalink / raw)
  To: Al Viro, John Ericson
  Cc: Li Chen, Cong Wang, Christian Brauner, linux-arch, linux-kernel,
	linux-fsdevel, linux-api, Arnd Bergmann, Andy Lutomirski,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	Jan Kara, Jonathan Corbet, Shuah Khan, Kees Cook,
	Sergei Zimmerman, Farid Zakaria

On 2026-06-24 16:12, Al Viro wrote:
> On Wed, Jun 24, 2026 at 06:51:47PM -0400, John Ericson wrote:
> 
>> #### Null mount namespace
>>
>> - requires:
>>
>>   - null root file system: absolute paths don't work.
>>
>>   - null current working directory: relative paths with traditional,
>>     non-`*at` system calls (and `*at` ones using `AT_FDCWD`) don't work.
>>
>> - All operations relating to the "ambient" mount tree don't work.
>>
>> - `*at` operations with a file descriptor do work.
> 
> Huh?  The last bit looks contradicts the previous one - if you have
> an opened directory in a mount from some namespace, those `*at` operations
> with that descriptor *will* be seeing the mount tree of that namespace,
> whatever the hell is "ambient" supposed to mean.  Either that, or you
> will be exposing whatever's overmounted in that mount, which is a huge
> can of worms.

It seems to me that this is really no different *in practice* to having an
empty mount namespace, no? You might still be able to stat("/") and get a
d--------- result, but how does that actually affect anything?

The big thing with a lot of this is that introducing a null case can really
complicate things all over the place, and since this is very likely to be only
a niche use case, it kind of screams to me like it has the potential to become
an attack surface like any other rarely used code in the kernel...

	-hpa


^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [RFC] Null Namespaces
  2026-06-25 21:00   ` H. Peter Anvin
@ 2026-06-25 21:50     ` John Ericson
  2026-06-25 23:09       ` Andy Lutomirski
  2026-06-29 10:39       ` Christian Brauner
  0 siblings, 2 replies; 38+ messages in thread
From: John Ericson @ 2026-06-25 21:50 UTC (permalink / raw)
  To: H. Peter Anvin, Al Viro
  Cc: Li Chen, Cong Wang, Christian Brauner, linux-arch, LKML,
	linux-fsdevel, linux-api, Arnd Bergmann, Andy Lutomirski,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	Jan Kara, Jonathan Corbet, Shuah Khan, Kees Cook,
	Sergei Zimmerman, Farid Zakaria

On Thu, Jun 25, 2026, at 5:00 PM, H. Peter Anvin wrote:
> On 2026-06-24 16:12, Al Viro wrote:
> > On Wed, Jun 24, 2026 at 06:51:47PM -0400, John Ericson wrote:
> >
> >> #### Null mount namespace
> >>
> >> - requires:
> >>
> >>   - null root file system: absolute paths don't work.
> >>
> >>   - null current working directory: relative paths with traditional,
> >>     non-`*at` system calls (and `*at` ones using `AT_FDCWD`) don't work.
> >>
> >> - All operations relating to the "ambient" mount tree don't work.
> >>
> >> - `*at` operations with a file descriptor do work.
> >
> > Huh?  The last bit looks contradicts the previous one - if you have
> > an opened directory in a mount from some namespace, those `*at` operations
> > with that descriptor *will* be seeing the mount tree of that namespace,
> > whatever the hell is "ambient" supposed to mean.  Either that, or you
> > will be exposing whatever's overmounted in that mount, which is a huge
> > can of worms.
>
> It seems to me that this is really no different *in practice* to having an
> empty mount namespace, no? You might still be able to stat("/") and get a
> d--------- result, but how does that actually affect anything?

The argument against just having an empty, immutable root directory and
calling it a day is the tie-in with a new process-spawning API discussed
near the bottom of my original email. I want to have nice secure
defaults, rather than forcing the programmer to remember to unshare, but
I also don't want to degrade performance by speculatively creating new
empty mount namespaces that might just be thrown away. Null fields alone
get us both --- security and good performance.

> The big thing with a lot of this is that introducing a null case can really
> complicate things all over the place, and since this is very likely to be only
> a niche use case, it kind of screams to me like it has the potential to become
> an attack surface like any other rarely used code in the kernel...

I understand and am sympathetic to this line of reasoning, but I think
it is important to look at the patch in question (which I suppose I
should soon submit) to weigh the competing concerns.

The kernel rightfully has consolidated path resolution in a few key
places as much as possible -- the internal `struct path` does not suffer
from these issues. I barely modify those places to support null root and
CWD, and because of that consolidation, we shouldn't expect new places
to crop up in the future. (Duplicative path resolution logic is a bad
idea whether or not we have a nascent, little-used NULL-cwd/root code
path.) Therefore, I think existing code review, even among people
totally ignorant of this feature, will protect us --- the vast majority
of code will just be working with `struct path`, and be totally
unaffected by this change.

Moreover, every new feature starts rarely used. This is to me a
judicious anti-feature (removing state, making more things fail) that
should be quite intuitive to those developing for Linux, given the
prominence of things like WASI, and I will do what I can in the Nix
ecosystem to try to get it widely used in short order. Just guessing
from its design, this ought to be something other ecosystems, like
Android, are also interested in.

John

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [RFC] Null Namespaces
  2026-06-25 21:50     ` John Ericson
@ 2026-06-25 23:09       ` Andy Lutomirski
  2026-06-26  8:27         ` David Laight
  2026-06-29 10:39       ` Christian Brauner
  1 sibling, 1 reply; 38+ messages in thread
From: Andy Lutomirski @ 2026-06-25 23:09 UTC (permalink / raw)
  To: John Ericson
  Cc: H. Peter Anvin, Al Viro, Li Chen, Cong Wang, Christian Brauner,
	linux-arch, LKML, linux-fsdevel, linux-api, Arnd Bergmann,
	Andy Lutomirski, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
	Dave Hansen, Jan Kara, Jonathan Corbet, Shuah Khan, Kees Cook,
	Sergei Zimmerman, Farid Zakaria

On Thu, Jun 25, 2026 at 2:53 PM John Ericson <mail@johnericson.me> wrote:
>
> On Thu, Jun 25, 2026, at 5:00 PM, H. Peter Anvin wrote:
> > On 2026-06-24 16:12, Al Viro wrote:
> > > On Wed, Jun 24, 2026 at 06:51:47PM -0400, John Ericson wrote:
> > >
> > >> #### Null mount namespace
> > >>
> > >> - requires:
> > >>
> > >>   - null root file system: absolute paths don't work.
> > >>
> > >>   - null current working directory: relative paths with traditional,
> > >>     non-`*at` system calls (and `*at` ones using `AT_FDCWD`) don't work.
> > >>
> > >> - All operations relating to the "ambient" mount tree don't work.
> > >>
> > >> - `*at` operations with a file descriptor do work.
> > >
> > > Huh?  The last bit looks contradicts the previous one - if you have
> > > an opened directory in a mount from some namespace, those `*at` operations
> > > with that descriptor *will* be seeing the mount tree of that namespace,
> > > whatever the hell is "ambient" supposed to mean.  Either that, or you
> > > will be exposing whatever's overmounted in that mount, which is a huge
> > > can of worms.
> >
> > It seems to me that this is really no different *in practice* to having an
> > empty mount namespace, no? You might still be able to stat("/") and get a
> > d--------- result, but how does that actually affect anything?
>
> The argument against just having an empty, immutable root directory and
> calling it a day is the tie-in with a new process-spawning API discussed
> near the bottom of my original email. I want to have nice secure
> defaults, rather than forcing the programmer to remember to unshare, but
> I also don't want to degrade performance by speculatively creating new
> empty mount namespaces that might just be thrown away. Null fields alone
> get us both --- security and good performance.

This seems like a false dichotomy.  There's such thing as a singleton.

In fact, we have this spiffy nullfs_fs_get_tree.  It seems relatively
straightforward to have an API to get an fd to the singleton nullfs,
and the default for a newly spawned process could even be to have cwd
pointing at nullfs.

root is still harder, because of the shadowing issue.  I think I
proposed, ages ago, relaxing the chroot rules so that, at least under
certain circumstances (e.g. the task is not already chrooted) an
unprivileged task could chroot.  chrooting to nullfs seems like a
somewhat useful operation.

I can imagine more complex schemes to allow even a chrooted process to
safely start acting as though their root is nullfs, but that would be
potentially fairly nasty.  *Maybe* everything would work if there was
a root-for-dotdot and a separate root-for-absolute-paths, and
nameidata->root could point to the former, but I'm certainly not
willing to say that I think this would work with any confidence at
all.

--Andy

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [RFC] Null Namespaces
  2026-06-25  3:41         ` John Ericson
  2026-06-25 15:51           ` Andy Lutomirski
@ 2026-06-26  0:15           ` Al Viro
  2026-06-26 16:26             ` John Ericson
  2026-06-29 10:31             ` Christian Brauner
  1 sibling, 2 replies; 38+ messages in thread
From: Al Viro @ 2026-06-26  0:15 UTC (permalink / raw)
  To: John Ericson
  Cc: Andy Lutomirski, Li Chen, Cong Wang, Christian Brauner,
	linux-arch, LKML, linux-fsdevel, linux-api, Arnd Bergmann,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	H. Peter Anvin, Jan Kara, Jonathan Corbet, Shuah Khan, Kees Cook,
	Sergei Zimmerman, Farid Zakaria

On Wed, Jun 24, 2026 at 11:41:07PM -0400, John Ericson wrote:

> The current working directory, roughly, is *just* some global state
> holding a directory file descriptor.

So's the descriptor table; what's the difference?

> But I don't want that global state.

Don't use it, then... out of curiosity, does that extend to stdout et.al.?

> If I am writing my userland program (that is not a shell), I would not
> create the global variable. I do not appreciate the fact that the kernel
> foists that state upon me whether I like it or not.

<wry> Kernel will have to live without your appreciation, I suppose. </wry>

> Now obviously we cannot have a giant breaking change removing the notion
> of a current working directory altogether. But we can allow individual
> processes which don't want it to opt out, and that is what nulling out
> these fields (and updating the path resolution code to cope with that)
> allows.
> 
> There is no loss of expressive power doing this, because one can (and
> should!) just use the `*at` and file descriptors. But there is, however,
> the imposition of discipline.

So supply a library of your own and try to convince people to use it
instead of libc.  You'll have to anyway, seeing that a large and
hard-to-predict part of libc will be non-functional.  Which syscalls
are used by your library is entirely up to you.

Would that kind of thing added kernel-side assist the development of such
library?  Maybe, but I wouldn't bet too much on that - if you start from
scratch, you can trivially verify that you don't even attempt given
set of syscalls and if you use libc as a starting point, you get to
debug all the failure exits you've added...

> The programmer (or coding agent) is
> encouraged to do everything with file descriptors rather than path
> concatenations etc., because they need to use `*at` anyways, and then
> voilà, without browbeating anyone in security seminars or code review, a
> bunch of TOCTOU issues disappear simply because doing the right thing is
> now the path of least resistance.

I'm sorry, but the path of least resistance is picking a snippet from google
that will implement open(), etc., on top of your setup and using it.
_Especially_ if coding agents are going to be involved, precisely because
they'll do a convincing simulation of human duhveloper's behaviour, i.e.
"cut'n'paste it from the net".

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [RFC] Null Namespaces
  2026-06-25 23:09       ` Andy Lutomirski
@ 2026-06-26  8:27         ` David Laight
  2026-06-26 17:23           ` John Ericson
  0 siblings, 1 reply; 38+ messages in thread
From: David Laight @ 2026-06-26  8:27 UTC (permalink / raw)
  To: Andy Lutomirski
  Cc: John Ericson, H. Peter Anvin, Al Viro, Li Chen, Cong Wang,
	Christian Brauner, linux-arch, LKML, linux-fsdevel, linux-api,
	Arnd Bergmann, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
	Dave Hansen, Jan Kara, Jonathan Corbet, Shuah Khan, Kees Cook,
	Sergei Zimmerman, Farid Zakaria

On Thu, 25 Jun 2026 16:09:58 -0700
Andy Lutomirski <luto@kernel.org> wrote:

> On Thu, Jun 25, 2026 at 2:53 PM John Ericson <mail@johnericson.me> wrote:
> >
> > On Thu, Jun 25, 2026, at 5:00 PM, H. Peter Anvin wrote:  
> > > On 2026-06-24 16:12, Al Viro wrote:  
> > > > On Wed, Jun 24, 2026 at 06:51:47PM -0400, John Ericson wrote:
> > > >  
> > > >> #### Null mount namespace
> > > >>
> > > >> - requires:
> > > >>
> > > >>   - null root file system: absolute paths don't work.
> > > >>
> > > >>   - null current working directory: relative paths with traditional,
> > > >>     non-`*at` system calls (and `*at` ones using `AT_FDCWD`) don't work.
> > > >>
> > > >> - All operations relating to the "ambient" mount tree don't work.
> > > >>
> > > >> - `*at` operations with a file descriptor do work.  
> > > >
> > > > Huh?  The last bit looks contradicts the previous one - if you have
> > > > an opened directory in a mount from some namespace, those `*at` operations
> > > > with that descriptor *will* be seeing the mount tree of that namespace,
> > > > whatever the hell is "ambient" supposed to mean.  Either that, or you
> > > > will be exposing whatever's overmounted in that mount, which is a huge
> > > > can of worms.  
> > >
> > > It seems to me that this is really no different *in practice* to having an
> > > empty mount namespace, no? You might still be able to stat("/") and get a
> > > d--------- result, but how does that actually affect anything?  
> >
> > The argument against just having an empty, immutable root directory and
> > calling it a day is the tie-in with a new process-spawning API discussed
> > near the bottom of my original email. I want to have nice secure
> > defaults, rather than forcing the programmer to remember to unshare, but
> > I also don't want to degrade performance by speculatively creating new
> > empty mount namespaces that might just be thrown away. Null fields alone
> > get us both --- security and good performance.  
> 
> This seems like a false dichotomy.  There's such thing as a singleton.
> 
> In fact, we have this spiffy nullfs_fs_get_tree.  It seems relatively
> straightforward to have an API to get an fd to the singleton nullfs,
> and the default for a newly spawned process could even be to have cwd
> pointing at nullfs.
> 
> root is still harder, because of the shadowing issue.  I think I
> proposed, ages ago, relaxing the chroot rules so that, at least under
> certain circumstances (e.g. the task is not already chrooted) an
> unprivileged task could chroot.  chrooting to nullfs seems like a
> somewhat useful operation.
> 
> I can imagine more complex schemes to allow even a chrooted process to
> safely start acting as though their root is nullfs, but that would be
> potentially fairly nasty.  *Maybe* everything would work if there was
> a root-for-dotdot and a separate root-for-absolute-paths, and
> nameidata->root could point to the former, but I'm certainly not
> willing to say that I think this would work with any confidence at
> all.

You'd also need to sort out the 'pwd' mess.
The kernel inode always has its real parent, inside a chroot the scan stops
when the inode is the same as that of the base of the chroot.
But faf about with namespaces (IIRC I was doing an unshare to get out of
a network namespace) and that comparison can fail (if the chroot base isn't
a mount point) - so "../.." can go all the way back to the real root rather
than stopping at the base of the chroot (as you would expect).

	David


> 
> --Andy
> 


^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [RFC] Null Namespaces
  2026-06-26  0:15           ` Al Viro
@ 2026-06-26 16:26             ` John Ericson
  2026-06-29 10:31             ` Christian Brauner
  1 sibling, 0 replies; 38+ messages in thread
From: John Ericson @ 2026-06-26 16:26 UTC (permalink / raw)
  To: Al Viro
  Cc: Andy Lutomirski, Li Chen, Cong Wang, Christian Brauner,
	linux-arch, LKML, linux-fsdevel, linux-api, Arnd Bergmann,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	H. Peter Anvin, Jan Kara, Jonathan Corbet, Shuah Khan, Kees Cook,
	Sergei Zimmerman, Farid Zakaria

On Thu, Jun 25, 2026, at 8:15 PM, Al Viro wrote:
> On Wed, Jun 24, 2026 at 11:41:07PM -0400, John Ericson wrote:
>
> > But I don't want that global state.
>
> Don't use it, then... out of curiosity, does that extend to stdout et.al.?

Good question; it turns out I like the standard streams much better!

First of all, the standard streams are just an idiom --- there is
nothing actually special about file descriptors 0, 1, and 2. That's a
clean design --- the kernel doesn't need to know about userspace idioms.

Second of all, if you don't want any of those, you can just close 'em!
You can't do that with the cwd, however. It's stuck open.

Ideally `*at` would have been with us from the beginning, and, say, file
descriptor 3 would have been the "current working directory" merely by
convention.

> Would that kind of thing added kernel-side assist the development of such
> library?  Maybe, but I wouldn't bet too much on that - if you start from
> scratch, you can trivially verify that you don't even attempt given
> set of syscalls and if you use libc as a starting point, you get to
> debug all the failure exits you've added...

First of all, I am trying to change what processes are allowed to do,
and this includes programs I did not write. A libc-based solution is the
program cooperating with its own sandboxing; this is not a solution for
running arbitrary programs which may not be trusted in a restricted
manner.

Second of all, this would be very laborious in practice, because we're
talking not about what syscalls the program uses, but about what data is
passed in those syscalls. Any program that consumes arbitrary user input
(like shell utilities) might receive an absolute or relative path, and
so it would have to manually check for that, lest the user input "trick"
the program into using the root dir and cwd it is trying to ignore.

Making a tiny few edits in the kernel path resolution logic to allow for
these null fields is much more practical than defending a much broader
perimeter in userspace.

> > The programmer (or coding agent) is
> > encouraged to do everything with file descriptors rather than path
> > concatenations etc., because they need to use `*at` anyways, and then
> > voilà, without browbeating anyone in security seminars or code review, a
> > bunch of TOCTOU issues disappear simply because doing the right thing is
> > now the path of least resistance.
>
> I'm sorry, but the path of least resistance is picking a snippet from google
> that will implement open(), etc., on top of your setup and using it.
> _Especially_ if coding agents are going to be involved, precisely because
> they'll do a convincing simulation of human duhveloper's behaviour, i.e.
> "cut'n'paste it from the net".

We agree! But this is precisely why it is important to make these things
fail. Mindless Stack Overflow cut'n'pasters (human or agent) still run
their program to make sure it works. Making the thing you don't want
them to do *actually fail* creates sufficiently strong and incremental
feedback that they will end up doing the right thing.

> > The current working directory, roughly, is *just* some global state
> > holding a directory file descriptor.
>
> So's the descriptor table; what's the difference?

Now that I've responded to everything else, I can answer this in
summary:

- File descriptors can be closed; cwd and root cannot be.

- File descriptors need to be explicitly used in syscalls. The cwd and
  root are implicitly used (in too many different syscalls to make
  syscall-level auditing practical) based on the sort of path string
  argument to the syscall, without the program's explicit consent.

John

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [RFC] Null Namespaces
  2026-06-26  8:27         ` David Laight
@ 2026-06-26 17:23           ` John Ericson
  0 siblings, 0 replies; 38+ messages in thread
From: John Ericson @ 2026-06-26 17:23 UTC (permalink / raw)
  To: David Laight, Andy Lutomirski
  Cc: H. Peter Anvin, Al Viro, Li Chen, Cong Wang, Christian Brauner,
	linux-arch, LKML, linux-fsdevel, linux-api, Arnd Bergmann,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	Jan Kara, Jonathan Corbet, Shuah Khan, Kees Cook,
	Sergei Zimmerman, Farid Zakaria

I am replying to both Andy and David in a single email --- hope that is
not confusing.

On Thu, Jun 25, 2026, at 7:09 PM, Andy Lutomirski wrote:
> On Thu, Jun 25, 2026 at 2:53 PM John Ericson <mail@johnericson.me> wrote:
> >
> > The argument against just having an empty, immutable root directory and
> > calling it a day is the tie-in with a new process-spawning API discussed
> > near the bottom of my original email. I want to have nice secure
> > defaults, rather than forcing the programmer to remember to unshare, but
> > I also don't want to degrade performance by speculatively creating new
> > empty mount namespaces that might just be thrown away. Null fields alone
> > get us both --- security and good performance.
>
> This seems like a false dichotomy.  There's such thing as a singleton.
>
> In fact, we have this spiffy nullfs_fs_get_tree.  It seems relatively
> straightforward to have an API to get an fd to the singleton nullfs,
> and the default for a newly spawned process could even be to have cwd
> pointing at nullfs.

Ah! This is the first I am learning about the new nullfs. OK yes I agree
this gives us both properties, since it is truly immutably empty.

I still have a slight preference for something that also makes
statting/opening/etc. of `/` itself fail, but this is otherwise good ---
there's no denying it.

> root is still harder, because of the shadowing issue.  I think I
> proposed, ages ago, relaxing the chroot rules so that, at least under
> certain circumstances (e.g. the task is not already chrooted) an
> unprivileged task could chroot.  chrooting to nullfs seems like a
> somewhat useful operation.
>
> I can imagine more complex schemes to allow even a chrooted process to
> safely start acting as though their root is nullfs, but that would be
> potentially fairly nasty.  *Maybe* everything would work if there was
> a root-for-dotdot and a separate root-for-absolute-paths, and
> nameidata->root could point to the former, but I'm certainly not
> willing to say that I think this would work with any confidence at
> all.

I really like these ideas!

- Splitting the two uses of root sounds great. Even more generally (at
  least as a thought experiment, I don't like the O(n) performance), one
  can imagine a set of paths one must not `cd ..` past. Conceptually, I
  feel optimistic that inserting another boundary path into the set on
  every `chroot` makes it safe.

- In the original "real root", the "root for .." field could be null,
  since no `..` check is actually needed. Then, if we only want to have
  a single "root for .." (to avoid the O(n)), only the initial
  assignment of it from null to non-null would be unprivileged --- this
  would implement your "task is not already chrooted" idea. Subsequent
  assignment would still be privileged since we are replacing, not
  extending our "set". (The nullable single path means we have 0 or 1
  paths in our set.)

----

On Fri, Jun 26, 2026, at 4:27 AM, David Laight wrote:
>
> You'd also need to sort out the 'pwd' mess.
> The kernel inode always has its real parent, inside a chroot the scan stops
> when the inode is the same as that of the base of the chroot.
> But faf about with namespaces (IIRC I was doing an unshare to get out of
> a network namespace) and that comparison can fail (if the chroot base isn't
> a mount point) - so "../.." can go all the way back to the real root rather
> than stopping at the base of the chroot (as you would expect).
>
> David

I did get the impression that the `..` check is...rather fragile. I am
also thinking that a global setting like `openat2`'s `RESOLVE_BENEATH`
to make `..` never work would be useful; then all manner of chrooting is
trivially safe, because you cannot go up regardless!

----

Given the state of the discussion, I'll go submit my null cwd and root
patch momentarily. The nullfs alternative is quite compelling; to the
extent that I do prefer making the root operations fail as I said above,
I think my best shot is demonstrating that this patch is so small and
lightweight that this slight benefit is paid for by the simplicity of
the implementation.

John

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [RFC] Null Namespaces
  2026-06-26  0:15           ` Al Viro
  2026-06-26 16:26             ` John Ericson
@ 2026-06-29 10:31             ` Christian Brauner
  1 sibling, 0 replies; 38+ messages in thread
From: Christian Brauner @ 2026-06-29 10:31 UTC (permalink / raw)
  To: Al Viro
  Cc: John Ericson, Andy Lutomirski, Li Chen, Cong Wang, linux-arch,
	LKML, linux-fsdevel, linux-api, Arnd Bergmann, Thomas Gleixner,
	Ingo Molnar, Borislav Petkov, Dave Hansen, H. Peter Anvin,
	Jan Kara, Jonathan Corbet, Shuah Khan, Kees Cook,
	Sergei Zimmerman, Farid Zakaria

> So supply a library of your own and try to convince people to use it
> instead of libc.  You'll have to anyway, seeing that a large and

I agree. And in fact that is what we've been doing:

https://github.com/cyphar/libpathrs

I also plan on splitting the chase*() machinery in systemd out as
a separate C library as well:

https://github.com/systemd/systemd/blob/104750fd60da4c563650785e272a7ce0a6694d01/src/basic/chase.c#L238

> hard-to-predict part of libc will be non-functional.  Which syscalls
> are used by your library is entirely up to you.
> 
> Would that kind of thing added kernel-side assist the development of such
> library?  Maybe, but I wouldn't bet too much on that - if you start from

It wouldn't really and we haven't needed it for that.

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [RFC] Null Namespaces
  2026-06-25 21:50     ` John Ericson
  2026-06-25 23:09       ` Andy Lutomirski
@ 2026-06-29 10:39       ` Christian Brauner
  2026-07-01  9:49         ` Jori Koolstra
  1 sibling, 1 reply; 38+ messages in thread
From: Christian Brauner @ 2026-06-29 10:39 UTC (permalink / raw)
  To: John Ericson
  Cc: H. Peter Anvin, Al Viro, Li Chen, Cong Wang, linux-arch, LKML,
	linux-fsdevel, linux-api, Arnd Bergmann, Andy Lutomirski,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	Jan Kara, Jonathan Corbet, Shuah Khan, Kees Cook,
	Sergei Zimmerman, Farid Zakaria

> The kernel rightfully has consolidated path resolution in a few key
> places as much as possible -- the internal `struct path` does not suffer
> from these issues. I barely modify those places to support null root and
> CWD, and because of that consolidation, we shouldn't expect new places
> to crop up in the future. (Duplicative path resolution logic is a bad
> idea whether or not we have a nascent, little-used NULL-cwd/root code
> path.) Therefore, I think existing code review, even among people
> totally ignorant of this feature, will protect us --- the vast majority
> of code will just be working with `struct path`, and be totally
> unaffected by this change.

I actually did laugh out loud reading this. I'm sorry, I can't really
take this argument seriously. May I introduce you to drivers/ for a
start and the history of path lookup exploits of the last - say 10
years.

You have to excuse me but it's a mixture of amusement and slight anger.
Amusement because this is really naive and thus also a bit endearing.
Anger because it single-handedly dismisses how big of an attack surface
and problem space path lookup is. The equivalent of every math
professor's "trivial. excercise left to the reader".

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [RFC] Null Namespaces
  2026-06-24 22:51 [RFC] Null Namespaces John Ericson
  2026-06-24 23:06 ` Andy Lutomirski
  2026-06-24 23:12 ` Al Viro
@ 2026-06-29 11:45 ` Christian Brauner
  2026-06-29 21:06   ` Andy Lutomirski
  2026-06-30  2:50   ` John Ericson
  2026-07-04 13:20 ` Li Chen
  3 siblings, 2 replies; 38+ messages in thread
From: Christian Brauner @ 2026-06-29 11:45 UTC (permalink / raw)
  To: John Ericson
  Cc: Li Chen, Cong Wang, linux-arch, linux-kernel, linux-fsdevel,
	linux-api, Arnd Bergmann, Andy Lutomirski, Thomas Gleixner,
	Ingo Molnar, Borislav Petkov, Dave Hansen, H. Peter Anvin,
	Jan Kara, Jonathan Corbet, Shuah Khan, Alexander Viro, Kees Cook,
	Sergei Zimmerman, Farid Zakaria

On Wed, Jun 24, 2026 at 06:51:47PM -0400, John Ericson wrote:
> Hello, I am hoping to discuss an idea I've had for a while, that I am
> calling "null namespaces" that has become more relevant with some recent
> other discussions. First I'll discuss null namespaces in general terms,
> and then I'll link those recent discussions and relate null namespaces
> to them.
> 
> ### Null namespaces
> 
> The essence of null namespaces is trying to give processes as little
> ambient authority as possible, so they are lighter weight and allowed to
> do even less than fully unshared processes today.
> 
> Namespaces as they exist today are frequently described as an isolation
> mechanism, but I think this is the conflation of two different things.
> *Removing* a new process from its parent's namespaces unquestionably is
> increasing isolation --- no disagreement there. But putting the process
> in new namespaces is something else; I would call it supporting
> "delusions of grandeur" of that process. For example, namespaces allow a
> process to do mounts, have `CAP_SYS_ADMIN`, create network interfaces,
> look up other processes by PID, etc.
> 
> Conceptually, to remove a process from one ambient authority scope (the
> very name "namespaces" indicates they are about ambient authority)
> should not require putting it in some ambient authority scope. Just
> because, for example, the process cannot see one mount tree, doesn't
> mean it needs to see another.
> 
> Here's what I am thinking would happen concretely:
> 
> First, the simpler cases:
> 
> #### Null mount namespace
> 
> - requires:
> 
>   - null root file system: absolute paths don't work.
> 
>   - null current working directory: relative paths with traditional,
>     non-`*at` system calls (and `*at` ones using `AT_FDCWD`) don't work.
> 
> - All operations relating to the "ambient" mount tree don't work.
> 
> - `*at` operations with a file descriptor do work.
> 
> - The new fd-based mount APIs with detached mounts do work, modulo
>   the calling process having enough permissions (as usual).

Nothing here requires you to NULL anything and I oppose this on code
sanity reasons alone. We shoud absolutely not start to stash any NULL
pointers in core kernel objects such as struct path that are used
everywhere.

So I've added nullfs a few releases back. It's currently not mountable
from userspace but I've already mentioned in the commit message that
this is going to change. But I also added:

unshare(UNSHARE_EMPTY_MNTNS)
clone3(CLONE_EMPTY_MNTNS)

In both cases the process is placed into a completely empty mount
namespace with nullfs as it's root and cwd. If you're in a new mount
namespace with CAP_SYS_ADMIN thrown away it means you're going to be in
nullfs forever.

It's possible we can come up with:

unshare(UNSHARE_FS_EMPTY)
clone3(CLONE_FS_EMPTY)

which just moves the task into an isolated nullfs instance (it would
need some thinking about interactions with chroot()).

But I guess the even simpler model would be to copy what I've been doing
for pidfs:

+static struct path nullfs_root_path = {};
+
+void nullfs_get_root(struct path *path)
+{
+       *path = nullfs_root_path;
+       path_get(path);
+}
+
 static void __init init_mount_tree(void)
 {
        struct vfsmount *mnt, *nullfs_mnt;
@@ -6209,6 +6217,8 @@ static void __init init_mount_tree(void)
        /* Mount mutable rootfs on top of nullfs. */
        root.mnt                = nullfs_mnt;
        root.dentry             = nullfs_mnt->mnt_root;
+       nullfs_root_path.mnt    = nullfs_mnt;
+       pidfs_root_path.dentry  = nullfs_mnt->mnt_root;

        LOCK_MOUNT_EXACT(mp, &root);
        if (unlikely(IS_ERR(mp.parent)))
diff --git a/include/uapi/linux/fcntl.h b/include/uapi/linux/fcntl.h
index aadfbf6e0cb3..f55c87c70b78 100644
--- a/include/uapi/linux/fcntl.h
+++ b/include/uapi/linux/fcntl.h
@@ -124,6 +124,7 @@ struct delegation {

 #define FD_PIDFS_ROOT                  -10002 /* Root of the pidfs filesystem */
 #define FD_NSFS_ROOT                   -10003 /* Root of the nsfs filesystem */
+#define FD_NULLFS_ROOT                 -10004 /* Root of the nullfs filesystem */
 #define FD_INVALID                     -10009 /* Invalid file descriptor: -10000 - EBADF = -10009 */

 /* Generic flags for the *at(2) family of syscalls. */

we then add fchroot() (overdue anyway) and then teach both fchdir() and
fchroot() to honor FD_NULLFS_ROOT. Then a process may shed its fs state
and move itself into nullfs. Restrict *chdir() and *chroot() for said
process via seccomp and it's locked in forever as well.

^ permalink raw reply related	[flat|nested] 38+ messages in thread

* Re: [RFC] Null Namespaces
  2026-06-29 11:45 ` Christian Brauner
@ 2026-06-29 21:06   ` Andy Lutomirski
  2026-06-30  4:25     ` John Ericson
  2026-07-02  9:34     ` Christian Brauner
  2026-06-30  2:50   ` John Ericson
  1 sibling, 2 replies; 38+ messages in thread
From: Andy Lutomirski @ 2026-06-29 21:06 UTC (permalink / raw)
  To: Christian Brauner
  Cc: John Ericson, Li Chen, Cong Wang, linux-arch, linux-kernel,
	linux-fsdevel, linux-api, Arnd Bergmann, Andy Lutomirski,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	H. Peter Anvin, Jan Kara, Jonathan Corbet, Shuah Khan,
	Alexander Viro, Kees Cook, Sergei Zimmerman, Farid Zakaria

On Mon, Jun 29, 2026 at 4:45 AM Christian Brauner <brauner@kernel.org> wrote:
>

> But I guess the even simpler model would be to copy what I've been doing
> for pidfs:
>
> +static struct path nullfs_root_path = {};
> +
> +void nullfs_get_root(struct path *path)
> +{
> +       *path = nullfs_root_path;
> +       path_get(path);
> +}
> +
>  static void __init init_mount_tree(void)
>  {
>         struct vfsmount *mnt, *nullfs_mnt;
> @@ -6209,6 +6217,8 @@ static void __init init_mount_tree(void)
>         /* Mount mutable rootfs on top of nullfs. */
>         root.mnt                = nullfs_mnt;
>         root.dentry             = nullfs_mnt->mnt_root;
> +       nullfs_root_path.mnt    = nullfs_mnt;
> +       pidfs_root_path.dentry  = nullfs_mnt->mnt_root;
>
>         LOCK_MOUNT_EXACT(mp, &root);
>         if (unlikely(IS_ERR(mp.parent)))
> diff --git a/include/uapi/linux/fcntl.h b/include/uapi/linux/fcntl.h
> index aadfbf6e0cb3..f55c87c70b78 100644
> --- a/include/uapi/linux/fcntl.h
> +++ b/include/uapi/linux/fcntl.h
> @@ -124,6 +124,7 @@ struct delegation {
>
>  #define FD_PIDFS_ROOT                  -10002 /* Root of the pidfs filesystem */
>  #define FD_NSFS_ROOT                   -10003 /* Root of the nsfs filesystem */
> +#define FD_NULLFS_ROOT                 -10004 /* Root of the nullfs filesystem */
>  #define FD_INVALID                     -10009 /* Invalid file descriptor: -10000 - EBADF = -10009 */
>
>  /* Generic flags for the *at(2) family of syscalls. */
>
> we then add fchroot() (overdue anyway) and then teach both fchdir() and
> fchroot() to honor FD_NULLFS_ROOT. Then a process may shed its fs state
> and move itself into nullfs. Restrict *chdir() and *chroot() for said
> process via seccomp and it's locked in forever as well.
>

One thing comes to mind that might need a bit of care: this would give
an API for any task to get an fd to a directory that lives in the init
mount namespace.  It's not at all obvious to me that this is dangerous
or even observable (you're not about to find a setuid program in
nullfs), but I think it's at least worth a tiny bit of consideration.

But if this happens, maybe we could finally land one of the patches to
enable unprivileged chroot?  It's been tried a few times.

https://lore.kernel.org/lkml/0e2f0f54e19bff53a3739ecfddb4ffa9a6dbde4d.1327858005.git.luto@amacapital.net/

https://lore.kernel.org/all/20210316203633.424794-2-mic@digikod.net/

I think the need for it has reduced a tiny bit with user namespaces,
as you can sort of emulate it by unsharing your user namespace and
thus getting enough privilege, but this is rather heavyweight and
limiting.


If all of the above landed, then the old chroot /var/empty kludge that
security-minded programs have done for decades could finally be
modernized and not require any privilege :)

Hmm, thinking aloud: every now and then someone brings up the idea of
having an fd (really an OFD) that points to a file or a directory but
carries less in the way of permissions/capabilities than the usual
OFDs.  If we had a way to make an OFD to a directory that forced
RESOLVE_BENEATH (or RESOLVE_IN_ROOT) and that propagated that
restriction to anything you open using it, and if an unprivileged
process could chroot itself to nullfs, then we would be getting quite
close to what Capsicum can do.

--Andy

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [RFC] Null Namespaces
  2026-06-29 11:45 ` Christian Brauner
  2026-06-29 21:06   ` Andy Lutomirski
@ 2026-06-30  2:50   ` John Ericson
  2026-06-30  7:14     ` Christian Brauner
  1 sibling, 1 reply; 38+ messages in thread
From: John Ericson @ 2026-06-30  2:50 UTC (permalink / raw)
  To: Christian Brauner
  Cc: Li Chen, Cong Wang, linux-arch, LKML, linux-fsdevel, linux-api,
	Arnd Bergmann, Andy Lutomirski, Thomas Gleixner, Ingo Molnar,
	Borislav Petkov, Dave Hansen, H. Peter Anvin, Jan Kara,
	Jonathan Corbet, Shuah Khan, Al Viro, Kees Cook, Sergei Zimmerman,
	Farid Zakaria

On Mon, Jun 29, 2026, at 7:45 AM, Christian Brauner wrote:
> But I guess the even simpler model would be to copy what I've been doing
> for pidfs:
>
> [...]
>
> we then add fchroot() (overdue anyway) and then teach both fchdir() and
> fchroot() to honor FD_NULLFS_ROOT. Then a process may shed its fs state
> and move itself into nullfs. Restrict *chdir() and *chroot() for said
> process via seccomp and it's locked in forever as well.

This looks good! It delivers most of what I want, and I do want to be
very clear that while I am responding to your comments on my patch
below, I would still be very pleased if we just did this, much more than
I am pleased with the status quo.

(And also, yes, good to create the long-overdue fchroot regardless of
what we do here.)

> Nothing here requires you to NULL anything and I oppose this on code
> sanity reasons alone. We shoud absolutely not start to stash any NULL
> pointers in core kernel objects such as struct path that are used
> everywhere.

Before we do the "pidfd style" nullfs route, I want to make one thing
clear about my patch: I was *not* trying to relax the invariant across
the board that (live) `struct path` should only contain non-null
pointers. Rather, I just want `struct fs_struct` to contain ("morally")
`Option<struct path>`. My use of the null pointer was merely me doing
the sort of ragged union packing that, for example, Rust does. I think
as a matter of A_B_I (emphasis on "binary"), this is fine, and not
going to cause Armageddon --- `struct path` is widely used, but `struct
fs_struct` is (as far as I can tell) not.

All that said, as a matter of A_P_I (emphasis on "program"), I do see
your point that it's too easy for someone to not read my comment, and
then `struct path` with null pointers starts leaking all over the place,
making a big mess. I think a simple enough fix is to just use another C
encoding, such as a union, for `Option<struct path>`.

For example:

    union optional_path {
        struct {
            void *p0, *p1; /* must be null */
        } __randomize_layout null_path;
        struct path path; /* both fields must be non-null */
    };

To continue saving space, or --- if relying on the overlap of
`null_path` and `path.mnt` is too sketchy --- making a bona fide tagged
union:

    struct optional_path {
        enum {
            OPTIONAL_PATH_ABSENT,
            OPTIONAL_PATH_PRESENT,
        } tag;
        union {
            struct {} null_path;
            struct path path;
        };
    };

And either way, there can be an inline function:

    const struct path * /* nullable */
    get_optional_path(const struct optional_path * /* non-nullable */);

taking a non-null pointer and returning a nullable pointer to help
consumers of `struct fs_struct` not screw up accessing `root` and `pwd`.

A third option is simply copying the definition of `struct path`, doing:

    /* Just like `struct path`, but instead of both fields always being
     * non-null, both fields can also both be null to indicate an absent
     * path. One field null, the other field non-null is still not
     * permitted, however.
     */
    struct optional_path {
            struct vfsmount *optional_mnt;
            struct dentry *optional_dentry;
    } __randomize_layout;

in which case `get_optional_path` works by value instead of by
reference, because in the `CONFIG_RANDSTRUCT`-case the field order may
not be the same.

Any of these 3 variations would make absolutely clear that the
invariants around `struct path` have not changed, and only `struct
fs_struct` is changed. Furthermore, the API breakage on `fs->pwd` and
`fs->root` will mechanically ensure that all consumers get caught and
fixed (with the fix being to use `get_optional_path` and check for the
null case).

I do like these versions better than my original, because I do agree
making a safer C API is worthwhile. And because of the API breakage
forcing a complete patch as discussed above, I think that if I make a v2
along these lines, the diff will either prove or refute my basic premise
that `pwd` and `root` in `struct fs_struct`, unlike `struct path`, are
not widely used, and so changing their definitions like this (from
`struct path` to `... optional_path`) is lightweight.

Thanks,

John

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [RFC] Null Namespaces
  2026-06-29 21:06   ` Andy Lutomirski
@ 2026-06-30  4:25     ` John Ericson
  2026-07-02  9:34     ` Christian Brauner
  1 sibling, 0 replies; 38+ messages in thread
From: John Ericson @ 2026-06-30  4:25 UTC (permalink / raw)
  To: Andy Lutomirski, Christian Brauner
  Cc: Li Chen, Cong Wang, linux-arch, LKML, linux-fsdevel, linux-api,
	Arnd Bergmann, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
	Dave Hansen, H. Peter Anvin, Jan Kara, Jonathan Corbet,
	Shuah Khan, Al Viro, Kees Cook, Sergei Zimmerman, Farid Zakaria

On Mon, Jun 29, 2026, at 5:06 PM, Andy Lutomirski wrote:
> But if this happens, maybe we could finally land one of the patches to
> enable unprivileged chroot?  It's been tried a few times.

> If we had a way to make an OFD to a directory that forced
> RESOLVE_BENEATH (or RESOLVE_IN_ROOT) and that propagated that
> restriction to anything you open using it, and if an unprivileged
> process could chroot itself to nullfs, then we would be getting quite
> close to what Capsicum can do.

I just want to briefly say that I agree that these are both things worth
pursuing.

Once the root and working directories are sorted out (whether by nullfs
or by making those optional in `fs_struct`, see my other email), I am
fine putting my yet-unsubmitted patches for the null namespaces
themselves on hold and addressing these things instead. I can indeed see
it may be useful to wrap up such loose ends in VFS-land while we are
here, before switching gears to other namespaces and other subsystems.

John

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [RFC] Null Namespaces
  2026-06-30  2:50   ` John Ericson
@ 2026-06-30  7:14     ` Christian Brauner
  2026-06-30 17:20       ` John Ericson
  0 siblings, 1 reply; 38+ messages in thread
From: Christian Brauner @ 2026-06-30  7:14 UTC (permalink / raw)
  To: John Ericson
  Cc: Li Chen, Cong Wang, linux-arch, LKML, linux-fsdevel, linux-api,
	Arnd Bergmann, Andy Lutomirski, Thomas Gleixner, Ingo Molnar,
	Borislav Petkov, Dave Hansen, H. Peter Anvin, Jan Kara,
	Jonathan Corbet, Shuah Khan, Al Viro, Kees Cook, Sergei Zimmerman,
	Farid Zakaria

On Mon, Jun 29, 2026 at 10:50:38PM -0400, John Ericson wrote:
> On Mon, Jun 29, 2026, at 7:45 AM, Christian Brauner wrote:
> > But I guess the even simpler model would be to copy what I've been doing
> > for pidfs:
> >
> > [...]
> >
> > we then add fchroot() (overdue anyway) and then teach both fchdir() and
> > fchroot() to honor FD_NULLFS_ROOT. Then a process may shed its fs state
> > and move itself into nullfs. Restrict *chdir() and *chroot() for said
> > process via seccomp and it's locked in forever as well.
> 
> This looks good! It delivers most of what I want, and I do want to be
> very clear that while I am responding to your comments on my patch
> below, I would still be very pleased if we just did this, much more than
> I am pleased with the status quo.
> 
> (And also, yes, good to create the long-overdue fchroot regardless of
> what we do here.)
> 
> > Nothing here requires you to NULL anything and I oppose this on code
> > sanity reasons alone. We shoud absolutely not start to stash any NULL
> > pointers in core kernel objects such as struct path that are used
> > everywhere.
> 
> Before we do the "pidfd style" nullfs route, I want to make one thing
> clear about my patch: I was *not* trying to relax the invariant across
> the board that (live) `struct path` should only contain non-null
> pointers. Rather, I just want `struct fs_struct` to contain ("morally")
> `Option<struct path>`. My use of the null pointer was merely me doing
> the sort of ragged union packing that, for example, Rust does. I think
> as a matter of A_B_I (emphasis on "binary"), this is fine, and not
> going to cause Armageddon --- `struct path` is widely used, but `struct
> fs_struct` is (as far as I can tell) not.
> 
> All that said, as a matter of A_P_I (emphasis on "program"), I do see
> your point that it's too easy for someone to not read my comment, and
> then `struct path` with null pointers starts leaking all over the place,
> making a big mess. I think a simple enough fix is to just use another C
> encoding, such as a union, for `Option<struct path>`.
> 
> For example:
> 
>     union optional_path {
>         struct {
>             void *p0, *p1; /* must be null */
>         } __randomize_layout null_path;
>         struct path path; /* both fields must be non-null */
>     };
> 
> To continue saving space, or --- if relying on the overlap of
> `null_path` and `path.mnt` is too sketchy --- making a bona fide tagged
> union:
> 
>     struct optional_path {
>         enum {
>             OPTIONAL_PATH_ABSENT,
>             OPTIONAL_PATH_PRESENT,
>         } tag;
>         union {
>             struct {} null_path;
>             struct path path;
>         };
>     };

I think Al is about to have a stroke reading this... and I might too.
I agree with the sentiment I disagree with the details of this and
touching the whole kernel for this. You know what the easy solution is:
don't allow a struct path to be empty...

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [RFC] Null Namespaces
  2026-06-30  7:14     ` Christian Brauner
@ 2026-06-30 17:20       ` John Ericson
  2026-06-30 17:41         ` Andy Lutomirski
  2026-06-30 18:05         ` H. Peter Anvin
  0 siblings, 2 replies; 38+ messages in thread
From: John Ericson @ 2026-06-30 17:20 UTC (permalink / raw)
  To: Christian Brauner
  Cc: Li Chen, Cong Wang, linux-arch, LKML, linux-fsdevel, linux-api,
	Arnd Bergmann, Andy Lutomirski, Thomas Gleixner, Ingo Molnar,
	Borislav Petkov, Dave Hansen, H. Peter Anvin, Jan Kara,
	Jonathan Corbet, Shuah Khan, Al Viro, Kees Cook, Sergei Zimmerman,
	Farid Zakaria

I'll throw in the towel after this email, I promise :)

On Tue, Jun 30, 2026, at 3:14 AM, Christian Brauner wrote:
> I think Al is about to have a stroke reading this... and I might too.

Hahaha. Alas, C does have a longstanding beef with discriminated unions
--- I can't do anything about that! (Well, other than wait 15 years for
this stuff to eventually be rewritten in Rust, that is ;).)

> I agree with the sentiment

Thanks, I appreciate it :).

> You know what the easy solution is: don't allow a struct path to be
> empty...

Just so we're clear, my quibble here is purely behavioral: the nullfs
directory can be opened, right? And that open directory can also be
getdents64ed (yielding no entries, since it is empty), right? If I am
wrong about these things then sure, no objections from me --- let's ship
nullfs FDs right away!

My reasoning for being a bit of a weenie on that behavior is just that I
think "fail fast" is good. A lot of userspace programs crawl the file
space pretty willy-nilly (e.g. they are doing some caching thing, or
they are looking for something, etc.). I suspect the nullfs approach is
going to result in more "red herring" error messages and google searches
about "why can't I write to the working directory, not even as root"
etc. I just think "no directory" vs "immutable empty directory" sends a
clearer message to userspace, and will align mental models more rapidly.

Put another way, if there were no implementation downsides to either
approach, I assume everyone else would also slightly prefer "no
directory" over "immutable empty directory".

From that premise, I am further presuming that any non-empty `struct
path` to a directory that doesn't exist would be a real VFS crime, and
so making `optional_path` one way or another is the proper way to
implement this behavior.

If I am wrong about either step of my reasoning --- that nullfs like
every sort of FS ought to be openable/readdirable with sufficient perms
at the root, that a valid `struct path` to a "non-object" is bad design
--- do say so, and I'll drop the `optional_path` stuff completely.

> I disagree with the details of this

You mean the unergonomics of making a valid `optional_path`, right?

> and touching the whole kernel for this.

I want to make sure this is a difference in opinion and not a difference
in the view of the facts.

The linchpin of my prior email was that very little of the kernel cares
about these fields in `struct fs_struct`, or even cares about `struct
fs_struct` at all, so this is *not* a "whole kernel" change. Yes, thanks
to `current`, a bunch of code *could* look at this stuff if it wants to.
But it should *not* want to, regardless of what we do.

If there were a way to make parts of `struct task_struct` opaque
(without including another header) to enforce this design principle, I
would definitely go contribute that. (I remember the desire for
something like this coming up with the "fast headers" patches, but there
wasn't a good implementation strategy in C, alas.)

Likewise, for the tiny few usages outside of `fs/namei.c` that I found,
I would be happy to more creatively look at that code to see if I can
indeed avoid `struct fs_struct` altogether.

Cheers,

John

P.S. The "regardless of what we do" part was key to my earlier code
review argument that gave you "mixture of amusement and slight anger":
of course I don't expect other maintainers to keep abreast of subtle
null pointer invariants, but the simple rule that nothing but
`fs/namei.c` really ought to be consuming these `struct fs_struct`
fields is, I believe, all three of: good, already true, and intuitive.

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [RFC] Null Namespaces
  2026-06-30 17:20       ` John Ericson
@ 2026-06-30 17:41         ` Andy Lutomirski
  2026-07-02  9:29           ` Christian Brauner
  2026-06-30 18:05         ` H. Peter Anvin
  1 sibling, 1 reply; 38+ messages in thread
From: Andy Lutomirski @ 2026-06-30 17:41 UTC (permalink / raw)
  To: John Ericson
  Cc: Christian Brauner, Li Chen, Cong Wang, linux-arch, LKML,
	linux-fsdevel, linux-api, Arnd Bergmann, Andy Lutomirski,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	H. Peter Anvin, Jan Kara, Jonathan Corbet, Shuah Khan, Al Viro,
	Kees Cook, Sergei Zimmerman, Farid Zakaria

On Tue, Jun 30, 2026 at 10:20 AM John Ericson <mail@johnericson.me> wrote:
>
> I'll throw in the towel after this email, I promise :)
>
> On Tue, Jun 30, 2026, at 3:14 AM, Christian Brauner wrote:
> > I think Al is about to have a stroke reading this... and I might too.
>
> Hahaha. Alas, C does have a longstanding beef with discriminated unions
> --- I can't do anything about that! (Well, other than wait 15 years for
> this stuff to eventually be rewritten in Rust, that is ;).)
>
> > I agree with the sentiment
>
> Thanks, I appreciate it :).
>
> > You know what the easy solution is: don't allow a struct path to be
> > empty...
>
> Just so we're clear, my quibble here is purely behavioral: the nullfs
> directory can be opened, right? And that open directory can also be
> getdents64ed (yielding no entries, since it is empty), right? If I am
> wrong about these things then sure, no objections from me --- let's ship
> nullfs FDs right away!
>

Christian, how would you feel about a variant of nullfs that fails all
operations instead of acting as if it were empty?  (I'm far from
convinced that this would actually be better, but it at least seems
pretty straightforwardly possible.  And obviously the
nullfs-at-the-root-of-everything would not want this variant.)

> My reasoning for being a bit of a weenie on that behavior is just that I
> think "fail fast" is good. A lot of userspace programs crawl the file
> space pretty willy-nilly (e.g. they are doing some caching thing, or
> they are looking for something, etc.). I suspect the nullfs approach is
> going to result in more "red herring" error messages and google searches
> about "why can't I write to the working directory, not even as root"
> etc. I just think "no directory" vs "immutable empty directory" sends a
> clearer message to userspace, and will align mental models more rapidly.

This is the difference between a greenfield project and working with
existing designs.

If I were designing an OS and its entire API from scratch, then, sure,
the cwd and the root directory would just be well-known items in a
capability table, and they could be absent.

Linux's implementation and its API are not greenfield projects,
though, and it seems pretty silly to try to make this change.

--Andy

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [RFC] Null Namespaces
  2026-06-30 17:20       ` John Ericson
  2026-06-30 17:41         ` Andy Lutomirski
@ 2026-06-30 18:05         ` H. Peter Anvin
  1 sibling, 0 replies; 38+ messages in thread
From: H. Peter Anvin @ 2026-06-30 18:05 UTC (permalink / raw)
  To: John Ericson, Christian Brauner
  Cc: Li Chen, Cong Wang, linux-arch, LKML, linux-fsdevel, linux-api,
	Arnd Bergmann, Andy Lutomirski, Thomas Gleixner, Ingo Molnar,
	Borislav Petkov, Dave Hansen, Jan Kara, Jonathan Corbet,
	Shuah Khan, Al Viro, Kees Cook, Sergei Zimmerman, Farid Zakaria

On June 30, 2026 10:20:05 AM PDT, John Ericson <mail@johnericson.me> wrote:
>I'll throw in the towel after this email, I promise :)
>
>On Tue, Jun 30, 2026, at 3:14 AM, Christian Brauner wrote:
>> I think Al is about to have a stroke reading this... and I might too.
>
>Hahaha. Alas, C does have a longstanding beef with discriminated unions
>--- I can't do anything about that! (Well, other than wait 15 years for
>this stuff to eventually be rewritten in Rust, that is ;).)
>
>> I agree with the sentiment
>
>Thanks, I appreciate it :).
>
>> You know what the easy solution is: don't allow a struct path to be
>> empty...
>
>Just so we're clear, my quibble here is purely behavioral: the nullfs
>directory can be opened, right? And that open directory can also be
>getdents64ed (yielding no entries, since it is empty), right? If I am
>wrong about these things then sure, no objections from me --- let's ship
>nullfs FDs right away!
>
>My reasoning for being a bit of a weenie on that behavior is just that I
>think "fail fast" is good. A lot of userspace programs crawl the file
>space pretty willy-nilly (e.g. they are doing some caching thing, or
>they are looking for something, etc.). I suspect the nullfs approach is
>going to result in more "red herring" error messages and google searches
>about "why can't I write to the working directory, not even as root"
>etc. I just think "no directory" vs "immutable empty directory" sends a
>clearer message to userspace, and will align mental models more rapidly.
>
>Put another way, if there were no implementation downsides to either
>approach, I assume everyone else would also slightly prefer "no
>directory" over "immutable empty directory".
>
>From that premise, I am further presuming that any non-empty `struct
>path` to a directory that doesn't exist would be a real VFS crime, and
>so making `optional_path` one way or another is the proper way to
>implement this behavior.
>
>If I am wrong about either step of my reasoning --- that nullfs like
>every sort of FS ought to be openable/readdirable with sufficient perms
>at the root, that a valid `struct path` to a "non-object" is bad design
>--- do say so, and I'll drop the `optional_path` stuff completely.
>
>> I disagree with the details of this
>
>You mean the unergonomics of making a valid `optional_path`, right?
>
>> and touching the whole kernel for this.
>
>I want to make sure this is a difference in opinion and not a difference
>in the view of the facts.
>
>The linchpin of my prior email was that very little of the kernel cares
>about these fields in `struct fs_struct`, or even cares about `struct
>fs_struct` at all, so this is *not* a "whole kernel" change. Yes, thanks
>to `current`, a bunch of code *could* look at this stuff if it wants to.
>But it should *not* want to, regardless of what we do.
>
>If there were a way to make parts of `struct task_struct` opaque
>(without including another header) to enforce this design principle, I
>would definitely go contribute that. (I remember the desire for
>something like this coming up with the "fast headers" patches, but there
>wasn't a good implementation strategy in C, alas.)
>
>Likewise, for the tiny few usages outside of `fs/namei.c` that I found,
>I would be happy to more creatively look at that code to see if I can
>indeed avoid `struct fs_struct` altogether.
>
>Cheers,
>
>John
>
>P.S. The "regardless of what we do" part was key to my earlier code
>review argument that gave you "mixture of amusement and slight anger":
>of course I don't expect other maintainers to keep abreast of subtle
>null pointer invariants, but the simple rule that nothing but
>`fs/namei.c` really ought to be consuming these `struct fs_struct`
>fields is, I believe, all three of: good, already true, and intuitive.

15 years? 

Don't make me laugh...

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [RFC] Null Namespaces
  2026-06-29 10:39       ` Christian Brauner
@ 2026-07-01  9:49         ` Jori Koolstra
  2026-07-02 21:28           ` H. Peter Anvin
  0 siblings, 1 reply; 38+ messages in thread
From: Jori Koolstra @ 2026-07-01  9:49 UTC (permalink / raw)
  To: Christian Brauner
  Cc: John Ericson, H. Peter Anvin, Al Viro, Li Chen, Cong Wang,
	linux-arch, LKML, linux-fsdevel, linux-api, Arnd Bergmann,
	Andy Lutomirski, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
	Dave Hansen, Jan Kara, Jonathan Corbet, Shuah Khan, Kees Cook,
	Sergei Zimmerman, Farid Zakaria

On Mon, Jun 29, 2026 at 12:39:56PM +0200, Christian Brauner wrote:
> > The kernel rightfully has consolidated path resolution in a few key
> > places as much as possible -- the internal `struct path` does not suffer
> > from these issues. I barely modify those places to support null root and
> > CWD, and because of that consolidation, we shouldn't expect new places
> > to crop up in the future. (Duplicative path resolution logic is a bad
> > idea whether or not we have a nascent, little-used NULL-cwd/root code
> > path.) Therefore, I think existing code review, even among people
> > totally ignorant of this feature, will protect us --- the vast majority
> > of code will just be working with `struct path`, and be totally
> > unaffected by this change.
> 
> I actually did laugh out loud reading this. I'm sorry, I can't really
> take this argument seriously. May I introduce you to drivers/ for a
> start and the history of path lookup exploits of the last - say 10
> years.
> 
> You have to excuse me but it's a mixture of amusement and slight anger.
> Amusement because this is really naive and thus also a bit endearing.
> Anger because it single-handedly dismisses how big of an attack surface
> and problem space path lookup is. The equivalent of every math
> professor's "trivial. excercise left to the reader".

I could easily show you why path lookup is trivial, but I have no space
left in the margins of this email.

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [RFC] Null Namespaces
  2026-06-30 17:41         ` Andy Lutomirski
@ 2026-07-02  9:29           ` Christian Brauner
  0 siblings, 0 replies; 38+ messages in thread
From: Christian Brauner @ 2026-07-02  9:29 UTC (permalink / raw)
  To: Andy Lutomirski
  Cc: John Ericson, Li Chen, Cong Wang, linux-arch, LKML, linux-fsdevel,
	linux-api, Arnd Bergmann, Thomas Gleixner, Ingo Molnar,
	Borislav Petkov, Dave Hansen, H. Peter Anvin, Jan Kara,
	Jonathan Corbet, Shuah Khan, Al Viro, Kees Cook, Sergei Zimmerman,
	Farid Zakaria

> > > I agree with the sentiment
> >
> > Thanks, I appreciate it :).
> >
> > > You know what the easy solution is: don't allow a struct path to be
> > > empty...
> >
> > Just so we're clear, my quibble here is purely behavioral: the nullfs
> > directory can be opened, right? And that open directory can also be
> > getdents64ed (yielding no entries, since it is empty), right? If I am
> > wrong about these things then sure, no objections from me --- let's ship
> > nullfs FDs right away!
> >
> 
> Christian, how would you feel about a variant of nullfs that fails all
> operations instead of acting as if it were empty?  (I'm far from
> convinced that this would actually be better, but it at least seems
> pretty straightforwardly possible.  And obviously the
> nullfs-at-the-root-of-everything would not want this variant.)

I think it would have to be a separate fs type: failfs. The problem is -
if taken to its logical extreme - it would have to refuse statfs and
fstatfs as well which means you can't discover it. You'd still be able
to get your cwd and root of course via /proc/self/{cwd,root}. So for
that we could just add:

FD_FAILFS_ROOT

I don't have quarrels with that. I can add that and it seems genuinely
useful. I'd again keep it as a kernel-only thing (not mountable) for now
until we have an idea whether the semantics work out...

(Btw, I've done something vaguely similar for kthreads in work to be
merged coming cycle. Their root and pwd are in a _separate_ nullfs that
can't even be overmounted. Which means you can't do arbitrary lookup or
I/O in init's fs state from kthreads anymore without having to use an
explicit scoped_with_init_fs() override with init's fs state
temporarily. Which means that hopefully things like ksmbd will not
happen again...)

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [RFC] Null Namespaces
  2026-06-29 21:06   ` Andy Lutomirski
  2026-06-30  4:25     ` John Ericson
@ 2026-07-02  9:34     ` Christian Brauner
  2026-07-02 15:43       ` John Ericson
                         ` (2 more replies)
  1 sibling, 3 replies; 38+ messages in thread
From: Christian Brauner @ 2026-07-02  9:34 UTC (permalink / raw)
  To: Andy Lutomirski
  Cc: John Ericson, Li Chen, Cong Wang, linux-arch, linux-kernel,
	linux-fsdevel, linux-api, Arnd Bergmann, Thomas Gleixner,
	Ingo Molnar, Borislav Petkov, Dave Hansen, H. Peter Anvin,
	Jan Kara, Jonathan Corbet, Shuah Khan, Alexander Viro, Kees Cook,
	Sergei Zimmerman, Farid Zakaria

On Mon, Jun 29, 2026 at 02:06:55PM -0700, Andy Lutomirski wrote:
> On Mon, Jun 29, 2026 at 4:45 AM Christian Brauner <brauner@kernel.org> wrote:
> >
> 
> > But I guess the even simpler model would be to copy what I've been doing
> > for pidfs:
> >
> > +static struct path nullfs_root_path = {};
> > +
> > +void nullfs_get_root(struct path *path)
> > +{
> > +       *path = nullfs_root_path;
> > +       path_get(path);
> > +}
> > +
> >  static void __init init_mount_tree(void)
> >  {
> >         struct vfsmount *mnt, *nullfs_mnt;
> > @@ -6209,6 +6217,8 @@ static void __init init_mount_tree(void)
> >         /* Mount mutable rootfs on top of nullfs. */
> >         root.mnt                = nullfs_mnt;
> >         root.dentry             = nullfs_mnt->mnt_root;
> > +       nullfs_root_path.mnt    = nullfs_mnt;
> > +       pidfs_root_path.dentry  = nullfs_mnt->mnt_root;
> >
> >         LOCK_MOUNT_EXACT(mp, &root);
> >         if (unlikely(IS_ERR(mp.parent)))
> > diff --git a/include/uapi/linux/fcntl.h b/include/uapi/linux/fcntl.h
> > index aadfbf6e0cb3..f55c87c70b78 100644
> > --- a/include/uapi/linux/fcntl.h
> > +++ b/include/uapi/linux/fcntl.h
> > @@ -124,6 +124,7 @@ struct delegation {
> >
> >  #define FD_PIDFS_ROOT                  -10002 /* Root of the pidfs filesystem */
> >  #define FD_NSFS_ROOT                   -10003 /* Root of the nsfs filesystem */
> > +#define FD_NULLFS_ROOT                 -10004 /* Root of the nullfs filesystem */
> >  #define FD_INVALID                     -10009 /* Invalid file descriptor: -10000 - EBADF = -10009 */
> >
> >  /* Generic flags for the *at(2) family of syscalls. */
> >
> > we then add fchroot() (overdue anyway) and then teach both fchdir() and
> > fchroot() to honor FD_NULLFS_ROOT. Then a process may shed its fs state
> > and move itself into nullfs. Restrict *chdir() and *chroot() for said
> > process via seccomp and it's locked in forever as well.
> >
> 
> One thing comes to mind that might need a bit of care: this would give
> an API for any task to get an fd to a directory that lives in the init
> mount namespace.  It's not at all obvious to me that this is dangerous
> or even observable (you're not about to find a setuid program in
> nullfs), but I think it's at least worth a tiny bit of consideration.

Yes, I thought about this as well. But it doesn't have to be this way.
Every mount namespaces has nullfs as it's root ever since I introduced
it. Which means FD_NULLFS_ROOT can also just mean "nullfs within that
specific mount namespace". That's fine.

For my FD_FAILFS_ROOT proposal it would be enough if we make failfs
SB_KERNMOUNT which means it's logically distinct from every mount
namespace. I think that might be the right thing to do. I need to spend
one or more brain cycles on this though.

> 
> But if this happens, maybe we could finally land one of the patches to
> enable unprivileged chroot?  It's been tried a few times.
> 
> https://lore.kernel.org/lkml/0e2f0f54e19bff53a3739ecfddb4ffa9a6dbde4d.1327858005.git.luto@amacapital.net/
> 
> https://lore.kernel.org/all/20210316203633.424794-2-mic@digikod.net/
> 
> I think the need for it has reduced a tiny bit with user namespaces,
> as you can sort of emulate it by unsharing your user namespace and
> thus getting enough privilege, but this is rather heavyweight and
> limiting.

I think we could make that work with both FD_NULLFS_ROOT and
FD_FAILFS_ROOT...

> 
> 
> If all of the above landed, then the old chroot /var/empty kludge that
> security-minded programs have done for decades could finally be
> modernized and not require any privilege :)

I think I like it.

> Hmm, thinking aloud: every now and then someone brings up the idea of
> having an fd (really an OFD) that points to a file or a directory but
> carries less in the way of permissions/capabilities than the usual
> OFDs.  If we had a way to make an OFD to a directory that forced
> RESOLVE_BENEATH (or RESOLVE_IN_ROOT) and that propagated that
> restriction to anything you open using it, and if an unprivileged
> process could chroot itself to nullfs, then we would be getting quite
> close to what Capsicum can do.

Next steps. I hear you volunteering...

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [RFC] Null Namespaces
  2026-07-02  9:34     ` Christian Brauner
@ 2026-07-02 15:43       ` John Ericson
  2026-07-03  8:59         ` Christian Brauner
  2026-07-03 17:35       ` Directory capability brain dump (Re: [RFC] Null Namespaces) Andy Lutomirski
  2026-07-06 14:52       ` [RFC] Null Namespaces Christian Brauner
  2 siblings, 1 reply; 38+ messages in thread
From: John Ericson @ 2026-07-02 15:43 UTC (permalink / raw)
  To: Christian Brauner, Andy Lutomirski
  Cc: Li Chen, Cong Wang, linux-arch, LKML, linux-fsdevel, linux-api,
	Arnd Bergmann, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
	Dave Hansen, H. Peter Anvin, Jan Kara, Jonathan Corbet,
	Shuah Khan, Al Viro, Kees Cook, Sergei Zimmerman, Farid Zakaria

On Thu, Jul 2, 2026, at 4:34 AM, Christian Brauner wrote:
> I think we could make that work with both FD_NULLFS_ROOT and
> FD_FAILFS_ROOT...

Fantastic!!

What comes next? Do you want to submit the patches for this, Christian,
or do you want one of us to? (Just trying to be helpful.)

John

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [RFC] Null Namespaces
  2026-07-01  9:49         ` Jori Koolstra
@ 2026-07-02 21:28           ` H. Peter Anvin
  0 siblings, 0 replies; 38+ messages in thread
From: H. Peter Anvin @ 2026-07-02 21:28 UTC (permalink / raw)
  To: Jori Koolstra, Christian Brauner
  Cc: John Ericson, Al Viro, Li Chen, Cong Wang, linux-arch, LKML,
	linux-fsdevel, linux-api, Arnd Bergmann, Andy Lutomirski,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	Jan Kara, Jonathan Corbet, Shuah Khan, Kees Cook,
	Sergei Zimmerman, Farid Zakaria

On July 1, 2026 2:49:42 AM PDT, Jori Koolstra <jkoolstra@xs4all.nl> wrote:
>On Mon, Jun 29, 2026 at 12:39:56PM +0200, Christian Brauner wrote:
>> > The kernel rightfully has consolidated path resolution in a few key
>> > places as much as possible -- the internal `struct path` does not suffer
>> > from these issues. I barely modify those places to support null root and
>> > CWD, and because of that consolidation, we shouldn't expect new places
>> > to crop up in the future. (Duplicative path resolution logic is a bad
>> > idea whether or not we have a nascent, little-used NULL-cwd/root code
>> > path.) Therefore, I think existing code review, even among people
>> > totally ignorant of this feature, will protect us --- the vast majority
>> > of code will just be working with `struct path`, and be totally
>> > unaffected by this change.
>> 
>> I actually did laugh out loud reading this. I'm sorry, I can't really
>> take this argument seriously. May I introduce you to drivers/ for a
>> start and the history of path lookup exploits of the last - say 10
>> years.
>> 
>> You have to excuse me but it's a mixture of amusement and slight anger.
>> Amusement because this is really naive and thus also a bit endearing.
>> Anger because it single-handedly dismisses how big of an attack surface
>> and problem space path lookup is. The equivalent of every math
>> professor's "trivial. excercise left to the reader".
>
>I could easily show you why path lookup is trivial, but I have no space
>left in the margins of this email.

🤣🤣🤣

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [RFC] Null Namespaces
  2026-07-02 15:43       ` John Ericson
@ 2026-07-03  8:59         ` Christian Brauner
  0 siblings, 0 replies; 38+ messages in thread
From: Christian Brauner @ 2026-07-03  8:59 UTC (permalink / raw)
  To: John Ericson
  Cc: Andy Lutomirski, Li Chen, Cong Wang, linux-arch, LKML,
	linux-fsdevel, linux-api, Arnd Bergmann, Thomas Gleixner,
	Ingo Molnar, Borislav Petkov, Dave Hansen, H. Peter Anvin,
	Jan Kara, Jonathan Corbet, Shuah Khan, Al Viro, Kees Cook,
	Sergei Zimmerman, Farid Zakaria

On Thu, Jul 02, 2026 at 11:43:31AM -0400, John Ericson wrote:
> On Thu, Jul 2, 2026, at 4:34 AM, Christian Brauner wrote:
> > I think we could make that work with both FD_NULLFS_ROOT and
> > FD_FAILFS_ROOT...
> 
> Fantastic!!
> 
> What comes next? Do you want to submit the patches for this, Christian,
> or do you want one of us to? (Just trying to be helpful.)

I'll send a patch soon-ish.

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Directory capability brain dump (Re: [RFC] Null Namespaces)
  2026-07-02  9:34     ` Christian Brauner
  2026-07-02 15:43       ` John Ericson
@ 2026-07-03 17:35       ` Andy Lutomirski
  2026-07-06 14:52       ` [RFC] Null Namespaces Christian Brauner
  2 siblings, 0 replies; 38+ messages in thread
From: Andy Lutomirski @ 2026-07-03 17:35 UTC (permalink / raw)
  To: Christian Brauner
  Cc: Andy Lutomirski, John Ericson, Li Chen, Cong Wang, linux-arch,
	LKML, Linux FS Devel, Linux API, Arnd Bergmann, Thomas Gleixner,
	Ingo Molnar, Borislav Petkov, Dave Hansen, H. Peter Anvin,
	Jan Kara, Jonathan Corbet, Shuah Khan, Alexander Viro, Kees Cook,
	Sergei Zimmerman, Farid Zakaria

On Thu, Jul 2, 2026 at 3:02 AM Christian Brauner <brauner@kernel.org> wrote:
>
> On Mon, Jun 29, 2026 at 02:06:55PM -0700, Andy Lutomirski wrote:
> > On Mon, Jun 29, 2026 at 4:45 AM Christian Brauner <brauner@kernel.org> wrote:
> > >
> >
> > > But I guess the even simpler model would be to copy what I've been doing
> > > for pidfs:
> > >
> > > +static struct path nullfs_root_path = {};
> > > +
> > > +void nullfs_get_root(struct path *path)
> > > +{
> > > +       *path = nullfs_root_path;
> > > +       path_get(path);
> > > +}
> > > +
> > >  static void __init init_mount_tree(void)
> > >  {
> > >         struct vfsmount *mnt, *nullfs_mnt;
> > > @@ -6209,6 +6217,8 @@ static void __init init_mount_tree(void)
> > >         /* Mount mutable rootfs on top of nullfs. */
> > >         root.mnt                = nullfs_mnt;
> > >         root.dentry             = nullfs_mnt->mnt_root;
> > > +       nullfs_root_path.mnt    = nullfs_mnt;
> > > +       pidfs_root_path.dentry  = nullfs_mnt->mnt_root;
> > >
> > >         LOCK_MOUNT_EXACT(mp, &root);
> > >         if (unlikely(IS_ERR(mp.parent)))
> > > diff --git a/include/uapi/linux/fcntl.h b/include/uapi/linux/fcntl.h
> > > index aadfbf6e0cb3..f55c87c70b78 100644
> > > --- a/include/uapi/linux/fcntl.h
> > > +++ b/include/uapi/linux/fcntl.h
> > > @@ -124,6 +124,7 @@ struct delegation {
> > >
> > >  #define FD_PIDFS_ROOT                  -10002 /* Root of the pidfs filesystem */
> > >  #define FD_NSFS_ROOT                   -10003 /* Root of the nsfs filesystem */
> > > +#define FD_NULLFS_ROOT                 -10004 /* Root of the nullfs filesystem */
> > >  #define FD_INVALID                     -10009 /* Invalid file descriptor: -10000 - EBADF = -10009 */
> > >
> > >  /* Generic flags for the *at(2) family of syscalls. */
> > >
> > > we then add fchroot() (overdue anyway) and then teach both fchdir() and
> > > fchroot() to honor FD_NULLFS_ROOT. Then a process may shed its fs state
> > > and move itself into nullfs. Restrict *chdir() and *chroot() for said
> > > process via seccomp and it's locked in forever as well.
> > >
> >
> > One thing comes to mind that might need a bit of care: this would give
> > an API for any task to get an fd to a directory that lives in the init
> > mount namespace.  It's not at all obvious to me that this is dangerous
> > or even observable (you're not about to find a setuid program in
> > nullfs), but I think it's at least worth a tiny bit of consideration.
>
> Yes, I thought about this as well. But it doesn't have to be this way.
> Every mount namespaces has nullfs as it's root ever since I introduced
> it. Which means FD_NULLFS_ROOT can also just mean "nullfs within that
> specific mount namespace". That's fine.
>
> For my FD_FAILFS_ROOT proposal it would be enough if we make failfs
> SB_KERNMOUNT which means it's logically distinct from every mount
> namespace. I think that might be the right thing to do. I need to spend
> one or more brain cycles on this though.
>
> >
> > But if this happens, maybe we could finally land one of the patches to
> > enable unprivileged chroot?  It's been tried a few times.
> >
> > https://lore.kernel.org/lkml/0e2f0f54e19bff53a3739ecfddb4ffa9a6dbde4d.1327858005.git.luto@amacapital.net/
> >
> > https://lore.kernel.org/all/20210316203633.424794-2-mic@digikod.net/
> >
> > I think the need for it has reduced a tiny bit with user namespaces,
> > as you can sort of emulate it by unsharing your user namespace and
> > thus getting enough privilege, but this is rather heavyweight and
> > limiting.
>
> I think we could make that work with both FD_NULLFS_ROOT and
> FD_FAILFS_ROOT...
>
> >
> >
> > If all of the above landed, then the old chroot /var/empty kludge that
> > security-minded programs have done for decades could finally be
> > modernized and not require any privilege :)
>
> I think I like it.
>
> > Hmm, thinking aloud: every now and then someone brings up the idea of
> > having an fd (really an OFD) that points to a file or a directory but
> > carries less in the way of permissions/capabilities than the usual
> > OFDs.  If we had a way to make an OFD to a directory that forced
> > RESOLVE_BENEATH (or RESOLVE_IN_ROOT) and that propagated that
> > restriction to anything you open using it, and if an unprivileged
> > process could chroot itself to nullfs, then we would be getting quite
> > close to what Capsicum can do.
>
> Next steps. I hear you volunteering...
>

Haha.  I volunteer to give somewhat of a brain dump, which is very
incomplete.  Doing a decent job of this would be maybe 90% figuring
out nasty details and 10% implementation.  I might even volunteer to
do some implementation if there is anything resembling agreement on a
design.

First of all, I was way too glib with "OFD", because OFD doesn't cover
cwd, root, or any of the magic links in /proc, nor does OFD cover
mountpoints.

Second, I confess to having my brain dump be hopelessly polluted by a
substantial ulterior motive: I want fds (or whatevers)-as-capabilities
to go both ways, which means that I want to be able to open an fd that
captures certain of my privileges such that someone with different
current->cred can use it.  And I want *that* ability to work with bind
mounts and such.  The main motivation for this is that I *despise* the
way that outside-a-container filesystem modes interact with
containers.  It makes me extremely disappointed that I can take an
ordinary subdirectory of my home directory and mount it into a Linux
container on a Mac running MacOS without mucking with permissions at
all, but trying to do the same thing on the average Linux distro plus
the average container runtime is an exercise in hair pulling,
especially when SELinux is involved.  *Why* can't it just work?

So let's call the directory version of this thing a "directory
capability", and directory capabilities can live in OFDs or in cwd,
root, or a vfsmount.

The default directory capability (IMO) ought to be exactly equivalent
to O_PATH or a cwd.  But I think one should be able to make a
restricted one with bits like:

- read: if not set, you can't read the files in here. executing
unconditionally counts as reading for this purpose.

- execute: If not set, you can't execute the files in here.  (This
kind of "execute" has nothing to do with opening subdirectories.
we're not talking about POSIX.)  Everyone can argue about whether
running a shell script and/or loading a shared library counts as
executing.

- write: If not set, you can't write the files in here.

- create: If not set, you can't create files in here.  (How does this
interact with write?)

- maybe some bits to prevent writing metadata, fs-specific ioctls, etc.

- dotdot and/or ancestor: needs thought.  One way would be what I
mentioned above: any path resolution starting at this directory
capability or anything derived from it forces RESOLVE_BENEATH.

- open question: what happens if i open a directory and then unmount
something that was mounted on one of its subdirectories?  Does this
matter?

The general idea of using these things is that one might open an fd
with constraints:

fd = open_with_constraints(..., read | execute);

Now using fd in openat forces RESOLVE_BENEATH (because I didn't say
dotdot), and any attempt to open a file for write or to create a new
file will fail.  And if I fchdir or fchroot to fd, then those
constraints still apply.  And if I do:

fd = open_with_constraints(..., read);
fd2 = open_with_constraints(fd, ..., read | execute);

then we need to decide whether to fail outright or to return something
that doesn't have execute.

If I do:

fd = open_with_constraints(..., read);
fchdir(fd);
open("/proc/self/cwd/../foo") needs to fail (or perhaps just open ~/foo?)

If I take that same fd and bind-mount it on /mnt/restricted, then I
should not be able to find its ancestors via /mnt/restricted/..
(obviously -- that doesn't work anyway).  (But maybe there should be
an extra flag "seethrough" which, if not set, would prevent a
non-recursive bind mount like that, since it would let someone see
through mounts on the tree referred to by fd.)  And I should also not
be able to write to /mnt/restricted/foo.


For added fun (see above), I want an ability to "delegate" my own
permissions into an fd.

fd = open_delegate(..., read);

Now *anyone* with a copy of fd (or access via cwd, etc) can read files
in the subtree using my credentials, as captured when I called
open_delegate.  And this should survive bind mounting, so I could
finally do:

my_favorite_container_tool --delegate /home/username/foo,/mnt/mountpoint,read

and the things in the container, even if they use a dynamic subuid or
have some funky SELinux context, could actually read via
/mnt/mountpoint, and I could stop cursing at my computer.

- Open question: what happens to /proc/.../exe?  IMO it would be
*really* nice if we could get away with applying this sort of system
to /proc/.../exe.  There are ABI compatibility concerns here.  A whole
class of old nasty vulnerabilities would go away if writing through
/proc/.../exe were simply impossible.


Okay, there's my brain dump.  Feel free to make fun of it.  No LLMs
were roused from their slumber in the writing of this email :)

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [RFC] Null Namespaces
  2026-06-24 22:51 [RFC] Null Namespaces John Ericson
                   ` (2 preceding siblings ...)
  2026-06-29 11:45 ` Christian Brauner
@ 2026-07-04 13:20 ` Li Chen
  3 siblings, 0 replies; 38+ messages in thread
From: Li Chen @ 2026-07-04 13:20 UTC (permalink / raw)
  To: John Ericson
  Cc: Cong Wang, Christian Brauner, linux-arch, linux-kernel,
	linux-fsdevel, linux-api, Arnd Bergmann, Andy Lutomirski,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	H. Peter Anvin, Jan Kara, Jonathan Corbet, Shuah Khan,
	Alexander Viro, Kees Cook, Sergei Zimmerman, Farid Zakaria

Hi John,

Sorry for the silence. I got pulled into some work stuff recently and
only caught up with these threads now. I still have not studied every
patch line by line, but I wanted to say that I really appreciate you
pushing on this.

The embryonic-process framing lines up well with the direction I have
been trying to move toward after the earlier feedback: make the
not-yet-runnable state explicit, and keep task creation separate from the
state installed into the new task.

For my next process-builder RFC, I plan to keep the first step small: a
pidfd-based builder for posix_spawn-style semantics, with limited action
support; it will not yet have the pristine/no-source backend. 
So cwd/root, non-CLOEXEC fds, and
other selected source state can still be inherited unless actions or
attributes say otherwise.

I think the lower-authority/null-namespace side should be an explicit
mode, not the default for the posix_spawn-compatible path. Otherwise it
would change existing expectations around cwd/root, inherited fds, and
other source state.

Longer term, I would like to add that lower-authority side as follow-up
work: pristine/no-source process creation, where resources are installed
deliberately instead of starting from the parent's fs/fd/mm state. I had
an earlier experimental local branch that tried to separate pristine task
allocation from source-state installation, but that is not the RFC I plan
to send first.

For that later mode, Christian's nullfs/failfs direction also looks like
the more practical way to express the low-authority fs state.

Thanks again for pushing this forward.

Regards,
Li​



^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [RFC] Null Namespaces
  2026-07-02  9:34     ` Christian Brauner
  2026-07-02 15:43       ` John Ericson
  2026-07-03 17:35       ` Directory capability brain dump (Re: [RFC] Null Namespaces) Andy Lutomirski
@ 2026-07-06 14:52       ` Christian Brauner
  2026-07-06 16:32         ` Jann Horn
  2026-07-06 17:04         ` Andy Lutomirski
  2 siblings, 2 replies; 38+ messages in thread
From: Christian Brauner @ 2026-07-06 14:52 UTC (permalink / raw)
  To: Andy Lutomirski
  Cc: Jann Horn, John Ericson, Li Chen, Cong Wang, linux-arch,
	linux-kernel, linux-fsdevel, linux-api, Arnd Bergmann,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	H. Peter Anvin, Jan Kara, Jonathan Corbet, Shuah Khan,
	Alexander Viro, Kees Cook, Sergei Zimmerman, Farid Zakaria

On Thu, Jul 02, 2026 at 11:34:01AM +0200, Christian Brauner wrote:
> On Mon, Jun 29, 2026 at 02:06:55PM -0700, Andy Lutomirski wrote:
> > On Mon, Jun 29, 2026 at 4:45 AM Christian Brauner <brauner@kernel.org> wrote:
> > >
> > 
> > > But I guess the even simpler model would be to copy what I've been doing
> > > for pidfs:
> > >
> > > +static struct path nullfs_root_path = {};
> > > +
> > > +void nullfs_get_root(struct path *path)
> > > +{
> > > +       *path = nullfs_root_path;
> > > +       path_get(path);
> > > +}
> > > +
> > >  static void __init init_mount_tree(void)
> > >  {
> > >         struct vfsmount *mnt, *nullfs_mnt;
> > > @@ -6209,6 +6217,8 @@ static void __init init_mount_tree(void)
> > >         /* Mount mutable rootfs on top of nullfs. */
> > >         root.mnt                = nullfs_mnt;
> > >         root.dentry             = nullfs_mnt->mnt_root;
> > > +       nullfs_root_path.mnt    = nullfs_mnt;
> > > +       pidfs_root_path.dentry  = nullfs_mnt->mnt_root;
> > >
> > >         LOCK_MOUNT_EXACT(mp, &root);
> > >         if (unlikely(IS_ERR(mp.parent)))
> > > diff --git a/include/uapi/linux/fcntl.h b/include/uapi/linux/fcntl.h
> > > index aadfbf6e0cb3..f55c87c70b78 100644
> > > --- a/include/uapi/linux/fcntl.h
> > > +++ b/include/uapi/linux/fcntl.h
> > > @@ -124,6 +124,7 @@ struct delegation {
> > >
> > >  #define FD_PIDFS_ROOT                  -10002 /* Root of the pidfs filesystem */
> > >  #define FD_NSFS_ROOT                   -10003 /* Root of the nsfs filesystem */
> > > +#define FD_NULLFS_ROOT                 -10004 /* Root of the nullfs filesystem */
> > >  #define FD_INVALID                     -10009 /* Invalid file descriptor: -10000 - EBADF = -10009 */
> > >
> > >  /* Generic flags for the *at(2) family of syscalls. */
> > >
> > > we then add fchroot() (overdue anyway) and then teach both fchdir() and
> > > fchroot() to honor FD_NULLFS_ROOT. Then a process may shed its fs state
> > > and move itself into nullfs. Restrict *chdir() and *chroot() for said
> > > process via seccomp and it's locked in forever as well.
> > >
> > 
> > One thing comes to mind that might need a bit of care: this would give
> > an API for any task to get an fd to a directory that lives in the init
> > mount namespace.  It's not at all obvious to me that this is dangerous
> > or even observable (you're not about to find a setuid program in
> > nullfs), but I think it's at least worth a tiny bit of consideration.
> 
> Yes, I thought about this as well. But it doesn't have to be this way.
> Every mount namespaces has nullfs as it's root ever since I introduced
> it. Which means FD_NULLFS_ROOT can also just mean "nullfs within that
> specific mount namespace". That's fine.
> 
> For my FD_FAILFS_ROOT proposal it would be enough if we make failfs
> SB_KERNMOUNT which means it's logically distinct from every mount
> namespace. I think that might be the right thing to do. I need to spend
> one or more brain cycles on this though.

I had to take a long drive on Sunday and I kept thinking about both
FD_NULLFS_ROOT and FD_FAILFS_ROOT and ofc there are some things to
consider/discuss.

I think the straightforward solution to FD_NULLFS_ROOT would be to just:

- make it always available
- refer to the caller's mount namespace nullfs
- work with fchroot()/fchdir()

So I considered two chroot() use-cases for the sake of simplicity:

(1) You want to isolate yourself for the sake of lookup

(2) You want to isolate yourself to assemble a "private mount tree" but
    not really be in a separate namespace (very odd use-case... but it
    helps to make a point).

The problem with this approach is that everyone who chroots into the
nullfs root would suffer from the problem that any mount on top of it is
still visible. So that kinda makes it pointless for both (1) and (2).

Also all mounts that someone else would do would also be visible
allowing multiple chroot()ers to affect each others state. That also
would somewhat defeat the purpose of the chroot(). So I'm not convinced
this is what we should do.

IOW, I think FD_NULLFS_ROOT to chroot to the nullfs of your mount
namespace is mostly useless and just not workable for unprivileged
fchroot().

Instead, this made me consider whether it wouldn't make more sense to
allow unprivileged mount namespace unsharing for both CLONE_EMPTY_MNTNS
and UNSHARE_EMPTY_MNTNS. Look, there's no real risk at all. It is
literally just placing the caller into a new mount namespace with only
nullfs in there. I fail to see any attack vector here. It's literally
self-sandboxing and you give up access to anything that you didn't have
a file descriptor open for. It's actually a bonus, because you don't
need to use userns for this. You just throw away your filesystem state.

FD_FAILFS_ROOT on the other hand should work fine. It would be a shared
single fs with SB_KERNMOUNT and you can't do anything at all:

- no lookup
- no creation (duh)
- no stat
- no mounting

We could certainly allow a chroot() into this which would mean from that
point onward all your lookup bust be relative to a given file
descriptor. Anything that requires absolute paths would fail. Which also
means any absolute symlink would fail afaict. It's kinda like an
fs_struct variant of: RESOLVE_BENEATH where a FD_FAILFS_ROOT fs_struct
forces you to provide an actual dirfd...

chroot()ing back into anything non-empty would necessarily require
CAP_SYS_CHROOT. And since you're chroot()ed you can't unshare a userns.
So the only way to get out of this is by having access to a file
descriptor to a mount namespace that the caller has privilege over and
can setns() into. So it's mostly a "throw-away-the-key" moment. 

> > But if this happens, maybe we could finally land one of the patches to
> > enable unprivileged chroot?  It's been tried a few times.
> > 
> > https://lore.kernel.org/lkml/0e2f0f54e19bff53a3739ecfddb4ffa9a6dbde4d.1327858005.git.luto@amacapital.net/
> > 
> > https://lore.kernel.org/all/20210316203633.424794-2-mic@digikod.net/
> > 
> > I think the need for it has reduced a tiny bit with user namespaces,
> > as you can sort of emulate it by unsharing your user namespace and
> > thus getting enough privilege, but this is rather heavyweight and
> > limiting.
> 
> I think we could make that work with both FD_NULLFS_ROOT and
> FD_FAILFS_ROOT...
> 
> > 
> > 
> > If all of the above landed, then the old chroot /var/empty kludge that
> > security-minded programs have done for decades could finally be
> > modernized and not require any privilege :)
> 
> I think I like it.
> 
> > Hmm, thinking aloud: every now and then someone brings up the idea of
> > having an fd (really an OFD) that points to a file or a directory but
> > carries less in the way of permissions/capabilities than the usual
> > OFDs.  If we had a way to make an OFD to a directory that forced
> > RESOLVE_BENEATH (or RESOLVE_IN_ROOT) and that propagated that
> > restriction to anything you open using it, and if an unprivileged
> > process could chroot itself to nullfs, then we would be getting quite
> > close to what Capsicum can do.
> 
> Next steps. I hear you volunteering...

Thanks for the braindump. I need to find time to process it all.

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [RFC] Null Namespaces
  2026-07-06 14:52       ` [RFC] Null Namespaces Christian Brauner
@ 2026-07-06 16:32         ` Jann Horn
  2026-07-06 17:10           ` Andy Lutomirski
  2026-07-06 17:04         ` Andy Lutomirski
  1 sibling, 1 reply; 38+ messages in thread
From: Jann Horn @ 2026-07-06 16:32 UTC (permalink / raw)
  To: Christian Brauner
  Cc: Andy Lutomirski, John Ericson, Li Chen, Cong Wang, linux-arch,
	linux-kernel, linux-fsdevel, linux-api, Arnd Bergmann,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	H. Peter Anvin, Jan Kara, Jonathan Corbet, Shuah Khan,
	Alexander Viro, Kees Cook, Sergei Zimmerman, Farid Zakaria

On Mon, Jul 6, 2026 at 4:52 PM Christian Brauner <brauner@kernel.org> wrote:
> I think the straightforward solution to FD_NULLFS_ROOT would be to just:
>
> - make it always available
> - refer to the caller's mount namespace nullfs
> - work with fchroot()/fchdir()
>
> So I considered two chroot() use-cases for the sake of simplicity:
>
> (1) You want to isolate yourself for the sake of lookup
>
> (2) You want to isolate yourself to assemble a "private mount tree" but
>     not really be in a separate namespace (very odd use-case... but it
>     helps to make a point).
>
> The problem with this approach is that everyone who chroots into the
> nullfs root would suffer from the problem that any mount on top of it is
> still visible. So that kinda makes it pointless for both (1) and (2).
>
> Also all mounts that someone else would do would also be visible
> allowing multiple chroot()ers to affect each others state. That also
> would somewhat defeat the purpose of the chroot(). So I'm not convinced
> this is what we should do.
>
> IOW, I think FD_NULLFS_ROOT to chroot to the nullfs of your mount
> namespace is mostly useless and just not workable for unprivileged
> fchroot().
>
> Instead, this made me consider whether it wouldn't make more sense to
> allow unprivileged mount namespace unsharing for both CLONE_EMPTY_MNTNS
> and UNSHARE_EMPTY_MNTNS. Look, there's no real risk at all. It is
> literally just placing the caller into a new mount namespace with only
> nullfs in there. I fail to see any attack vector here. It's literally
> self-sandboxing and you give up access to anything that you didn't have
> a file descriptor open for. It's actually a bonus, because you don't
> need to use userns for this. You just throw away your filesystem state.

I mostly agree, though we might want to gate this on no_new_privs just
to be sure - you could theoretically have a setuid root program that
gives the caller more privileges if it can't find its config file.
That's kind of a far-fetched scenario, and in reality it would
probably fail because the dynamic linker can't be found, but there is
precedent for other sandboxing stuff also requiring no_new_privs, so
we might as well require that here, too...

(I think it actually might be fine to just make chroot entirely
unprivileged as long as no_new_privs is set, but I don't think we
should actually do that, that would just be unnecessarily playing with
fire and would probably confuse some security monitoring tools or
such.)

We should probably also reject if current_chrooted() is true, for the
same reason we reject userns creation when that's true.

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [RFC] Null Namespaces
  2026-07-06 14:52       ` [RFC] Null Namespaces Christian Brauner
  2026-07-06 16:32         ` Jann Horn
@ 2026-07-06 17:04         ` Andy Lutomirski
  1 sibling, 0 replies; 38+ messages in thread
From: Andy Lutomirski @ 2026-07-06 17:04 UTC (permalink / raw)
  To: Christian Brauner
  Cc: Andy Lutomirski, Jann Horn, John Ericson, Li Chen, Cong Wang,
	linux-arch, linux-kernel, linux-fsdevel, linux-api, Arnd Bergmann,
	Thomas Gleixner, Ingo Molnar, Borislav Petkov, Dave Hansen,
	H. Peter Anvin, Jan Kara, Jonathan Corbet, Shuah Khan,
	Alexander Viro, Kees Cook, Sergei Zimmerman, Farid Zakaria

On Mon, Jul 6, 2026 at 8:31 AM Christian Brauner <brauner@kernel.org> wrote:
>
> On Thu, Jul 02, 2026 at 11:34:01AM +0200, Christian Brauner wrote:
> > On Mon, Jun 29, 2026 at 02:06:55PM -0700, Andy Lutomirski wrote:
> > > On Mon, Jun 29, 2026 at 4:45 AM Christian Brauner <brauner@kernel.org> wrote:
> > > >
> > >
> > > > But I guess the even simpler model would be to copy what I've been doing
> > > > for pidfs:
> > > >
> > > > +static struct path nullfs_root_path = {};
> > > > +
> > > > +void nullfs_get_root(struct path *path)
> > > > +{
> > > > +       *path = nullfs_root_path;
> > > > +       path_get(path);
> > > > +}
> > > > +
> > > >  static void __init init_mount_tree(void)
> > > >  {
> > > >         struct vfsmount *mnt, *nullfs_mnt;
> > > > @@ -6209,6 +6217,8 @@ static void __init init_mount_tree(void)
> > > >         /* Mount mutable rootfs on top of nullfs. */
> > > >         root.mnt                = nullfs_mnt;
> > > >         root.dentry             = nullfs_mnt->mnt_root;
> > > > +       nullfs_root_path.mnt    = nullfs_mnt;
> > > > +       pidfs_root_path.dentry  = nullfs_mnt->mnt_root;
> > > >
> > > >         LOCK_MOUNT_EXACT(mp, &root);
> > > >         if (unlikely(IS_ERR(mp.parent)))
> > > > diff --git a/include/uapi/linux/fcntl.h b/include/uapi/linux/fcntl.h
> > > > index aadfbf6e0cb3..f55c87c70b78 100644
> > > > --- a/include/uapi/linux/fcntl.h
> > > > +++ b/include/uapi/linux/fcntl.h
> > > > @@ -124,6 +124,7 @@ struct delegation {
> > > >
> > > >  #define FD_PIDFS_ROOT                  -10002 /* Root of the pidfs filesystem */
> > > >  #define FD_NSFS_ROOT                   -10003 /* Root of the nsfs filesystem */
> > > > +#define FD_NULLFS_ROOT                 -10004 /* Root of the nullfs filesystem */
> > > >  #define FD_INVALID                     -10009 /* Invalid file descriptor: -10000 - EBADF = -10009 */
> > > >
> > > >  /* Generic flags for the *at(2) family of syscalls. */
> > > >
> > > > we then add fchroot() (overdue anyway) and then teach both fchdir() and
> > > > fchroot() to honor FD_NULLFS_ROOT. Then a process may shed its fs state
> > > > and move itself into nullfs. Restrict *chdir() and *chroot() for said
> > > > process via seccomp and it's locked in forever as well.
> > > >
> > >
> > > One thing comes to mind that might need a bit of care: this would give
> > > an API for any task to get an fd to a directory that lives in the init
> > > mount namespace.  It's not at all obvious to me that this is dangerous
> > > or even observable (you're not about to find a setuid program in
> > > nullfs), but I think it's at least worth a tiny bit of consideration.
> >
> > Yes, I thought about this as well. But it doesn't have to be this way.
> > Every mount namespaces has nullfs as it's root ever since I introduced
> > it. Which means FD_NULLFS_ROOT can also just mean "nullfs within that
> > specific mount namespace". That's fine.
> >
> > For my FD_FAILFS_ROOT proposal it would be enough if we make failfs
> > SB_KERNMOUNT which means it's logically distinct from every mount
> > namespace. I think that might be the right thing to do. I need to spend
> > one or more brain cycles on this though.
>
> I had to take a long drive on Sunday and I kept thinking about both
> FD_NULLFS_ROOT and FD_FAILFS_ROOT and ofc there are some things to
> consider/discuss.
>
> I think the straightforward solution to FD_NULLFS_ROOT would be to just:
>
> - make it always available
> - refer to the caller's mount namespace nullfs
> - work with fchroot()/fchdir()
>
> So I considered two chroot() use-cases for the sake of simplicity:
>
> (1) You want to isolate yourself for the sake of lookup
>
> (2) You want to isolate yourself to assemble a "private mount tree" but
>     not really be in a separate namespace (very odd use-case... but it
>     helps to make a point).
>
> The problem with this approach is that everyone who chroots into the
> nullfs root would suffer from the problem that any mount on top of it is
> still visible. So that kinda makes it pointless for both (1) and (2).

Ugh.

You're at least functionally correct, although this all reminds me
that I have never felt like Linux's vfs mount hierarchy makes any
sense:

root@debian:/mnt/empty# mkdir hidden
root@debian:/mnt/empty# mount --bind /usr .
root@debian:/mnt/empty# ls
hidden
root@debian:/mnt/empty# ls hidden/..
bin  games  include  lib  libexec  local  sbin share  src

>
> FD_FAILFS_ROOT on the other hand should work fine. It would be a shared
> single fs with SB_KERNMOUNT and you can't do anything at all:
>
> - no lookup
> - no creation (duh)
> - no stat
> - no mounting
>
> We could certainly allow a chroot() into this which would mean from that
> point onward all your lookup bust be relative to a given file
> descriptor. Anything that requires absolute paths would fail. Which also
> means any absolute symlink would fail afaict. It's kinda like an
> fs_struct variant of: RESOLVE_BENEATH where a FD_FAILFS_ROOT fs_struct
> forces you to provide an actual dirfd...
>
> chroot()ing back into anything non-empty would necessarily require
> CAP_SYS_CHROOT. And since you're chroot()ed you can't unshare a userns.
> So the only way to get out of this is by having access to a file
> descriptor to a mount namespace that the caller has privilege over and
> can setns() into. So it's mostly a "throw-away-the-key" moment.
>
> > > But if this happens, maybe we could finally land one of the patches to
> > > enable unprivileged chroot?  It's been tried a few times.
> > >
> > > https://lore.kernel.org/lkml/0e2f0f54e19bff53a3739ecfddb4ffa9a6dbde4d.1327858005.git.luto@amacapital.net/
> > >
> > > https://lore.kernel.org/all/20210316203633.424794-2-mic@digikod.net/
> > >
> > > I think the need for it has reduced a tiny bit with user namespaces,
> > > as you can sort of emulate it by unsharing your user namespace and
> > > thus getting enough privilege, but this is rather heavyweight and
> > > limiting.
> >
> > I think we could make that work with both FD_NULLFS_ROOT and
> > FD_FAILFS_ROOT...
> >
> > >
> > >
> > > If all of the above landed, then the old chroot /var/empty kludge that
> > > security-minded programs have done for decades could finally be
> > > modernized and not require any privilege :)
> >
> > I think I like it.
> >
> > > Hmm, thinking aloud: every now and then someone brings up the idea of
> > > having an fd (really an OFD) that points to a file or a directory but
> > > carries less in the way of permissions/capabilities than the usual
> > > OFDs.  If we had a way to make an OFD to a directory that forced
> > > RESOLVE_BENEATH (or RESOLVE_IN_ROOT) and that propagated that
> > > restriction to anything you open using it, and if an unprivileged
> > > process could chroot itself to nullfs, then we would be getting quite
> > > close to what Capsicum can do.
> >
> > Next steps. I hear you volunteering...
>
> Thanks for the braindump. I need to find time to process it all.
>

^ permalink raw reply	[flat|nested] 38+ messages in thread

* Re: [RFC] Null Namespaces
  2026-07-06 16:32         ` Jann Horn
@ 2026-07-06 17:10           ` Andy Lutomirski
  0 siblings, 0 replies; 38+ messages in thread
From: Andy Lutomirski @ 2026-07-06 17:10 UTC (permalink / raw)
  To: Jann Horn
  Cc: Christian Brauner, Andy Lutomirski, John Ericson, Li Chen,
	Cong Wang, linux-arch, linux-kernel, linux-fsdevel, linux-api,
	Arnd Bergmann, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
	Dave Hansen, H. Peter Anvin, Jan Kara, Jonathan Corbet,
	Shuah Khan, Alexander Viro, Kees Cook, Sergei Zimmerman,
	Farid Zakaria

On Mon, Jul 6, 2026 at 9:55 AM Jann Horn <jannh@google.com> wrote:
>

> I mostly agree, though we might want to gate this on no_new_privs just
> to be sure - you could theoretically have a setuid root program that
> gives the caller more privileges if it can't find its config file.
> That's kind of a far-fetched scenario, and in reality it would
> probably fail because the dynamic linker can't be found, but there is
> precedent for other sandboxing stuff also requiring no_new_privs, so
> we might as well require that here, too...
>

My ancient patch did this gating.

FWIW, if we are contemplating letting unprivileged tasks chroot to an
*empty* mountns and they don't have privileges to bind anything there,
then the mnt_may_suid() will prevent them from using execveat to run a
setuid program.

> (I think it actually might be fine to just make chroot entirely
> unprivileged as long as no_new_privs is set, but I don't think we
> should actually do that, that would just be unnecessarily playing with
> fire and would probably confuse some security monitoring tools or
> such.)
>
> We should probably also reject if current_chrooted() is true, for the
> same reason we reject userns creation when that's true.
>

These empty-tree proposals might expose a little issue in
current_chrooted().  We don't want to check whether our root is the
namespace root -- I think we may want to check whether our root has a
parent.  Otherwise once you chroot to an empty tree once, you can't
chroot again, which is silly.  Maybe if we add this empty tree thing,
the definition of current_chrooted should change.

(This is already an issue with open_tree and probably even with
detached mounts before that, but I don't think it's as easily
observable.)

^ permalink raw reply	[flat|nested] 38+ messages in thread

end of thread, other threads:[~2026-07-06 17:10 UTC | newest]

Thread overview: 38+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-06-24 22:51 [RFC] Null Namespaces John Ericson
2026-06-24 23:06 ` Andy Lutomirski
2026-06-24 23:20   ` Andy Lutomirski
2026-06-24 23:53     ` John Ericson
2026-06-25  1:10       ` Al Viro
2026-06-25  3:41         ` John Ericson
2026-06-25 15:51           ` Andy Lutomirski
2026-06-25 18:21             ` John Ericson
2026-06-26  0:15           ` Al Viro
2026-06-26 16:26             ` John Ericson
2026-06-29 10:31             ` Christian Brauner
2026-06-24 23:12 ` Al Viro
2026-06-25 21:00   ` H. Peter Anvin
2026-06-25 21:50     ` John Ericson
2026-06-25 23:09       ` Andy Lutomirski
2026-06-26  8:27         ` David Laight
2026-06-26 17:23           ` John Ericson
2026-06-29 10:39       ` Christian Brauner
2026-07-01  9:49         ` Jori Koolstra
2026-07-02 21:28           ` H. Peter Anvin
2026-06-29 11:45 ` Christian Brauner
2026-06-29 21:06   ` Andy Lutomirski
2026-06-30  4:25     ` John Ericson
2026-07-02  9:34     ` Christian Brauner
2026-07-02 15:43       ` John Ericson
2026-07-03  8:59         ` Christian Brauner
2026-07-03 17:35       ` Directory capability brain dump (Re: [RFC] Null Namespaces) Andy Lutomirski
2026-07-06 14:52       ` [RFC] Null Namespaces Christian Brauner
2026-07-06 16:32         ` Jann Horn
2026-07-06 17:10           ` Andy Lutomirski
2026-07-06 17:04         ` Andy Lutomirski
2026-06-30  2:50   ` John Ericson
2026-06-30  7:14     ` Christian Brauner
2026-06-30 17:20       ` John Ericson
2026-06-30 17:41         ` Andy Lutomirski
2026-07-02  9:29           ` Christian Brauner
2026-06-30 18:05         ` H. Peter Anvin
2026-07-04 13:20 ` Li Chen

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.