Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for user and group ID mappings #468

Open
dgerber opened this issue Dec 29, 2021 · 15 comments
Open

Support for user and group ID mappings #468

dgerber opened this issue Dec 29, 2021 · 15 comments

Comments

@dgerber
Copy link

dgerber commented Dec 29, 2021

Add options --uid-map and --gid-map to map multiple users between child and parent namespace: bwrap --uid-map '<uid_in_child_ns> <loweruid_in_parent_ns> <count> ...' ...

Relying on shadow newuidmap utility, or implementing the same whitelisting logic from /etc/subuid (/etc/subgid).

This would allow for (non-fakeroot) chown calls in build containers.

Recently added in util-linux as unshare --map-users=....

@justinfx
Copy link

justinfx commented Feb 22, 2022

Any movement on this? I've been exploring bubblewrap for a sandboxed build env and seem to also need this feature for chown purposes.

@smcv
Copy link
Collaborator

smcv commented Feb 22, 2022

If you want to propose an implementation, that would be the most likely way to get this feature.

Note that bubblewrap is setuid root on some systems, so any new feature would have to be implemented in a way that does not create a security vulnerability - either by implementing it in a way that is safe for a setuid bubblewrap, or by automatically disabling the feature when setuid.

@justinfx
Copy link

Fair enough, and thanks for the reply. I've only started experimenting with the project on an Ubuntu system, without setuid. So I am not familiar with the code or an approach to an implementation yet.
If I manage to figure that out before someone else, I can follow up.

@justinfx
Copy link

It turns out that the example demos/userns-block-fd.py got me where I needed, since it provides the hooks into running newuidmap and newgidmap

@digitalsignalperson
Copy link

I'm also interested in (non-)fakeroot within containers. I'm imagining something like

UID inside namespace UID outside namespace
0 10000
1000 1000

So that inside the container we have two levels of security. The inside container "root" could install packages, but the inside container "1000" user cannot manipulate the inside container "root" files. And outside the container 1000 maps to 1000 as usual, and "root" to 10000 which is still unprivileged.

@justinfx I'm curious where you got to with the demos/userns-block-fd.py demo code. Did you manage to map to any uid/gid other than the current user/group (e.g. 1000)? I can't get it to work with a loweruid other than 1000.

In the demo code we set

    subprocess.call(["newuidmap", child_pid, "0", str(os.getuid()), "1"])
    subprocess.call(["newgidmap", child_pid, "0", str(os.getgid()), "1"])

and inside the container we appear as uid 0 (root) which maps to 1000 (current user) outside the container.

I can change the "0" to any number and it seems to work. But I cannot change the 2nd last arg to map to anything other than 1000 (eg 10000+ which should be valid per /etc/subuid) or I get the same error mentioned in #518 bwrap: Creating newroot failed: Value too large for defined data type. My current user has 10000:65536 ranges in /etc/subuid and /etc/subgid.

@smcv
Copy link
Collaborator

smcv commented Jan 2, 2024

If bwrap is not setuid root, then there are severe restrictions on the uid/gid mapping that the kernel will allow it to create (see user_namespaces(7) for the full details). Some bwrap features do not and probably cannot work (safely, or at all) when it is setuid root, and making it setuid root comes with a risk of new root privilege escalation vulnerabilities, so I would be reluctant to add new bwrap features that require it to be setuid root.

The only reason why newuidmap and newgidmap are allowed to break the kernel's restrictions is that they are setuid root. They use /etc/subuid, /etc/subgid to allow a sysadmin to configure what they will allow users to do. However, there is nothing special about those files from the kernel point of view: just because a uid range is mentioned in /etc/subuid, that doesn't mean the kernel will allow unprivileged programs to use it.

Inside a bwrap sandbox, the setuid bit on newuidmap and newgidmap does not have any effect, because processes inside the sandbox have the NO_NEW_PRIVS flag. This is another kernel requirement: if we did not set that flag, then an unprivileged bwrap would not be allowed to create new user namespaces.

If bwrap does not do what you want, then using (or writing) a different tool that is not bwrap is always an option.

@justinfx
Copy link

justinfx commented Jan 2, 2024

@digitalsignalperson it's been almost 2 years since I had been playing in this space, so I had to review my prototype code. I was able to get what I needed from the demo code, with the difference being that I was only trying to ever map to the same internal and external uid and gid. And for the newgidmap count I was using 65000. My notes say the downside is that I did require host provisioning of the /etc/sub{uid,gid} files to ${USER}:1000:65536.
I had started some experiments trying to have my own setuid script that would dynamically write out a subgid file for the real user and then sudo unshare bind mount it into /etc/subgid before then launching bwrap.
Ultimately my goal would be to not have any special extra setuid binaries to have to deploy with the solution, not have to provision the host subuid and subgid files, and to rely on only bwrap to do the right thing for me.

@rusty-snake
Copy link
Contributor

FWIW

