Adam Dobos
2023-10-31

What you nee...

This document is currently work in progress.

User namespaces

Note: The following examples assume that your Linux machine is configured to allow unprivileged user namespaces, i.e. non-root users are able to create new user namespaces. Some might consider it a security issue to leave this enabled (more on this later) so it’s possible that it is disabled for you. If that’s that case you should use the root user if you want to test the commands out.

1
2
3
4
5
6
7
user@localhost:~$
user@localhost:~$ unshare -U
nobody@localhost:~$
nobody@localhost:~$ whoami
nobody
nobody@localhost:~$ id
uid=65534(nobody) gid=65534(nogroup) groups=65534(nogroup)

Currently we don’t have any users mapped in our new namespace so we are the user nobody with the uid 65534.
We can validate that there are no uid and gid mappings for this process by checking corresponding uid_map and gid_map:

1
2
3
4
nobody@localhost:~$ cat /proc/self/uid_map

nobody@localhost:~$ cat /proc/self/gid_map

Creating the uid and gid mappings in the new namespace

When we create a new user namespace we can specify the uid and gid mappings only once. After it is set, it cannot be overwritten. To do this we can use the newuidmap and newgidmap commands.

The syntax is:

1
newuidmap [PID] [uid in new namespace] [uid in parent namespace] [count]

For example:

1
newuidmap 123 0 1000 1

This command will create a single uid mapping for the process with pid 123 and will map the root user (with id 0) inside the user namespace of the process to uid 1000 in the parent’s user namespace. If we were to change the count parameter to 2 (newuidmap 123 0 1000 2), we would map two consecutive uids: 0 -> 1000 and 1 -> 1001. You could also specify multiple ranges if you want to map ids that are not contiguous, e.g. newuidmap 123 0 1000 2 2 2000 2 will do the following mappings: 0 -> 1000, 1 -> 1001, 2 -> 2000 and 3 -> 2001.

If we were to try to create a new uid_map from this newly created namespace it would fail obviously because we are an unmapped user, we don’t have the permissions to do so:

1
2
nobody@localhost:~$ newuidmap $$ 1000 1000 1
newuidmap: uid range [1000-1001) -> [1000-1001) not allowed

So to do this get the process id, and inside an other terminal issue the newuidmap command:

1
2
3
4
nobody@localhost:~$ echo $$
123
# on another terminal
user@localhost:~$ newuidmap 123 $(id -u) $(id -u) 1

If the command succeeded and you head back to the original terminal you will… see nothing new in particular. Your shell prompt will still say you are nobody. But if you check the uid_map of the process you will see that it was applied correctly:

1
2
nobody@localhost:~$ echo /proc/self/uid_map
1000 1000 1

In fact if you check your username or id, you will also find that the mapping took effect. When you applied the new uid_map for the user namespace you automatically became the first available id in the mapping (which is your id outside the user namespace, in my case 1000).

1
2
3
4
nobody@localhost:~$ whoami
user
nobody@localhost:~$ id
uid=1000(user) gid=65534(nogroup) groups=65534(nogroup)

So it’s simply the case of your shell prompt not updating, but you can force it by reentering your shell via exec $SHELL:

1
2
nobody@localhost:~$ exec $SHELL
user@localhost:~$

Just to emphasize again, you can create this new uid map only once. If you try to write it more than once, you will get an operation not permitted error. So if you want to map multiple ranges, you must specify every range in the first call.

1
2
3
4
5
6
# BAD
user@localhost:~$ newuidmap 123 1000 1000 1
user@localhost:~$ newuidmap 123 2000 2000 1
newuidmap: write to uid_map failed: Operation not permitted
# GOOD
user@localhost:~$ newuidmap 123 1000 1000 1 2000 2000 1

The newgidmap command and generally group id mappings work exactly the same way.

But you might be wondering:

  • What is this exactly useful for?
  • Can you do arbitrary mappings or are there limitations?

We will keep digging deeper to discover the answer for these questions.

Mapping user ids