From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-alma10-1.taild15c8.ts.net [100.103.45.18]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B054241A78A; Mon, 6 Jul 2026 14:52:17 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=100.103.45.18 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783349540; cv=none; b=i9FClhoWlvjSpDUq2PPS5A/kHZD6SMg4eIUgofGrPFBTUjyO/rHwd64m79OKbAjpcYxDGcx2Et2XXoenfB3GwnuPePwtdXhvmJb4wOzTMjaxJvofXqGcG8+MoasZjMbCOrf/RK7CPFBbcg4CrTy+u/oXySYaOIAIYRKWjyPhm5E= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1783349540; c=relaxed/simple; bh=E+N1CaT9P1kQXtoXwW/tR+bSIibes4Ncw4kAADx02D4=; h=Date:From:To:Cc:Subject:Message-ID:References:MIME-Version: Content-Type:Content-Disposition:In-Reply-To; b=dnqmy7UqiyLRNv75+vsZ9HO2jukGOS3b3TtG6/Wk5+YNN3JDdVLimtDsJ99IB3FZdv+725zi+TffL7EgebbTPHNvaBSiZHzyWZ85v0ktmDbthK3xaHDAxmDn5mNE/cof3ncCzgbQDzt9pxAAbTqdtyG4IW8ewcKEAl4lKEwL6aQ= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=cwtRtKXJ; arc=none smtp.client-ip=100.103.45.18 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="cwtRtKXJ" Received: by smtp.kernel.org (Postfix) with ESMTPSA id E3C531F000E9; Mon, 6 Jul 2026 14:52:12 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1783349537; bh=XBYL8V/PO921SnCtewae6ZrWrVk/vZQSAffys4XbIb4=; h=Date:From:To:Cc:Subject:References:In-Reply-To; b=cwtRtKXJT50Y8kC5OnYLlHnY42Yw236rPJhuKc6D9swJ0h3SrTb5BAzIjDdiB2r8Z oWB1s4wYurkTLh6uSVzaQQXTsu/Tkh4teBSLIqpsIe+p9Ld0Eebe7ez7RAWsFf2PnU LYSStlWpMHZ+B7h9M+afLAUbrduMZ2uv7sPRgqv9DZnVkS6e1XvErRYvSsnt+3yY1G P8oGRVd2D1aA9+Pq1yj68M3chTW8JVgUJ7L9xxrqRSXxHJ9fNKNLzPhSW8BPQVbFfu O7GAvmhW8DT1sBIPHInCWskUgGnv0bTa5lvA9YKTa/rbRZQ0qtl/aSPM2p+X+0G4KL iINuLW3u0iDLg== Date: Mon, 6 Jul 2026 16:52:10 +0200 From: Christian Brauner To: Andy Lutomirski Cc: Jann Horn , John Ericson , Li Chen , Cong Wang , linux-arch@vger.kernel.org, linux-kernel@vger.kernel.org, linux-fsdevel@vger.kernel.org, linux-api@vger.kernel.org, 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 Subject: Re: [RFC] Null Namespaces Message-ID: <20260706-dabei-radeln-glitzer-71ecb835029c@brauner> References: <20260629-hauer-erhitzen-sobald-96d3dff68707@brauner> <20260702-entladen-farbkombinationen-klarheit-fe24cb608f23@brauner> Precedence: bulk X-Mailing-List: linux-api@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Disposition: inline Content-Transfer-Encoding: 8bit In-Reply-To: <20260702-entladen-farbkombinationen-klarheit-fe24cb608f23@brauner> 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 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.