{U,G}IDs and Docker

If you’ve ever used Docker before (or any Linux-based containerization for that matter), you may be familiar with the mess it can leave on your filesystem. You start up your web server or database container and soon enough you have weird users like systemd-timesync owning your files. That can’t be right though: you were just running a web server! I’m going to delve into this topic for people who may be unfamiliar with Linux identity services and how containers interact with this model on the host machine, leading to unexpected (and potentially dangerous!) side-effects.

Linux Identity Overview

In Linux, access control is done primarily through two constructs: users (identified with their UID) and groups (identified through their GID). When accessing a file, the system checks if the effective UID matches the owner of the file. If it does, it allows access and modification to the file according to some permissions (read, write, and execute). If their UID does not match the owner of the file but they are a part of the group that owns the file, it will match the permissions set for the group of that file. Otherwise, a default permission set for all other users is applied.

In practice, this gives the following view of a file in a Linux filesystem:

decaby7e@envy > ls -al
total 56
drwxr-xr-x  9 decaby7e decaby7e 4096 Apr  6 15:42 .
drwxr-xr-x  7 decaby7e decaby7e 4096 Mar 18 17:53 ..
drwxr-xr-x  2 decaby7e decaby7e 4096 Aug  8  2021 archetypes
-rw-r--r--  1 decaby7e decaby7e   77 Aug  8  2021 config.toml
drwxr-xr-x  3 decaby7e decaby7e 4096 May 11 10:15 content
-rwxr-xr-x  1 decaby7e decaby7e  186 Apr  6 15:41 deploy.sh
drwxr-xr-x  8 decaby7e decaby7e 4096 May 31 23:16 .git
-rw-r--r--  1 decaby7e decaby7e   35 Mar 10 08:29 .gitignore
-rw-r--r--  1 decaby7e decaby7e    0 Mar 10 08:28 .hugo_build.lock
drwxr-xr-x  6 decaby7e decaby7e 4096 Nov  5  2021 layouts
drwxr-xr-x 11 decaby7e decaby7e 4096 Apr  6 15:42 public
-rw-r--r--  1 decaby7e decaby7e  262 Aug  8  2021 README.md
drwxr-xr-x  3 decaby7e decaby7e 4096 Aug  8  2021 resources
drwxr-xr-x  6 decaby7e decaby7e 4096 Mar 10 08:27 static
-rw-r--r--  1 decaby7e decaby7e  419 Aug  8  2021 TODO.md

The column of characters on the left shows the owner, group, and other permissions as a list of characters, with each empty space representing the lack of that type of permission and the presence of a character representing the possession of that permission.

My username and group (decaby7e) is displayed as the primary user and group owner respectively of these files. However, the system actually uses the UID and GID to store this information, and thus it is possible for these names to change without the owner and group of the file changing. In my case, my UID/GID is 1000/1000.

What does the container see?

One of the ways that Docker isolates containers from the host machine is through the use of kernel namespaces. Some of these namespaces, that are not enabled by default, are the user and group namespaces. Thus, when you run ls in a container on a directory that has been bind-mounted into that container, you will see the same exact owners as a user on the host would see. Additionally, when you run as a UID (which defaults to the root UID of 0) in a container, the side-effects you leave on the filesystem in the container are the same as the ones you would leave on the host (e.g. owning a file in a container as root will leave a corresponding change of the file’s owner on the host. This is because they are the same file and the bind-mount does not do any remapping of UIDs from users running in the container).

Why is this a problem?

  1. Certain vulnerabilites (e.g. CVE-2019-5736) are able to take advantage of the lack of isolation that is left from not using seperate UID namespaces to achieve privilage escalation on the host machine from compromised containers.

  2. Additionally, if your filesystem is not secured properly, it can lead to inadvertent data leakage on the host machine from container users changing file ownership in unpredictable ways (a container user changes the ownership of a file that allows a system user on the host machine to access files it otherwise shouldn’t be able to).

Solution: User Namespaces

The solution is to use user namespaces. It used to be the case (2020 and before) that this was not widely supported and even to date lots of people using Docker are still using privileged containers. However, tools like Podman allow you to run rootless containers and I believe Kubernetes also allows this.

Takeaways

I learned some valuable lessons about ownership and user permissions from researching this topic: