Change:
On NixOS 18.03: The assertion in question is this: If I'm able to to run outside the user namespace, why should I lose this capability inside a user namespace where I am root? Could the assertion be safely relaxed so that I can still drop privileges to inside my faked root environment (using sudo, after executing some commands as that fake root user)? CC
Note: this breaks with all security wrappers, not just sudo (just duped with in )
I get the same problem with steam-run. (also did this with sudo, same thing) Is there any way around this?
Sorry for the very late reply, the original Github issue email message must have got lost in my inbox :-/ It has been a while since I've looked at the wrapper code so I will need to familiarize myself first before I can provide an answer to the question asking if it is safe to relax the assertion. I'll see if I can spend some time doing that later today.
Pinging this: I'm trying to use from a Rails app run under systemd, and I trigger this same assertion.
Running the activation script in a chroot in the namespace creates wrappers with the right ownership (though it also reported other errors.) Unfortunately, in the case of trying to drop to regular user with or , I then get or . I believe it has to do with the kernel checking , but don't understand more than that yet. Edit: if you're already root in the namespace, you can also just bypass the wrapper (execute the path in e.g. ), but I ran into issues that are probably not strictly NixOS-specific, such as the above, or bailing because it's files are not owned by root as viewed from within the namespace.
Thank you for your contributions. This has been automatically marked as stale because it has had no activity for 180 days. If this is still important to you, we ask that you leave a comment below. Your comment can be as simple as "still important to me". This lets people see that at least one person still cares about this. Someone will have to do this at most twice a year if there is no other activity. Here are suggestions that might help resolve this more quickly: for maintainers and people that previously touched the related code and @ mention them in a comment. on the . on the on .
Still a problem, just hit this on 20.03 when trying to use Gitea with nullmailer.
I also get the same error, but when running a normal appImage (appimage runs fuse):
This issue has been mentioned on NixOS Discourse. There might be relevant details there:
The same happens to when run without root.
Bubblewrap which is what causes the check to fail. Unfortunately, this means that even if the check were removed, the functionality that requires setuid in the first place would just fail later down the line. The only way around this would be passing a message to a setuid binary on the host, as Flatpak does with .
I've the feeling that bubblewrap creates more troubles than it solves, because bubblewrap's goal is to jail apps while (as far as I understand) NixOs only uses bubblewrap to get user namespaces… Wouldn't it be easier for NixOs to simply write a short C code that only provides user namespaces?
Nix runs on more than just NixOS and many distros do not have user namespaces enabled for security reasons.
I seem to have this issue in (set as duplicate of this issue)
Duplicate with some more discussion and repro steps:
I did some looking into making a unprivileged userns container with unshare, and setuid is non-trivial to work there too. It will be possible to get setuid to a "fake" root, but getting "real" root inside the container doesn't seem possible, as "real" root no longer has uid 0 inside the container. This is probably by design - if a user can create an arbitrary container with an arbitrary host setuid binary, they can surely design the container such that they can escalate privileges (eg. by swapping out libc out from under the setuid binary.) I suppose we could look into a privileged mountns script that drops back to the capabilities of the calling user.
Just found this in a different situation: Can't we at least change the wrapper to check for an unshared user namespace and call directly the real binary? It's pretty easy to detect by looking at .
That will only work if we're root in the container, and only for things that a fake root can do (mounts inside the container, but not bind to port 80 or capture packets/trace from host processes, etc...) Correct me if I'm wrong, but the primary use case for userns on Nix is to emulate a FHS distro, but while preserving the user's environment and Nix functionality, is it not? I do have to wonder if we're simply using the wrong tool for that.
This issue has been mentioned on NixOS Discourse. There might be relevant details there:
Might be related: Was bitten by this when attempting to deploy a script that used sudo alongside with its dependency using . Found no way to make available to the bundled script. Only workaround I found was to adapt my script so that is not required (root access already available) and requiring my client to invoke the bundle using sudo (external to the bundle).
This issue has been mentioned on NixOS Discourse. There might be relevant details there:
Stumbled upon this as well. Thanks your comment lead me to this general workaround: This might be overly generic, or might need to be adapted in combination with other environments etc., but seems like a simple workaround. Edit: This version might be a bit cleaner: It simply removes all entries from (and it assumes that there are entries before and after it in ) and thus doesn't otherwise reorder entries. Edit: Of course this doesn't work for /, because the path is hard-compiled into the executable there...:
I've had a look into how Flatpak solves this issue and it seems like most apps use Polkit to authenticate. SteamVR (the main reason I'm here) uses it too. AFAIUI, it's an IPC-based privilege escalation mechanism which enables it to work in containers. It needs a privileged daemon on the host of course though. Perhaps we could build a sudo substitute on top of polkit?
This is quite annoying, it's not explained anywhere that basically doesn't work on NixOS due to these wrappers. At least this should be documented somewhere and the fix is:
I created an so that I could run some scripts, and I ran into this bug. The workaround that I came up with is to have the user run , and then, once the shell starts, run
see
This issue has been mentioned on NixOS Discourse. There might be relevant details there:
This issue has been mentioned on NixOS Discourse. There might be relevant details there:

Discussion:
<ins> #define _GNU_SOURCE </ins> #include <stdlib.h> #include <stdio.h> #include <string.h>

#include <syscall.h> #include <byteswap.h> <ins> // aborts when false, printing the failed expression </ins> #define ASSERT(expr) ((expr) ? (void) 0 : assert_failure(#expr)) <ins> // aborts when returns non-zero, printing the failed expression and errno #define MUSTSUCCEED(expr) ((expr) ? print_errno_and_die(#expr) : (void) 0) </ins> extern char **environ;

