Open nickodell opened 4 years ago
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.
I'm currently looking into alternatives that don't require root. The perfect approach for me meets these requirements:
It strikes me that there are two feature requests being discussed here, and the second is much more ambitious than the first:
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.
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 runsudo sysctl --system
only for Debian and Arch, but not for Ubuntu, openSUSE or CentOS
- 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.)
Example of current behavior:
I'd like the program I'm running to execute as the same user that I logged in as.