Thread (38 messages) 38 messages, 9 authors, 1d ago

Re: [RFC] Null Namespaces

From: Christian Brauner <brauner@kernel.org>
Date: 2026-07-06 14:52:17
Also in: linux-arch, linux-fsdevel, lkml

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:
quoted
On Mon, Jun 29, 2026 at 4:45 AM Christian Brauner [off-list ref] wrote:
quoted
quoted
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. 
quoted
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/ (local)

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

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...
quoted

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.
quoted
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.
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help