Re: Proposal: Enabling unprivileged chroot by default

From: Olivier Certner <olce_at_freebsd.org>
Date: Wed, 20 Aug 2025 12:46:51 UTC
Hi,

> > NO_NEW_PRIVS causes the kernel to ignore set-user-ID and set-group-ID
> > bits, preventing the well-known confused deputy issues affecting
> > chroot(2).
> 
> FYI: mac_do(4) needs to take NO_NEW_PRIVS into account.

TL;DR: It need not to in the immediate.  In the future, it does not seem very likely we would want to.

> Without that patch, an overly permissive mac_do(4) policy could result
> in privilege escalation in the unprivileged chroot:

There is no "privilege escalation" in the unprivileged chroot.  If your MAC/do rules authorize a transition from 1001 to 0, then switching to root is allowed, whether or not in the chroot, and there's nothing wrong with that in strictly logical terms (the chroot is transparent to mac_do(4)).  Perhaps you can convince yourself of that by remarking that the same result as with your steps can be reached by reversing the order of operations (i.e., first switching to root and then performing the chroot), which simply is out of reach for P2_NO_NEW_PRIVS.  Note also that switching to root inside a chroot is nothing absurd per se as root itself cannot escape the chroot (by usual means).  mac_do(4) supports per-jail configuration, so if switching to root is not allowed in some uses, these uses can (and probably should) happen in a sub-jail where MAC/do rules do not allow that transition.  Jails are, for a variety of reasons, a sweet spot for configurability in this case.

At the semantical and architectural levels, you are basically expecting that an unprivileged chroot(2) restricts all further user transitions, which could have some merits but in any case goes beyond what is necessary to address the "confused deputy" issues of unprivileged chroot that the introduction of P2_NO_NEW_PRIVS was meant to avoid.  Despite its name, P2_NO_NEW_PRIVS only has an effect on setuid/setgid executables, which was enough to avoid these issues (as setuid/setgid executables then were the only mean to launch processes with other credentials).

"Confused deputy" situations occur because of a conjunction of characteristics and mechanisms, assuming unprivileged chroot is allowed.  First, some executables trust the content of files at absolute paths (more often then not, regardless of other attributes such as their ownership and permissions; but even if they care, some exploitations are sometimes possible, just harder).  Second, in a chroot, these absolute paths translate to ones relative to the chroot's root, which means that an unprivileged user that can call chroot(2) could first build an ad-hoc hierarchy and chroot(2) to it, trying to fool other programs into processing data of its choosing (either because he directly wrote custom files whose content are used without further checks, or because he hardlinked some "interesting" file not belonging to him in place).  Third, any unprivileged user can hardlink a file not belonging to him but to which he has read access to into a directory where he has write access (this can be prevented by setting 'security.bsd.hardlink_check_uid' to 1).  Four, the setuid/setgid bits are a property of a file, and not of a directory entry (or, in other words, a path; I haven't given much more thought about this, but a priori it looks like a significant historical "mistake").  With third and four, an unprivileged user can hardlink some setuid executable like 'su' at the expected place in the hierarchy while "preserving" the setuid bit, and also provide its own version of '/etc/master.passwd' in the 'chroot'.  With two, the unprivileged user can fool 'su', which has characteristic one, to use as the password file the content he prepared, in effect bypassing the password check (the 'wheel' check can be bypassed similarly).

Nothing similar can happen currently with mac_do(4)/mdo(1).  The mdo(1) executable itself cannot be fooled, as its path is checked in the context of the current jail (regardless of the chroot).  mdo(1) does not read nor processes any "well-known" file to determine what to do.  Moreover, it can launch any executable of the unprivileged user's choosing *by design* (in particular, you can obtain a shell with it). mac_do(4)'s configuration is entirely in the kernel, so cannot be tampered with by filesystem manipulations.  mdo(1) cannot successfully transition to other credentials that are not approved by mac_do(4).  If you authorize some user to become root via MAC/do rules, you just get what you asked for, even in a chroot.  Everybody has to realize that mac_do(4)/mdo(1) currently gives more power in some ways than su(1) (in particular, no password is asked).

For the future, we would like 'mdo(1)' to grow a mode where it asks for a configurable password (initially, that of the calling user).  It is still unclear how this will materialize.  The only thing that is certain is that mdo(1) will never access the password database directly, as this requires to be root (and thus, a setuid executable).  We will either need some daemon fulfilling this task (as typically is done for applications in Capsicum's capability mode) or have this functionality in the kernel directly (along with the rules).  The first case could enable "confused deputy" situations if we are not careful, but it does not seem that difficult to avoid them.  The second case is obviously not subject to them at all.  So a priori it seems relatively likely that obeying P2_NO_NEW_PRIVS will never be a security requirement for mac_do(4)/mdo(1).  Additionally, that would introduce a logical exception that might limit some interesting practical uses.

In conclusion, there is currently no security flaw, and I don't think mac_do(4) should obey P2_NO_NEW_PRIVS unless we have some fundamental change in mac_do(4)/mdo(1) creating a different security environment where "confuse deputy" issues could actually occur.

Thanks and regards.

-- 
Olivier Certner