LWN: Comments on "Unprivileged chroot()" https://lwn.net/Articles/849125/ This is a special feed containing comments posted to the individual LWN article titled "Unprivileged chroot()". en-us Thu, 16 Oct 2025 09:51:02 +0000 Thu, 16 Oct 2025 09:51:02 +0000 https://www.rssboard.org/rss-specification lwn@lwn.net Unprivileged chroot() and Outrun https://lwn.net/Articles/851794/ https://lwn.net/Articles/851794/ immibis <div class="FormattedComment"> is less of* a security risk<br> </div> Tue, 06 Apr 2021 19:28:53 +0000 Unprivileged chroot() and Outrun https://lwn.net/Articles/850868/ https://lwn.net/Articles/850868/ l0kod <div class="FormattedComment"> chroot(2) is much more simple (and limited) than namespaces, which is why there is no valid reason to be able to disable it (i.e. this unprivileged chroot is not, by design, a security risk).<br> </div> Sat, 27 Mar 2021 18:54:50 +0000 Unprivileged mknod() or use FUSE? https://lwn.net/Articles/850790/ https://lwn.net/Articles/850790/ jrincayc <div class="FormattedComment"> Hm, I wonder if you could get around the lack of things like no dev by either doing a &quot;dev&quot; in FUSE or possibly by another patch to allow some unprivileged uses of mknod? I would think that it might be possible to allow making /dev/null, /dev/zero, /dev/random and /dev/urandom be unprivileged.<br> </div> Fri, 26 Mar 2021 23:13:19 +0000 Unprivileged chroot() https://lwn.net/Articles/849944/ https://lwn.net/Articles/849944/ smurf <div class="FormattedComment"> Running in a plain chroot isn&#x27;t a good idea anyway; as soon as you do anything nontrivial things tend to break. The new unprivileged-chroot sycall is just one more example of many.<br> <p> Much better to use systemd-nspawn or some other tool that sets up a &quot;real&quot; file system namespace. The unprivileged chroot(2) will work there.<br> </div> Sun, 21 Mar 2021 10:50:29 +0000 Unprivileged chroot() https://lwn.net/Articles/849889/ https://lwn.net/Articles/849889/ kentonv <div class="FormattedComment"> Ahhhhh I see.<br> <p> That seems like a disappointing limitation though... any program that uses this feature will mysteriously break when run in a chroot.<br> </div> Fri, 19 Mar 2021 21:56:11 +0000 Unprivileged chroot() https://lwn.net/Articles/849881/ https://lwn.net/Articles/849881/ l0kod <div class="FormattedComment"> This is the reason of the unprivileged chroot limitations. It is only allowed to chroot one time: <a href="https://lore.kernel.org/lkml/20210316203633.424794-2-mic@digikod.net/">https://lore.kernel.org/lkml/20210316203633.424794-2-mic@...</a><br> </div> Fri, 19 Mar 2021 18:32:18 +0000 Unprivileged chroot() https://lwn.net/Articles/849801/ https://lwn.net/Articles/849801/ kentonv <div class="FormattedComment"> Right, but, my point is that the proposed feature would let anyone break out of chroots even if they were set up &quot;correctly&quot;.<br> </div> Fri, 19 Mar 2021 00:22:03 +0000 Unprivileged chroot() https://lwn.net/Articles/849799/ https://lwn.net/Articles/849799/ flussence <div class="FormattedComment"> This is a trap as old as time in using chroot securely: you&#x27;re supposed to call chroot() immediately followed by - at a bare minimum - chdir(&quot;/&quot;), to prevent escapes via old cwd handles, but the API lends itself well to forgetting.<br> </div> Fri, 19 Mar 2021 00:12:15 +0000 Unprivileged chroot() https://lwn.net/Articles/849787/ https://lwn.net/Articles/849787/ kentonv <div class="FormattedComment"> Do chroots stack? Or does calling chroot simply update the root pointer to point at something new?<br> <p> In the latter case, I think allowing unprivileged chroot() ironically makes it possible to escape a preexisting chroot jail by the following means:<br> <p> 1. chdir(&quot;/foo&quot;)<br> 2. chroot(&quot;/bar&quot;)<br> 3. open(&quot;../..&quot;)<br> <p> Step 3 opens the parent of the previous root! Because &quot;..&quot; is no longer recognized as being the current root, the kernel doesn&#x27;t prevent traversing up past it.<br> <p> Verifying that the current directory is under the new root is not enough... Instead of chdir() in step 1 you could also open a file descriptor to &quot;/foo&quot; and then openat() in step 3.<br> <p> Verifying that all open file descriptors are under the new root still isn&#x27;t enough, because file descirptors could be transmitted via SCM_RIGHTS over a unix socket from an accomplice program that isn&#x27;t inside the new chroot.<br> <p> I think it only works if chroots stack, but my understanding is that they don&#x27;t.<br> </div> Thu, 18 Mar 2021 22:11:34 +0000 Unprivileged chroot() https://lwn.net/Articles/849765/ https://lwn.net/Articles/849765/ matthias <div class="FormattedComment"> Yes, my suggestion was to use the modified chroot command as an alternative to unprivileged chroot() syscall. And it was not meant to be used repeatedly. Obviously, it cannot be used repeatedly as after one execution NO_NEW_PRIVS is set and the CAP_CHROOT filesystem capability will have no effect.<br> <p> And of course, if someone chroots a process without NO_NEW_PRIVS in a classic way, there should be no enchanted chroot command that gets capabilities from the filesystem laying around inside the new root. <br> </div> Thu, 18 Mar 2021 17:05:19 +0000 Unprivileged chroot() https://lwn.net/Articles/849759/ https://lwn.net/Articles/849759/ floppus <div class="FormattedComment"> It&#x27;s dangerous to allow that if the process is already chrooted, since it lets you escape from the outer chroot.<br> <p> For that reason (I think), unprivileged processes can&#x27;t create user namespaces when they&#x27;re already chrooted, and the proposed unprivileged chroot would likewise be forbidden.<br> </div> Thu, 18 Mar 2021 16:54:30 +0000 Unprivileged chroot() https://lwn.net/Articles/849763/ https://lwn.net/Articles/849763/ jcpunk <div class="FormattedComment"> I&#x27;m certain it is my ignorance showing, but is there a reason `chroot()` isn&#x27;t a type of mount namespace these days?<br> </div> Thu, 18 Mar 2021 16:45:19 +0000 Unprivileged chroot() and Outrun https://lwn.net/Articles/849703/ https://lwn.net/Articles/849703/ domenpk <div class="FormattedComment"> That same distro or sysadmin will almost surely also disable unprivileged chroot (being a newer and less tested feature), so you won&#x27;t gain anything.<br> </div> Thu, 18 Mar 2021 12:54:10 +0000 Unprivileged chroot() https://lwn.net/Articles/849701/ https://lwn.net/Articles/849701/ winstonx86 <div class="FormattedComment"> Yes but I suppose you couldn’t perform a second chroot because of the NO_NEW_PRIVS<br> </div> Thu, 18 Mar 2021 12:26:15 +0000 Unprivileged chroot() https://lwn.net/Articles/849695/ https://lwn.net/Articles/849695/ l0kod <div class="FormattedComment"> This should work with setpriv --no-new-privs /usr/sbin/chroot /new/root /bin/sh<br> </div> Thu, 18 Mar 2021 11:01:37 +0000 Unprivileged chroot() https://lwn.net/Articles/849685/ https://lwn.net/Articles/849685/ matthias <div class="FormattedComment"> Would it be possible to create a chroot command that has CAP_SYS_CHROOT filesystem capabilities, does the chroot, drops CAP_SYS_CHROOT and sets NO_NEW_PRIVS before calling the supplied command?<br> </div> Thu, 18 Mar 2021 09:14:01 +0000 Unprivileged chroot() https://lwn.net/Articles/849679/ https://lwn.net/Articles/849679/ NYKevin <div class="FormattedComment"> It depends on how your system&#x27;s chroot(8) was written.<br> <p> * If it explicitly checks geteuid() == 0, then it will continue to fail for non-root. This is probably a bad design decision, but not impossible if the application writer was trying to be &quot;helpful&quot; and provide a more explicit error message. On non-Linux systems, it would not be wrong to insert such a check, and some of these tools are written for &quot;any random Unix-like&quot; rather than Linux specifically.<br> * Unless it calls prctl() with PR_SET_NO_NEW_PRIVS, it will continue to fail for non-root. I see nothing about this in the man page for the GNU version, but it&#x27;s possible a vendor might ship a version of chroot which does this. If this patch does get implemented, future versions of the GNU tool might grow a command-line argument to enable this functionality (or they might not; I can&#x27;t read the GNU people&#x27;s collective mind).<br> * Because chroot(8) runs a separate executable after doing the chroot, shared libraries etc. need to be accessible from within the chroot environment. It is complicated (but not categorically impossible) for a non-privileged user to set this up.<br> <p> TL;DR: You probably still need to be root to profitably use chroot(8), even with this patch.<br> </div> Thu, 18 Mar 2021 01:48:37 +0000 Unprivileged chroot() and Outrun https://lwn.net/Articles/849678/ https://lwn.net/Articles/849678/ pabs <div class="FormattedComment"> It does if your distro or sysadmin has disabled unprivileged namespaces by default.<br> </div> Thu, 18 Mar 2021 01:03:29 +0000 Unprivileged chroot() and Outrun https://lwn.net/Articles/849673/ https://lwn.net/Articles/849673/ NYKevin <div class="FormattedComment"> Reading through user_namespaces(7), I can think of the following problems:<br> <p> 1. You have to set up uid_map and gid_map if you want to interact with the filesystem. Since you are using chroot(), you almost certainly do want to interact with the filesystem, so this is an obvious source of friction. Not impossible, just annoying.<br> 2. Assuming you don&#x27;t have CAP_SETUID/GID (in the parent user namespace), which is a safe assumption because otherwise you wouldn&#x27;t be asking for &quot;unprivileged chroot&quot; in the first place, then the man page appears to say that you can only map your own UID/GID. That certainly makes logical sense (the whole point of this operation is to give you a &quot;containerized&quot; or unprivileged CAP_SETUID, so we need to constrain it somehow), but it also means that stat(2) will lie to you about the ownership of any file you don&#x27;t own (the UID/GID is unmapped, so it gets converted to a generic &quot;don&#x27;t know&quot; value in the child namespace).<br> 3. SCM_CREDENTIALS will also produce unmapped garbage, as will plenty of other UID/GID-related interfaces. If you want to IPC with any process owned by a different user (e.g. a daemon running under a role account), you basically can&#x27;t confirm its identity, although it can confirm yours (which may be sufficient in some cases).<br> 4. Pervasively fixing all of the above, testing it, and maintaining everything, is likely harder than just granting CAP_SYS_CHROOT in the first place.<br> </div> Wed, 17 Mar 2021 22:39:45 +0000 Unprivileged chroot() https://lwn.net/Articles/849649/ https://lwn.net/Articles/849649/ metalheart <div class="FormattedComment"> Kernel noob here. This change will not affect the /usr/sbin/chroot tool?<br> </div> Wed, 17 Mar 2021 17:58:12 +0000 Unprivileged chroot() and Outrun https://lwn.net/Articles/849599/ https://lwn.net/Articles/849599/ floppus <div class="FormattedComment"> Right, but the point is that you *don&#x27;t* need a setuid executable. Creating a user namespace (calling &quot;unshare&quot;) normally doesn&#x27;t require any special privileges.<br> </div> Wed, 17 Mar 2021 17:39:34 +0000 Unprivileged chroot() https://lwn.net/Articles/849575/ https://lwn.net/Articles/849575/ l0kod <div class="FormattedComment"> These concerns have been taken into account in the commit message. ;)<br> </div> Wed, 17 Mar 2021 13:15:34 +0000 Unprivileged chroot() https://lwn.net/Articles/849573/ https://lwn.net/Articles/849573/ walters <div class="FormattedComment"> Just echoing my comments from 2012 in that thread with Andy: I still think running a process without at least the &quot;API devices&quot; e.g. `/dev/null` (and `/proc`) is just unnecessary painful for everyone authoring a shared library. There&#x27;s a lot of software that opens `/dev/null` when spawning child processes. And things like wanting to read `/proc/cpuinfo` as part of a multiprocessing library to determine how many threads to spawn.<br> <p> And yes, there&#x27;s now a syscall instead of `/dev/urandom` but still.<br> <p> </div> Wed, 17 Mar 2021 12:49:49 +0000 Unprivileged chroot() and Outrun https://lwn.net/Articles/849560/ https://lwn.net/Articles/849560/ smurf <div class="FormattedComment"> I know that there are several options to do this with a privileged process. My point is that this task should not require any. That way, if the user does get hacked, at least there&#x27;s no setuid executable for them to play with.<br> </div> Wed, 17 Mar 2021 10:09:13 +0000 Unprivileged chroot() https://lwn.net/Articles/849550/ https://lwn.net/Articles/849550/ roc <div class="FormattedComment"> Oooh, I didn&#x27;t know about RESOLVE_IN_ROOT. That solves my use-case perfectly!<br> <p> Unfortunately I can&#x27;t use it yet because I can&#x27;t guarantee we&#x27;re running on 5.6 or above, but this is the right API for me.<br> </div> Wed, 17 Mar 2021 02:07:49 +0000 Unprivileged chroot() https://lwn.net/Articles/849548/ https://lwn.net/Articles/849548/ dbnichol <div class="FormattedComment"> Right. The way I&#x27;ve seen this done before is to start the new process with several capabilities, setup the environment, and then drop all but the required caps before starting the real work. In a sense it&#x27;s better than what you could do with the unprivileged chroot that&#x27;s being suggested here. Once you do the intended chroot, you can drop the capability and then the rest of the code can&#x27;t use it anymore.<br> </div> Wed, 17 Mar 2021 00:28:05 +0000 Unprivileged chroot() and Outrun https://lwn.net/Articles/849529/ https://lwn.net/Articles/849529/ floppus <div class="FormattedComment"> That doesn&#x27;t require an *unprivileged* chroot; you could do something like:<br> <p> logfile = fopen(&quot;foo.log&quot;, &quot;a&quot;);<br> sqlite3_open(&quot;foo.db&quot;, &amp;db);<br> sprintf(rootdir, &quot;/run/user/%d/my-jail&quot;, getuid());<br> chdir(rootdir);<br> unshare(CLONE_NEWUSER);<br> chroot(&quot;.&quot;);<br> caps = cap_get_proc();<br> cap_clear(caps);<br> cap_set_proc(caps);<br> <p> <p> </div> Tue, 16 Mar 2021 19:07:15 +0000 Unprivileged chroot() https://lwn.net/Articles/849478/ https://lwn.net/Articles/849478/ gscrivano <div class="FormattedComment"> Have you already considered openat2(RESOLVE_IN_ROOT)? Wouldn&#x27;t that be enough to replace chroot()?<br> </div> Tue, 16 Mar 2021 13:45:08 +0000 Unprivileged chroot() https://lwn.net/Articles/849467/ https://lwn.net/Articles/849467/ roc <div class="FormattedComment"> That&#x27;s a good point. I prefer the code in question to run unprivileged inside its container, though.<br> </div> Tue, 16 Mar 2021 10:05:57 +0000 Unprivileged chroot() https://lwn.net/Articles/849465/ https://lwn.net/Articles/849465/ l0kod <div class="FormattedComment"> <font class="QuotedText">&gt; Salaün has not answered all of these points</font><br> <p> They are answered, especially with the third version: <a href="https://lore.kernel.org/lkml/20210311105242.874506-2-mic@digikod.net/">https://lore.kernel.org/lkml/20210311105242.874506-2-mic@...</a><br> </div> Tue, 16 Mar 2021 08:21:52 +0000 Unprivileged chroot() and Outrun https://lwn.net/Articles/849464/ https://lwn.net/Articles/849464/ smurf <div class="FormattedComment"> My program might want to open a sqlite database and a log file, *then* chroot to /run/user/UID/my-jail. When I restart the thing it might want to do the same thing and read the session files which the first program dumped there.<br> <p> There&#x27;s no way to do that without chroot.<br> </div> Tue, 16 Mar 2021 07:52:48 +0000 Unprivileged chroot() https://lwn.net/Articles/849462/ https://lwn.net/Articles/849462/ dbnichol <div class="FormattedComment"> If you&#x27;re already in a container with it&#x27;s own mount and user namespace (presumably), then can&#x27;t you just supply it with CAP_SYS_CHROOT?<br> </div> Tue, 16 Mar 2021 06:30:20 +0000 Unprivileged chroot() https://lwn.net/Articles/849460/ https://lwn.net/Articles/849460/ pabs <div class="FormattedComment"> Some of the other chroot-on-Android options are listed here:<br> <p> <a href="https://wiki.debian.org/ChrootOnAndroid">https://wiki.debian.org/ChrootOnAndroid</a><br> </div> Tue, 16 Mar 2021 04:32:39 +0000 Unprivileged chroot() https://lwn.net/Articles/849458/ https://lwn.net/Articles/849458/ rsidd The last time I used chroot was to run a linux subsystem in an android tab. I had a full xfce-based desktop on android, and did actual work on it. It required a rooted tab and, even so, later android releases made it hard. I haven't tried it recently but it seems these days they use <A HREF="https://wiki.termux.com/wiki/PRoot">proot</A> for this purpose, and root is not needed. Tue, 16 Mar 2021 03:47:38 +0000 Unprivileged chroot() https://lwn.net/Articles/849457/ https://lwn.net/Articles/849457/ geofft <div class="FormattedComment"> Unprivileged chroot, I think, was one of the original motivations for PR_SET_NO_NEW_PRIVS back in the day.<br> <p> Anyway, there&#x27;s one advantage of direct unprivileged chroot over making an unprivileged user + mount namespace and calling chroot inside there: you retain a full UID map of the outside system. If you use &quot;unshare -cm --keep-caps,&quot; you get to map a single UID, your own, and so things like &quot;ls -l /bin&quot; don&#x27;t display the results you&#x27;d expect. Since unprivileged chroot wouldn&#x27;t create a user namespace, things would still look normal.<br> <p> Maybe this could be worked around by saying something like, if you&#x27;re inside a user namespace, you have no capabilities, and you map to the same UID outside the namespace, and you call PR_SET_NO_NEW_PRIVS, you get the ability to write an identity map to uid_map and gid_map, even if they&#x27;ve already been written to. After all, in no new privs mode, you can&#x27;t switch users or gain any capabilities, so it doesn&#x27;t matter what UID mapping you see. But it seems extremely tricky to get the detail of that right and you&#x27;d probably introduce exploitable bugs the first few times you try.<br> <p> (I don&#x27;t believe CAP_SYS_CHROOT is a meaningful alternative here. How would you grant it? Would you give filesystem capabilities to the chroot command? It won&#x27;t be enforcing the NO_NEW_PRIVS requirement, then, and will turn into an immediate local root escalation. It _can&#x27;t_ enforce that requirement, in fact: if you run a setcap program under NO_NEW_PRIVS, those capabilities are ignored, specifically because you asked for no new privs! So it wouldn&#x27;t work if run from a no-new-privs parent process. If you somehow could avoid that constraint and run a setcap chroot, you could call it twice, the second time with a modified /lib thanks to being able to modify your chroot, and you could use that to escape the first chroot and hold onto chrooting privileges. It is technically true that CAP_SYS_CHROOT is the best example of how the capability mechanism is supposed to work - it&#x27;s a fantastic demonstration of how useless that mechanism is.)<br> </div> Tue, 16 Mar 2021 00:50:01 +0000 Unprivileged chroot() https://lwn.net/Articles/849455/ https://lwn.net/Articles/849455/ roc <div class="FormattedComment"> I ran into a use-case for this just recently.<br> <p> Our application runs in a container. It needs access to subtrees of the host filesystem. We mount each subtree /a/b/c under /host/a/b/c. Unfortunately this breaks because absolute symbolic links in the host filesystem (e.g. /a/b/c -&gt; /foo/bar) don&#x27;t exist in the container&#x27;s mount namespace (it would need to be interpreted as /host/foo/bar). I ended up writing an implementation of `realpath` that manually resolves symbolic links and knows how to rebase absolute symbolic links to the /host directory. It&#x27;s probably not nearly as efficient as doing it in the kernel though. I would have thought a lot of people ran into a need for this.<br> <p> Obviously unprivileged chroot() would provide a solution. Though, maybe unprivileged chroot alone wouldn&#x27;t be that great for us; we&#x27;d have to fork a helper process to do the chroot and pass fds back to the main process, which would be fairly complicated and maybe not faster than manual symlink resolution.<br> </div> Tue, 16 Mar 2021 00:11:40 +0000 Unprivileged chroot() and Outrun https://lwn.net/Articles/849451/ https://lwn.net/Articles/849451/ josh <div class="FormattedComment"> Debian allows unprivileged user namespaces these days, as of version 5.10.4-1 of the Linux packaging.<br> </div> Mon, 15 Mar 2021 22:30:08 +0000 Unprivileged chroot() and Outrun https://lwn.net/Articles/849445/ https://lwn.net/Articles/849445/ floppus <div class="FormattedComment"> That will fail if unprivileged user namespaces are disallowed, which is still the default on Debian, for instance.<br> </div> Mon, 15 Mar 2021 20:59:48 +0000 Unprivileged chroot() and Outrun https://lwn.net/Articles/849436/ https://lwn.net/Articles/849436/ mcon147 <div class="FormattedComment"> Can outrun use &#x27;unshare&#x27; ?<br> It seems like you can do<br> $ unshare -mr chroot os-tree-dir bash<br> </div> Mon, 15 Mar 2021 19:20:21 +0000 Unprivileged chroot() and Outrun https://lwn.net/Articles/849434/ https://lwn.net/Articles/849434/ nickodell <div class="FormattedComment"> Here&#x27;s a context where unprivileged chroot would be useful: a tool called Outrun. (<a href="https://github.com/Overv/outrun">https://github.com/Overv/outrun</a>)<br> <p> Outrun lets you execute a local command using the processing power of another Linux machine. In order to do this, it runs the process on the remote system, and redirects all filesystem calls back to the local system. It does this through two systems: FUSE and chroot. FUSE can be done in userspace with no extra permissions. chroot, however, requires root. For that reason, Outrun requires root privileges, even if the application you&#x27;re running doesn&#x27;t.<br> <p> There doesn&#x27;t seem to be a great way to solve this problem under the current permission scheme. Sure, there&#x27;s a chroot capability. But how do you give that chroot capability to processes running in Outrun? Outrun spawns processes from a normal login shell. If you give all login shells chroot capability, then that opens a security hole, due to setuid programs which can&#x27;t be allowed to run inside chroots.<br> <p> One solution which Outrun discussed was to write a setuid helper, which could run the chroot syscall on behalf of Outrun. However, those carry their own security risks. (See also: calibre&#x27;s setuid helper.)<br> <p> For these reasons, I think this patchset would be useful.<br> </div> Mon, 15 Mar 2021 19:04:52 +0000