abort(); } <ins> static noreturn void print_errno_and_die(const char *assertion) { fprintf(stderr, "Call `%s` in NixOS's wrapper.c failed: %sn", assertion, strerror(errno)); fflush(stderr); abort(); } </ins> int get_last_cap(unsigned *last_cap) { FILE* file = fopen("/proc/sys/kernel/cap_last_cap", "r"); if (file == NULL) {

fprintf(stderr, "cannot readlink /proc/self/exe: %s", strerror(-self_path_size)); } <ins> unsigned int ruid, euid, suid, rgid, egid, sgid; MUSTSUCCEED(getresuid(&ruid, &euid, &suid)); MUSTSUCCEED(getresgid(&rgid, &egid, &sgid)); // If true, then we did not benefit from setuid privilege escalation, // where the original uid is still in ruid and different from euid == suid. int didnt_suid = (ruid == euid) && (euid == suid); // If true, then we did not benefit from setgid privilege escalation int didnt_sgid = (rgid == egid) && (egid == sgid); </ins> // Make sure that we are being executed from the right location, // i.e., `safe_wrapper_dir'.  This is to prevent someone from creating // hard link `X' from some other location, along with a false

ASSERT('/' == wrapper_dir[0]); ASSERT('/' == self_path[len]); <del> // Make *really* *really* sure that we were executed as // `self_path', and not, say, as some other setuid program. That // is, our effective uid/gid should match the uid/gid of // `self_path'. </del> <ins> // If we got privileges with the fs set[ug]id bit, check that the privilege we // got matches the one one we expected, ie that our effective uid/gid // matches the uid/gid of `self_path`. This ensures that we were executed as // `self_path', and not, say, as some other setuid program. // We don't check that if we did not benefit from the set[ug]id bit, as // can be the case in nosuid mounts or user namespaces. </ins> struct stat st; ASSERT(lstat(self_path, &st) != -1); <del> ASSERT(!(st.st_mode & S_ISUID) || (st.st_uid == geteuid())); ASSERT(!(st.st_mode & S_ISGID) || (st.st_gid == getegid())); </del> <ins> // if the wrapper gained privilege with suid, check that we got the uid of the file owner ASSERT(!((st.st_mode & S_ISUID) && !didnt_suid) || (st.st_uid == euid)); // if the wrapper gained privilege with sgid, check that we got the gid of the file group ASSERT(!((st.st_mode & S_ISGID) && !didnt_sgid) || (st.st_gid == egid)); // same, but with suid instead of euid ASSERT(!((st.st_mode & S_ISUID) && !didnt_suid) || (st.st_uid == suid)); ASSERT(!((st.st_mode & S_ISGID) && !didnt_sgid) || (st.st_gid == sgid)); </ins> // And, of course, we shouldn't be writable. ASSERT(!(st.st_mode & (S_IWGRP | S_IWOTH)));

out = machine.succeed(cmd_as_regular(cmd)).strip() assert out == expected, "Expected {0} to output {1}, but got {2}".format(cmd, expected, out) <ins> def test_as_regular_in_userns_mapped_as_root(cmd, expected): out = machine.succeed(f"su -l regular -c '${pkgs.util-linux}/bin/unshare -rm {cmd}'").strip() assert out == expected, "Expected {0} to output {1}, but got {2}".format(cmd, expected, out) </ins> test_as_regular('${busybox pkgs}/bin/busybox id -u', '${toString userUid}') test_as_regular('${busybox pkgs}/bin/busybox id -ru', '${toString userUid}') test_as_regular('${busybox pkgs}/bin/busybox id -g', '${toString usersGid}')

test_as_regular('/run/wrappers/bin/sgid_root_busybox id -g', '0') test_as_regular('/run/wrappers/bin/sgid_root_busybox id -rg', '${toString usersGid}') <ins> test_as_regular_in_userns_mapped_as_root('/run/wrappers/bin/suid_root_busybox id -u', '0') test_as_regular_in_userns_mapped_as_root('/run/wrappers/bin/suid_root_busybox id -ru', '0') test_as_regular_in_userns_mapped_as_root('/run/wrappers/bin/suid_root_busybox id -g', '0') test_as_regular_in_userns_mapped_as_root('/run/wrappers/bin/suid_root_busybox id -rg', '0') test_as_regular_in_userns_mapped_as_root('/run/wrappers/bin/sgid_root_busybox id -u', '0') test_as_regular_in_userns_mapped_as_root('/run/wrappers/bin/sgid_root_busybox id -ru', '0') test_as_regular_in_userns_mapped_as_root('/run/wrappers/bin/sgid_root_busybox id -g', '0') test_as_regular_in_userns_mapped_as_root('/run/wrappers/bin/sgid_root_busybox id -rg', '0') </ins> # We are only testing the permitted set, because it's easiest to look at with capsh. machine.fail(cmd_as_regular('${pkgs.libcap}/bin/capsh --has-p=CAP_CHOWN')) machine.fail(cmd_as_regular('${pkgs.libcap}/bin/capsh --has-p=CAP_SYS_ADMIN')) machine.succeed(cmd_as_regular('/run/wrappers/bin/capsh_with_chown --has-p=CAP_CHOWN')) machine.fail(cmd_as_regular('/run/wrappers/bin/capsh_with_chown --has-p=CAP_SYS_ADMIN')) <ins> # test a few "attacks" against which the wrapper protects itself machine.succeed("cp /run/wrappers/bin/suid_root_busybox{,.real} /tmp/") machine.fail(cmd_as_regular("/tmp/suid_root_busybox id -u")) machine.succeed("chmod u+s,a+w /run/wrappers/bin/suid_root_busybox") machine.fail(cmd_as_regular("/run/wrappers/bin/suid_root_busybox id -u")) </ins> ''; })