Reboot your pc from a docker container
I came back from a PUT Security Day where I gave a talk about Docker security. One of the questions I asked myself when preparing the talk is whether one can reboot their PC (aka host machine) from a docker container.
Rebooting the usual way: reboot
program
Normally, one would use a reboot
program to do this task but this program isn’t present in many of the docker images.
Let’s give it a try with the official ubuntu
image:
$ docker run --rm -it ubuntu reboot
docker: Error response from daemon: OCI runtime create failed: container_linux.go:348: starting container process caused "exec: \"reboot\": executable file not found in $PATH": unknown.
ERRO[0001] error waiting for container: context canceled
as we can see, the program isn’t in $PATH
. Let’s see another ways of rebooting the container.
Reeboting with SysRq
It is possible to reboot Linux without any external dependencies by using SysRq. This can be achieved by first writing to the sysrq and then writing to sysrq-trigger to trigger the action:
$ echo 1 > /proc/sys/kernel/sysrq
$ echo b > /proc/sysrq-trigger
But if we execute it in a docker container, it won’t work, because the sysrq is a read-only file system in there:
$ docker run --rm -it ubuntu bash
root@c640b157389b:/# echo 1 > /proc/sys/kernel/sysrq
bash: /proc/sys/kernel/sysrq: Read-only file system
The thing is, even when we are the root
user in a docker container (note: and this is the same root as on the host machine) we don’t get full capabilities.
Those can be given by rerunning the container with --privileged
flag:
$ docker run --rm -it --privileged ubuntu bash
root@e293a1c415a7:/# echo 1 > /proc/sys/kernel/sysrq
root@e293a1c415a7:/# echo b > /proc/sysrq-triggerERRO[0005] error waiting for container: EOF
Running this on my machine actually didn’t reboot it but that’s because I use Docker for Mac and so all the containers are spawned in a virtual machine used for that purpose. But the action killed the docker daemon and resulted with such error:
Supervisor has failed, shutting down: Supervisor caught an error: one of the children died: com.docker.driver.amd64-linux (pid: 37738)
Rebooting with reboot
syscall
Another way to execute a reboot is to use a reboot
syscall. This can be achieved with a short C program:
#define _GNU_SOURCE
#include <unistd.h>
#include <sys/reboot.h>
int main() {
return reboot(RB_AUTOBOOT);
}
Let’s try to compile and run it in a docker container.
For a better understanding on what is going on, we will add a SYS_PTRACE
linux capability to our docker container,
so it will be possible to use strace
, a linux syscall tracer program, that uses the ptrace
syscall under the hood.
We will also use gcc
docker image with strace
installed on it from apt.
$ docker run --rm -it --cap-add=SYS_PTRACE gcc bash
root@dfe469dd8034:/# apt update 2>/dev/null 1>&2 && apt install -y strace 2>/dev/null 1>&2
root@dfe469dd8034:/# printf "#define _GNU_SOURCE\n#include <unistd.h>\n#include <sys/reboot.h>\nint main(){reboot(RB_AUTOBOOT);}" > a.c && gcc a.c
root@dfe469dd8034:/# strace -e reboot ./a.out
reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_CMD_RESTART) = -1 EPERM (Operation not permitted)
+++ exited with 0 +++
It didn’t work as we didn’t add a SYS_BOOT
capability (that makes it possible to use reboot
and kexec_load
syscalls). If we add it, the reboot
will kinda work:
dc@dc:~$ docker run --rm --cap-add=SYS_PTRACE --cap-add=SYS_BOOT -it gcc bash
root@e5c15796ae6b:/# apt update 2>/dev/null 1>&2 && apt install -y strace 2>/dev/null 1>&2
root@e5c15796ae6b:/# printf "#define _GNU_SOURCE\n#include <unistd.h>\n#include <sys/reboot.h>\nint main(){reboot(RB_AUTOBOOT);}" > a.c && gcc a.c
root@e5c15796ae6b:/# strace -e reboot ./a.out
reboot(LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_CMD_RESTARTdc@dc:~$
It actually killed the docker container but didn’t reboot machine the docker is run on (in this case I launched it on a vps).
The reason for that can be found in man 2 reboot
:
Behavior inside PID namespaces
Since Linux 3.4, if reboot() is called from a PID namespace other than the initial PID namespace with one of the cmd values listed below, it performs a "reboot" of that namespace: the
"init" process of the PID namespace is immediately terminated, with the effects described in pid_namespaces(7).
so the reboot worked, but for the PID namespace the container was in.
A short explanation for those who are not familiar with linux namespaces: this is one of linux kernel features utilized by docker which makes it possible to create isolated groups of given resources (e.g. PIDs, users, mounts and others).
See man namespaces
for more info.
Can you use SysRq method without --privileged
flag?
When I was preparing my talk I wondered if I can launch the SysRq example without the --privileged
flag.
At first I thought it may be because of lacking the SYS_BOOT
capability or maybe something with the default seccomp profile the docker uses (source; as long as your kernel supports seccomp), so I adjusted the flags, but it didn’t help:
$ docker run --rm --cap-add=SYS_BOOT --security-opt seccomp=unconfined -it gcc bash
root@b6342cab4756:/# echo 1 > /proc/sys/kernel/sysrq
bash: /proc/sys/kernel/sysrq: Read-only file system
I thought that maybe some other capability is needed, so I tried adding all of them:
$ docker run --rm --cap-add=ALL --security-opt seccomp=unconfined -it gcc bash
root@c4d8b01be2d1:/# echo 1 > /proc/sys/kernel/sysrq
bash: /proc/sys/kernel/sysrq: Read-only file system
but all I got is Read-only file system
.
It turns out this can be hacked by mounting the /proc
virtual filesystem to be writable:
$ docker run --rm -v /proc:/writable_proc -it gcc bash
root@8c1d0f5ed52e:/# echo 1 > /writable_proc/sys/kernel/sysrq
root@8c1d0f5ed52e:/# echo b > /writable_proc/sysrq-trigger
…and now it rebooted the machine.
It seems there is no capability to make it possible to write to /proc
.
This stackoverflow answer also states that there are no ACLs for that.
The end
And that’s all of this reboot
topic here.
In the end, it’s good that it is not possible to reboot the host machine with the default settings.
It is possible to do so by using --privileged
or by mounting the /proc
virtual filesystem to be writable.
I guess not very much people do that and I hope that if they do, they are aware of the consequences.
EDIT: Nonetheless, while not necessarily a fault of docker, this is a bad design that it is possible to reboot your host machine while disallowing the CAP_SYS_BOOT
(or SYS_BOOT
in docker nomenclature) capability.
If you ask what was the purpose - just playing with docker and understanding how the things are limited etc.
Also special thanks to Oshogbo for some ideas and discussion about this topic and to foxtrot_charlie and all the PUT Security Day team for inviting me to give a talk there o/.
Comments