If unprivileged userns are *believed* to be disabled (such as with "kernel.unprivileged_userns_clone = 0"), Lix would previously *give up* on trying to use a user namespace before actually trying it, even if, in cases such as unprivileged_userns_clone, it would actually be allowed since Nix has CAP_SYS_ADMIN when running as daemon. (see, e.g. https://github.com/archlinux/linux/commit/25d4709a4fc0e4f3b432c24b60dd508fb84f0cb2) We changed it to actually try it first, and then diagnose possible causes, and also to be more loud about the whole thing, using warnings instead of debugs. These warnings will only print on the first build run by the daemon, which is, tbh, eh, shrug. This is what led to us realizing that no-userns was a poorly exercised condition. Change-Id: I8e4f21afc89c574020dc7e89a560cc740ce6573a
99 lines
2.7 KiB
C++
99 lines
2.7 KiB
C++
#if __linux__
|
|
|
|
#include "namespaces.hh"
|
|
#include "util.hh"
|
|
#include "finally.hh"
|
|
|
|
#include <sys/mount.h>
|
|
|
|
namespace nix {
|
|
|
|
static void diagnoseUserNamespaces()
|
|
{
|
|
if (!pathExists("/proc/self/ns/user")) {
|
|
warn("'/proc/self/ns/user' does not exist; your kernel was likely built without CONFIG_USER_NS=y");
|
|
}
|
|
|
|
Path maxUserNamespaces = "/proc/sys/user/max_user_namespaces";
|
|
if (!pathExists(maxUserNamespaces) ||
|
|
trim(readFile(maxUserNamespaces)) == "0")
|
|
{
|
|
warn("user namespaces appear to be disabled; check '/proc/sys/user/max_user_namespaces'");
|
|
}
|
|
|
|
Path procSysKernelUnprivilegedUsernsClone = "/proc/sys/kernel/unprivileged_userns_clone";
|
|
if (pathExists(procSysKernelUnprivilegedUsernsClone)
|
|
&& trim(readFile(procSysKernelUnprivilegedUsernsClone)) == "0")
|
|
{
|
|
warn("user namespaces appear to be disabled for unprivileged users; check '/proc/sys/kernel/unprivileged_userns_clone'");
|
|
}
|
|
}
|
|
|
|
bool userNamespacesSupported()
|
|
{
|
|
static auto res = [&]() -> bool
|
|
{
|
|
try {
|
|
Pid pid = startProcess([&]()
|
|
{
|
|
_exit(0);
|
|
}, {
|
|
.cloneFlags = CLONE_NEWUSER
|
|
});
|
|
|
|
auto r = pid.wait();
|
|
assert(!r);
|
|
} catch (SysError & e) {
|
|
warn("user namespaces do not work on this system: %s", e.msg());
|
|
diagnoseUserNamespaces();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}();
|
|
return res;
|
|
}
|
|
|
|
bool mountAndPidNamespacesSupported()
|
|
{
|
|
static auto res = [&]() -> bool
|
|
{
|
|
try {
|
|
|
|
Pid pid = startProcess([&]()
|
|
{
|
|
/* Make sure we don't remount the parent's /proc. */
|
|
if (mount(0, "/", 0, MS_PRIVATE | MS_REC, 0) == -1)
|
|
_exit(1);
|
|
|
|
/* Test whether we can remount /proc. The kernel disallows
|
|
this if /proc is not fully visible, i.e. if there are
|
|
filesystems mounted on top of files inside /proc. See
|
|
https://lore.kernel.org/lkml/87tvsrjai0.fsf@xmission.com/T/. */
|
|
if (mount("none", "/proc", "proc", 0, 0) == -1)
|
|
_exit(2);
|
|
|
|
_exit(0);
|
|
}, {
|
|
.cloneFlags = CLONE_NEWNS | CLONE_NEWPID | (userNamespacesSupported() ? CLONE_NEWUSER : 0)
|
|
});
|
|
|
|
if (pid.wait()) {
|
|
debug("PID namespaces do not work on this system: cannot remount /proc");
|
|
return false;
|
|
}
|
|
|
|
} catch (SysError & e) {
|
|
debug("mount namespaces do not work on this system: %s", e.msg());
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}();
|
|
return res;
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|