Docker/runc CVE-2019-5736 Exploit

I wanted to understand the recent privilege escalation (escape from a container) vulnerability in runc (the runtime used in Docker), so I sat down looking through the original blog post by Adam and Borys and wrote and own PoC. I had a lot of fun doing this, I hope someone finds this insightful/useful.

Of course, this is not the first publicly available PoC, look on the web for others.

The code is on Github: cve-2019-5736-exp.

Technical Notes

Replacing glibc shared library

I chose glibc as the library to put put the attacker's code in. Replacing the glibc shared library (/usr/lib64/libc-2.28.so) proved to be difficult; naively copying the modified so the target location caused the container to exit with a "Bus error". I assume this had something to do with the shared library being used by running processes. Here's a rundown of what I ended up doing:

RUN cp /lib64/libc-2.28.so /lib64/libc-2.28.so.old && \
    ln -sf /lib64/libc-2.28.so.old /lib64/libc.so.6 && \
    rm /lib64/libc-2.28.so

RUN cp /glibc.inst/lib64/libc-2.28.so /lib64/ && \
    ln -sf /lib64/libc-2.28.so /lib64/libc.so.6

I made a copy of the old version of the library, and then continued in a separate RUN instruction. This will cause the Docker runtime to reload the container, which means the every running process will be linking to the backup file. Then I can safely copy my modified libc to the right place and recreate the symlink.

Not sure that this should work (as I'm not entirely convinced that simply copying the shared library over would not work), but the fact is it did, and the latter didn't.

Sleeping

Seemingly purposeless sleep in evil.c is there so that the runc process on the host has a chance to quit before I open the runc binary. This is because Linux won't allow you write to a file that's being executed (the same reason why the researchers suggested spawning a new process to do the overwrite rather than doing it in the modified shared library).

    const char *path = "/payload";

    sleep(2);

    size = filelen(path);