$ unshare -r --map-users=auto bwrap --dev-bind / / cat /proc/self/uid_map
         0       1000          1
         1     100000      65536

@digitalsignalperson
Copy link

digitalsignalperson commented Jan 3, 2024

Cool thanks for all the insightful responses!

@rusty-snake working off your example I was able to get su and sudo working inside a bwrapped bash. For groups --map-groups is needed (or just --map-auto to map both users and groups). To test changing users I bind a new /etc with the desired passwd/shadow/gshadow which are owned by 1000 outside the namespace.

unshare --map-root-user --map-auto bwrap --dev-bind / / --bind /tmp/fakeroot/etc /etc bash

For other bwrap args, we can't use --unshare-all or --unshare-user, so trying with everything else:
Currently when using --unshare-ipc --unshare-pid --unshare-net --unshare-uts --unshare-cgroup --new-session :

  • su <user> doesn't work with fish default shell
  • su <user> -c bash works
  • sudo -u <user> bash works

And here's the mapping like I was originally going for:

$ unshare --setuid 0 --setgid 0 --map-current-user --map-auto \
    bwrap --dev-bind / / --bind /tmp/fakeroot/etc /etc \
    --unshare-ipc --unshare-pid --unshare-net --unshare-uts --unshare-cgroup --new-session \
    cat /proc/self/uid_map
      1000        1000          1
         0      100000       1000
      1001      101000      64536

Without --setuid 0 --setgid 0 it won't let you both map a range and the current user.
And if you try --setuid 1000 --setgid 1000 you'll only get 1000:1000:1 mapped.

Now with chown -R <100k user>:<100k group> /tmp/fakeroot/etc, in the bwrap bash I can sudo -u <user> bash, to switch to uid 1000. I can obviously exit to go back to the 0:100000 root, but as uid 1000 if I run sudo anything it'll say

sudo: The "no new privileges" flag is set, which prevents sudo from running as root.
sudo: If sudo is running in a container, you may need to adjust the container configuration to disable the flag.

Ah that NO_NEW_PRIVS . I was hoping to achieve being user 1000:1000 and using sudo to install packages as user 0:100000. Maybe this just needs an outer container with --map-current-user and a nested inner container that maps root to something else and a stub sudo calls. actually idk

@smcv
Copy link
Collaborator

smcv commented Jan 3, 2024

I had started some experiments trying to have my own setuid script that would dynamically write out a subgid file for the real user and then sudo unshare bind mount it into /etc/subgid before then launching bwrap. Ultimately my goal would be to not have any special extra setuid binaries to have to deploy with the solution, not have to provision the host subuid and subgid files, and to rely on only bwrap to do the right thing for me.

If this was possible, then that would be a security vulnerability in something. The kernel is intentionally imposing a security restriction. Provisioning the host subuid and subgid files, in conjunction with the setuid newuidmap and newgidmap executables, is exactly the way for a sysadmin to relax that security restriction. Installing newuidmap and newgidmap as setuid is exactly the way for a sysadmin to say "I consider these tools' security model to be valid".

If some component allowed unprivileged users to bypass the security restriction without the sysadmin's consent, then that would imply that there is a security vulnerability somewhere in the stack, either an implementation vulnerability or a design flaw. I would recommend that you do not spend your valuable time on constructing something that we would have to reject because it would be a security vulnerability.

I was hoping to achieve being user 1000:1000 and using sudo to install packages as user 0:100000

It is not possible to do this in an unprivileged user namespace, because that would require uid 1000 and uid 0 to both exist inside your sandbox, and user namespaces created without using root privileges are not allowed to do that.

If you want to have more than one uid inside a container created by an unprivileged user, then you need something that uses subuid/newuidmap to use a sysadmin-approved uid range, such as Podman or Incus.

If you are talking about "using sudo" and "installing packages" then bwrap is probably the wrong tool for what you are aiming to achieve, and you would probably be better off using Podman or Incus anyway.

@smcv
Copy link
Collaborator

smcv commented Jan 3, 2024

Or, if you insist on not using something like Podman or Incus, you could construct your own elaborate containerization solution using unshare --map-users (which also uses newuidmap); but if you are doing that, at that point you no longer need bwrap.

@GalaxySnail
Copy link

GalaxySnail commented Jan 3, 2024

Is it possible for bwrap to call newuidmap before setting NO_NEW_PRIVS, similar to what unshare does?

@rusty-snake
Copy link
Contributor

