Overv/outrun

Feature request: drop privileges after chroot

Opened this issue ยท 8 comments

Example of current behavior:

$ outrun njodell@outrun-vm whoami
fuse: warning: library too old, some operations may not not work
root

I'd like the program I'm running to execute as the same user that I logged in as.

There are a number of tools that exist that could help with this including userchroot, which allows a system administrator to configure a system to allow unprivileged chrooting into a set of preconfigured locations.

Overv commented

I'm currently looking into alternatives that don't require root. The perfect approach for me meets these requirements:

  • Relies on tools that are already available on most Linux installations or very easy to install through standard package managers.
    • outrun should be as effortless as possible to install so you can quickly get started on a (fresh) remote system. I'm already working towards making this easier by implementing a fallback to FUSE 2.x (which is more widely available) and investigating ways to have outrun install itself on the remote system.
  • Requires no manual setup.
    • userchroot looks cool, but requires work to set up on a system.
  • Works for as much software as possible.
    • fakechroot, for example, requires the program to dynamically link with libc.

It strikes me that there are two feature requests being discussed here, and the second is much more ambitious than the first:

  1. Don't run the user's process as root. You still need root to set up the chroot, but not afterwards. This is helpful to prevent accidental damage, but not helpful against malicious attackers.

    Example: Imagine I use outrun to run a poorly-coded shell script. Running as a non-root user would limit the amount of damage it does.

  2. Don't require the user to be root (or have sudo privileges) at all. In order for this feature to be useful, it needs to be set up in a way where the user can't use the chroot mechanism to gain root. It's not clear to me how to do that. (If the user can supply /etc/sudoers from their own system, can't they just give themselves the power to be root? Not sure.)

Overv commented

You're right that (1) is a sensible first step towards (2). I think that it is possible to safely implement (2) through unshare, as described here.

You wan't to rely on user namespaces here. For instance:

$ docker export $(docker create alpine) | tar -xC rootfs
$ unshare -r chroot ./rootfs whoami
root

doesn't require you to be root to use chroot by mapping the current user to root inside the unshare process. However, you are only root in the context of that process, so you can't remove root-owned files or anything:

$ mkdir rootfs/test-permission
$ echo "hello" > rootfs/test-permission/file.txt
$ sudo chown -R root:root rootfs/test-permission

Can't remove the folder outside of unshare:

$ rm -rf rootfs/test-permission/
rm: cannot remove 'rootfs/test-permission/file.txt': Permission denied

And can't remove it inside of unshare being mapped to root:

$ unshare -r chroot rootfs rm -rf /test-permission
rm: can't remove '/test-permission/file.txt': Permission denied

Thanks @haampie!

Looking around on the net, I found a Python implementation โ€‹of bindings to the unshare(2) syscall. It's surprisingly simple, with only 50 lines of source code.

On a related note, I was discussing Outrun on LWN recently, in the context of unprivileged chroot. One of the commenters provided a detailed blueprint of how you might implement this:

That doesn't require an unprivileged chroot; you could do something like:

logfile = fopen("foo.log", "a");
sqlite3_open("foo.db", &db);
sprintf(rootdir, "/run/user/%d/my-jail", getuid());
chdir(rootdir);
unshare(CLONE_NEWUSER);
chroot(".");
caps = cap_get_proc();
cap_clear(caps);
cap_set_proc(caps);

(Credit to floppus on LWN.)

Edit: There is a potential complication here, though. Debian/Ubuntu carry a nonstandard patch which adds a the sysctl knob kernel.unprivileged_userns_clone. That controls whether an unprivileged process like outrun can create a user namespace.

Rootless docker has some overview of different systems: https://docs.docker.com/engine/security/rootless/#distribution-specific-hint they recommend

Add kernel.unprivileged_userns_clone=1 to /etc/sysctl.conf (or /etc/sysctl.d) and run sudo sysctl --system

only for Debian and Arch, but not for Ubuntu, openSUSE or CentOS

gnull commented
  1. Don't run the user's process as root. You still need root to set up the chroot, but not afterwards. This is helpful to prevent accidental damage, but not helpful against malicious attackers.

This is also required for some tools to work. For example, the Haskell build tool Stack refuses to proceed if it sees that ~/.stack is not owned by the user running Stack. (Which makes sense: you don't want root to create files owned by him in your home dir. As @nickodell notes, it prevents accidental damage as well.)