May 4, 2021

On Linux (Unix?) You Can Make Up a User ID On The Fly

Recently I’ve been working with Docker, launching non-root containers. Mostly because I mounted files into the container I didn’t want every file to be owned by root. I use commands like this: docker run --user="$(id --user):$(id --group) -v $PWD:/mounted-directory <image> <comand>. This way the processes inside the container use the same user id as the current user, so any file created inside the container on the mounted directory can easily be read and changed.

What happens if you pick a random user id, one which doesn’t exist? Try it:

docker run --detach --rm --user=42421:42421 busybox sleep 10
> 542d4de888751e6d067ddd84bf3b9f21698e2af4a0a90844b586c92a3d47d6d
ps aux | grep sleep
> 42421      88497  0.0  0.0   1308     4 ?        Ss   14:28   0:00 sleep 10
> roman      88539  0.0  0.0   6404  2292 pts/11   S+   14:28   0:00 grep --color=auto sleep

As you see, no error or warning. The process runs under user 42421. You didn’t have to create a user. The only odd thing is that you see the raw user-id instead of a name. So, is this Docker magic sauce? [1]

On The Fly Users
Figure 1. On The Fly Users

Outside Docker: Setreuid/Setregid

Let’s try this outside Docker. I’m using this small test file which prints the user id and sleeps for bit. Mark the file as executable.

/tmp/test.sh:
#!/usr/bin/env bash
id
sleep 10

For sudo, it doesn’t work. It wants to know the user:

sudo wants an existing user
chmod +x /tmp/test.sh
sudo -u 42421 /tmp/test.sh
sudo: unknown user: 42421
sudo: error initializing audit plugin sudoers_audit

Ok, lets go down to metal and use the Linux APIs (insert some GNU/Posix/Unix joke here yourself).

launch-setuid.c:
#include <stdio.h>
#include <unistd.h>

int main(int argc, char** argv) {
    int err = setregid(42421, 42421);
    if(err){
        perror("Failed to switch to other group id");
        return 1;
    }
    err = setreuid(42421, 42421);
    if(err){
        perror("Failed to switch to other user id");
        return 1;
    }

    execv("/tmp/test.sh", argv);

    perror("Failed to launch /tmp/test.sh");
    return 1;
}

Then we compile, run our program and check the user id. We compile it and run it.

First try:
gcc launch-setuid.c -o launch-setuid
./launch-setuid
> Failed to switch to other user id: Operation not permitted

We’ll get an error. That’s because we can’t just willy-nilly switch to another user. Otherwise, there would be no security boundary. We have to run the user switching as root: [2][3]

Launch with root, switches to user:
$ gcc launch-setuid.c -o launch-setuid
$ sudo ./launch-setuid &
> uid=42421 gid=42421 groups=42421,0(root)
$ ps aux | grep test.sh
> 42421     106250  0.0  0.0   7264  2824 pts/7    S    18:31   0:00 bash /tmp/test.sh
> roman     106286  0.0  0.0   6408  2368 pts/7    S+   18:31   0:00 grep --color=auto test.sh

Tada, we did it. That process runs as user 42421.

Weirdness in 'made-up' User Land

Because the user lacks a proper setup, you’ll get some weirdness. Some utilities might not find the user name or complain. For example, bash in docker will show this:

Bash confused by the missing username:
$ docker run -it --rm --user 42000:42000 ubuntu:20.04
> groups: cannot find name for group ID 42000
> I have no name!@7dd968f5308a:/$

You may need to define some things in that environment for some programs to work. Like, define the USER and HOME environment variables.

Define USER and HOME
USER=virtuser
HOME=/tmp/virtuser

Or in Docker you might go further and mount a /etc/passwd and /etc/group into the container. So you can generate some user name on the fly:

$ echo 'root:x:0:0::/root:/bin/bash' > ./virtual-passwd
$ echo 'virtualuser:x:42000:42000::/home/virtualuser:/bin/bash' >> ./virtual-passwd
$ echo 'root:x:0:root' > ./virtual-group
$ echo 'virtualuser:x:42000:' >> ./virtual-group
docker run -it --rm --user 42000:42000 \
    -v $PWD/virtual-passwd:/etc/passwd \
    -v $PWD/virtual-group:/etc/group \
    ubuntu:20.04

There are probably more consequences and odd behaviors. However, many basic processes which do not have some user interaction will run happily.

Happy Adhoc User Creating ;)

That’s it. I’m not a Linux/Unix guru to tell you much more or give any insights on deeper consequences =)

I also don’t know if this applies to all Unix-ish operating systems. I tried it on Linux and FreeBSD and it worked. I didn’t test on other Unix systems like illumos or MacOS


1. I ignored Linux user namespaces. I’m not familiar enough to make any comment about it
2. I guess you need the right capability, let’s ignore such details: https://man7.org/linux/man-pages/man7/capabilities.7.html
3. We’ll also ignore the setuid bit here
Tags: Unix Clojure Development