Technically yes, but I doubt that such a feature will ever get accepted. Furthermore you can already do this by wrapping bwrap in a userns create by unshare, using --userns+--userns2 or using --userns-block-fd`. And before/after NNP does not matter.

Nevermind, you will not get sudo to work (=switch uid) inside bwrap. You either need to run a second instance of bwrap with the same mount, or nsenter it. Or you usecase does not call for bwrap but for e.g. podman.

@digitalsignalperson
Copy link

It is not possible to do this in an unprivileged user namespace, because that would require uid 1000 and uid 0 to both exist inside your sandbox

you will not get sudo to work (=switch uid) inside bwrap

Here's a full demo that I think is the opposite of these assertions. This example shows at least one way sudo works inside bwrap (started from an unprivileged user), and we can switch between uid 0 and 1000 which both exist in the sandbox. I'm curious for opinions on if this is expected behaviour, a bug/vulnerability, or it has some other major caveats to consider.

Let's do the whole experiment in a clean VM from scratch since we'll be copying/chowning the contents of /etc.

incus launch images:archlinux archbwrap --vm -c security.secureboot=false
incus exec archbwrap bash

Inside this VM bash, setup:

pacman -S bubblewrap --noconfirm

# Add user 1k (uid/gid=1000) and user 100k (uid/gid=100000)
groupadd -g 1000 1k
useradd -m 1k -u 1000 -g 1000
groupadd -g 100000 100k
useradd -m 100k -u 100000 -g 100000

# set uid/gid mappings
echo "1k:100000:65536" > /etc/subuid
echo "1k:100000:65536" > /etc/subgid

# allow uid 1000 for sudo
echo "1k ALL=(ALL) ALL" > "/etc/sudoers.d/10-1k"

# set a password for uid 1000
echo "1k:1k" | chpasswd

# set up our experiment: make a copy of /etc owned by user 100k
mkdir /tmp/fakeroot
cp -r /etc /tmp/fakeroot
chown -R 100k:100k /tmp/fakeroot/etc

So if we bind this /tmp/fakeroot/etc to /etc inside bwrap, the 0:100000 root user (per unshare --setuid 0 --setgid 0 --map-current-user --map-auto) can read the required files for sudo (passwd, sudoers, etc.). Obviously in a real application we would not copy the hosts /etc but it would be created from scratch (which is essentially what we are doing with the VM).

Now change user to 1k to start our experiment. I'll show the full terminal output for clarity of switching users

[root@archlinux ~]# su 1k
[1k@archlinux root]$ unshare --setuid 0 --setgid 0 --map-current-user --map-auto \
    bwrap --dev-bind / / --bind /tmp/fakeroot/etc /etc \
    --unshare-ipc --unshare-pid --unshare-net --unshare-uts --unshare-cgroup --new-session \
    bash
bash: cannot set terminal process group (1): Inappropriate ioctl for device
bash: no job control in this shell
[root@archlinux ~]# cat /proc/self/uid_map
      1000       1000          1
         0     100000       1000
      1001     101000      64536
[root@archlinux ~]# id
uid=0(root) gid=0(root) groups=0(root)
[root@archlinux ~]# touch /tmp/bwrap-im-root

still inside bwrap, now let's sudo to user 1k

[root@archlinux ~]# sudo -u 1k bash
bash: cannot set terminal process group (1): Inappropriate ioctl for device
bash: no job control in this shell
[1k@archlinux ~]$ id
uid=1000(1k) gid=1000(1k) groups=1000(1k)
[1k@archlinux ~]$ touch /tmp/bwrap-im-1k

That works. Now try to sudo back to root

[1k@archlinux ~]$ sudo su
sudo: The "no new privileges" flag is set, which prevents sudo from running as root.
sudo: If sudo is running in a container, you may need to adjust the container configuration to disable the flag.
[1k@archlinux ~]$ su
Password: 
su: Authentication failure

no dice. exit back to root

[1k@archlinux ~]$ exit
exit
[root@archlinux ~]#

here's the files we created from this perspective

[root@archlinux ~]# ls -l /tmp/bwrap*
-rw-r--r-- 1 1k   1k   0 Jan  3 18:26 /tmp/bwrap-im-1k
-rw-r--r-- 1 root root 0 Jan  3 18:25 /tmp/bwrap-im-root

and now exit back to the VM and look at the same files

[root@archlinux ~]# exit
exit
[1k@archlinux root]$ ls -l /tmp/bwrap*
-rw-r--r-- 1 1k   1k   0 Jan  3 18:26 /tmp/bwrap-im-1k
-rw-r--r-- 1 100k 100k 0 Jan  3 18:25 /tmp/bwrap-im-root

In summary:

  • with unshare & bwrap we entered a namespace as uid 0:100000
  • we can read/write files that will have uid 100000 on the host
  • we can use sudo to change from uid 0:100000 to uid 1000:1000
  • we can read/write files that will have uid 1000 on the host
  • we can't use sudo to change from uid 1000:1000 to uid 0:100000

@rebx
Copy link

rebx commented May 6, 2024

Hi,

I'm running into the same error as well

bwrap: Creating newroot failed: Value too large for defined data type

If I were to patch this (for academic purposes) so that I wouldn't have to need elevated privileges to make this work, where do you think should I start looking? I would appreciate it if you can let me know where I can start my research at.

Many thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants