Docker symlink security hole

On 28th May a senior software engineer Aleksa Sarai discovered a security hole within Docker which can grant an attacker read and write access to any file on the host machine regardless of its permissions.

TOCTOU vulnerability

TOCTOU stands for time-of-check to time-of-use and is a race condition vulnerability resulting in inconsistent state of a system, usually a file system. When two or more processes operate on one resource, some of them may perform modifications based on an incorrect past state of that resource.

Consider a database of email recipients. One possible email would be sent to users who haven’t logged in to the app for at least 30 days. Let’s assume a job performs this task once a day, first selecting a set of users who satisfy the criterion (last login more than 30 days before), then resolving an email template, maybe calling an external service for some additional data, and then sending the emails to the selected users. While the server was resolving the template, Bob, who hasn’t been using the app for a while, logs in. He would still receive the email.

In general, despite their apparent simplicity, TOCTOU problems are usually very hard to solve when both consistency and performance are needed.

Bug location

The Docker vulnerability affects the FollowSymlinkInScope function used by many docker tools, notably docker cp that copies files between containers and the host machine. The purpose of FollowSymlinkInScope is to take a given symlink pointing to some path and safely resolve it as though the process was inside a container.

Take for example docker cp /path1 container1:/path2/sym1 executed on the host. It would copy a file from /path1 to the location within container1 to which sym1 points, say /loc1. /loc1 only makes sense within the container; it does not refer to /loc1 on the host machine. Hence sym1 has to be resolved to something else; to /loc1 at some hidden location $\ell$ on the host that Docker process uses internally as container1’s insides.

The problem with FollowSymlinkInScope is that after the symlink is resolved, it is passed around a little more before it is actually used - in the case of docker cp to do the copying. The usage phase assumes the path is safe to use since the resolution has been done in a safely manner before.

But it can happen that an attacker adds a symlink component to the path after the resolution but before the actual use, hitting the TOCTOU window. Then the symlink will not be treated specially and can therefore point to a file on the host machine.

Continuing the above example, container1:/path2/sym1 has been resolved to /loc1 within $\ell$. But suppose at attacker managed to swap /sym1 for another symlink, now pointing to /loc2, before docker cp started the actual copying. Since we are after the resolving phase, docker cp will not realise that it should search for /loc2 within $\ell$. It will take it as given, referring to a file on the host machine.

If used maliciously, this can give the attacker read and write permissions to any file on the host machine, even without appropriate permissions.

Exploitation with docker cp

Aleksa provided exemplary tools to showcase the security hole on the example of docker cp command. Let’s see what he did.

Attack setup

Assume we are a regular user on the host machine with docker permissions but without root access. We want to retrieve a root-only file from the system, say /w00t_w00t_im_a_flag. So the first thing that Aleksa does is he creates this file, prints some ‘secret’ message there and chmod’s it to 000 so that only root should have access to it.

Then he builds an runs a malicious container and subsequently starts an attack.

Container contents

The container used for the attack also contains /w00t_w00t_im_a_flag file with a different, ‘ordinary’ message. The container executes a single C program which creates a symlink /totally_safe_path pointing to /. It also creates an empty directory. The program then atomically swaps the two in a loop until the end of time.

The attack

docker cp container1:/totally_safe_path/w00t_w00t_im_a_flag is invoked in a never-ending loop, copying each time to a different directory. The underlying container keeps swapping around the symlink at /totally_safe_path. If it manages to insert the symlink to / after the resolution by FollowSymlinkInScope, then / will resolve to the root of the host machine and the file that should be inaccessible will be copied and compromised. If the symlink stays there throughout the operation of docker cp, / will resolve to the container’s root and we will simply copy the ‘ordinary’ message.

Aleksa commented that there is a <1% chance of hitting the TOCTOU window with this script. This means that it only takes roughly a 10s attack to get hold of the ‘secret’ file.

Fallout

Aleksa and Docker security team agreed that the public disclosure of the issue is legitimate even without a fix available.

The most comprehensive solution to the problem would require changes to the core pieces of Docker which is not possible. Some partial solutions are underway and as for now Docker officials said that possible attack scenario is “rare/unlikely” so the flaw, while serious, is not necessarily an emergency.

Bibliography

  1. CVE-2018-15664, Security Mailing List Archive
  2. Docker Bug Allows Root Access to Host Filesystem, Decipher
  3. Github, FollowSymlinkInScope, 04.06.2019
  4. All Docker versions affected by an unpatched race condition issue, Security Affairs
Written on June 4, 2019