はじめに
前回はとりあえずコンテナを作成してみてプロセスが隔離されている様子やリソース制限を確認したが、今回はコンテナを構成する要素技術の Namespace、cgroup、Capability を深ぼっていく
Namespace
Namespace はプロセスを隔離するために利用され、他の Namespace とは異なるリソースを参照することができる
Namespace はすべてのプロセスに関連付けされていて、指定がなければ親プロセスと同じ Namespace を参照するため、プロセス間で共通のリソースを扱うことになる
元の記事では 7 種類となっていたが、本記事執筆時点では 8 種類のリソースを隔離することが可能
Namespace | Flag | Isolates |
---|---|---|
cgroup | CLONE_NEWCGROUP | cgroup のルートディレクトリ |
IPC | CLONE_NEWIPC | System V IPC、POSIX メッセージキュー |
Network | CLONE_NEWNET | ネットワークデバイス、スタック、ポートなど |
Mount | CLONE_NEWNS | マウントポイント |
PID | CLONE_NEWPID | プロセス ID |
Time | CLONE_NEWTIME | 起動時間、単調増加時計 |
User | CLONE_NEWUSER | ユーザおよびグループ ID |
UTS | CLONE_NEWUTS | ホスト名および NIS ドメイン |
namespaces(7) - Linux manual page
プロセスの Namespace は /proc/<PID>/ns
で確認でき、括弧の中が inode 番号となっている
inode 番号が同じプロセスは同じ Namespace を参照しているということ
$ ls -l /proc/$$/ns total 0 lrwxrwxrwx 1 xxxxx xxxxx 0 Mar 17 08:16 cgroup -> 'cgroup:[4026531835]' lrwxrwxrwx 1 xxxxx xxxxx 0 Mar 17 08:16 ipc -> 'ipc:[4026531839]' lrwxrwxrwx 1 xxxxx xxxxx 0 Mar 17 08:16 mnt -> 'mnt:[4026531841]' lrwxrwxrwx 1 xxxxx xxxxx 0 Mar 17 08:16 net -> 'net:[4026531840]' lrwxrwxrwx 1 xxxxx xxxxx 0 Mar 17 08:16 pid -> 'pid:[4026531836]' lrwxrwxrwx 1 xxxxx xxxxx 0 Mar 17 08:16 pid_for_children -> 'pid:[4026531836]' lrwxrwxrwx 1 xxxxx xxxxx 0 Mar 17 08:16 time -> 'time:[4026531834]' lrwxrwxrwx 1 xxxxx xxxxx 0 Mar 17 08:16 time_for_children -> 'time:[4026531834]' lrwxrwxrwx 1 xxxxx xxxxx 0 Mar 17 08:16 user -> 'user:[4026531837]' lrwxrwxrwx 1 xxxxx xxxxx 0 Mar 17 08:16 uts -> 'uts:[4026531838]'
試しに unshare
でプロセスを隔離してみると、inode 番号が先ほどと異なっていることを確認できる
$ unshare -muipr --fork /bin/sh & [1] 2393 $ ls -l /proc/2393/ns total 0 lrwxrwxrwx 1 xxxxx xxxxx 0 Mar 17 08:19 cgroup -> 'cgroup:[4026531835]' lrwxrwxrwx 1 xxxxx xxxxx 0 Mar 17 08:19 ipc -> 'ipc:[4026532451]' lrwxrwxrwx 1 xxxxx xxxxx 0 Mar 17 08:19 mnt -> 'mnt:[4026532449]' lrwxrwxrwx 1 xxxxx xxxxx 0 Mar 17 08:19 net -> 'net:[4026531840]' lrwxrwxrwx 1 xxxxx xxxxx 0 Mar 17 08:19 pid -> 'pid:[4026531836]' lrwxrwxrwx 1 xxxxx xxxxx 0 Mar 17 08:19 pid_for_children -> 'pid:[4026532452]' lrwxrwxrwx 1 xxxxx xxxxx 0 Mar 17 08:19 time -> 'time:[4026531834]' lrwxrwxrwx 1 xxxxx xxxxx 0 Mar 17 08:19 time_for_children -> 'time:[4026531834]' lrwxrwxrwx 1 xxxxx xxxxx 0 Mar 17 08:19 user -> 'user:[4026532448]' lrwxrwxrwx 1 xxxxx xxxxx 0 Mar 17 08:19 uts -> 'uts:[4026532450]'
UTS を隔離したプロセスでホスト名を変更してみると、親プロセスには影響せずにホスト名を変更できていることが確認できる
nsenter
は実行中のプロセスの Namespace に接続して指定したコマンドを実行できるというもので docker exec
のようなもの
$ unshare -muipr --fork /bin/sh -c 'hostname foobar; sleep 15' & [1] 2786 $ hostname colima $ sudo nsenter -u -t 2739 hostname foobar
Namespace はプロセスが終了すると消えてしまうが、/proc/<PID>/ns
以下のファイルをマウントしておくことで維持ができる
nsentar
でこのファイルを指定すると、Namespace にプロセスを関連づけることができる
$ touch ns_uts $ sudo unshare --uts=ns_uts /bin/sh -c 'hostname foobar' $ mount | grep ns_uts nsfs on /path/to/ns_uts type nsfs (rw) $ sudo nsenter --uts=ns_uts hostname foobar
cgroup
cgroup はプロセスをグループ化して、リソース (CPU、Memory など) の使用量を制御したりリソースを監視することが可能な仕組み
サブシステムと呼ばれるものでリソース毎の管理が可能で、各サブシステムの操作はコントローラを介して行う
cgroup は cgroupfs というインターフェースを介して操作が可能で、/sys/fs/cgroup
や /cgroup
にマウントされている
$ mount -t cgroup2 cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime,nsdelegate,memory_recursiveprot)
/sys/fs/cgroup
配下を確認すると、サブシステムが見れる
cgroup v1 ではサブシステム毎に階層構造を持つことができたが、cgroup v2 では階層は 1 つになっている
$ ls -l /sys/fs/cgroup total 0 -r--r--r-- 1 root root 0 Mar 17 08:16 cgroup.controllers -rw-r--r-- 1 root root 0 Mar 17 08:41 cgroup.max.depth -rw-r--r-- 1 root root 0 Mar 17 08:41 cgroup.max.descendants -rw-r--r-- 1 root root 0 Mar 17 08:41 cgroup.pressure -rw-r--r-- 1 root root 0 Mar 17 08:16 cgroup.procs -r--r--r-- 1 root root 0 Mar 17 08:41 cgroup.stat -rw-r--r-- 1 root root 0 Mar 17 08:16 cgroup.subtree_control -rw-r--r-- 1 root root 0 Mar 17 08:41 cgroup.threads -rw-r--r-- 1 root root 0 Mar 17 08:41 cpu.pressure -r--r--r-- 1 root root 0 Mar 17 08:41 cpu.stat -r--r--r-- 1 root root 0 Mar 17 08:16 cpuset.cpus.effective -r--r--r-- 1 root root 0 Mar 17 08:16 cpuset.mems.effective drwxr-xr-x 2 root root 0 Mar 17 08:16 dev-hugepages.mount drwxr-xr-x 2 root root 0 Mar 17 08:16 dev-mqueue.mount drwxr-xr-x 2 root root 0 Mar 17 08:16 init.scope -rw-r--r-- 1 root root 0 Mar 17 08:41 io.cost.model -rw-r--r-- 1 root root 0 Mar 17 08:41 io.cost.qos -rw-r--r-- 1 root root 0 Mar 17 08:41 io.pressure -rw-r--r-- 1 root root 0 Mar 17 08:41 io.prio.class -r--r--r-- 1 root root 0 Mar 17 08:41 io.stat -r--r--r-- 1 root root 0 Mar 17 08:41 memory.numa_stat -rw-r--r-- 1 root root 0 Mar 17 08:41 memory.pressure --w------- 1 root root 0 Mar 17 08:41 memory.reclaim -r--r--r-- 1 root root 0 Mar 17 08:41 memory.stat -r--r--r-- 1 root root 0 Mar 17 08:41 misc.capacity -r--r--r-- 1 root root 0 Mar 17 08:41 misc.current drwxr-xr-x 2 root root 0 Mar 17 08:16 proc-sys-fs-binfmt_misc.mount drwxr-xr-x 2 root root 0 Mar 17 08:16 sys-fs-fuse-connections.mount drwxr-xr-x 2 root root 0 Mar 17 08:16 sys-kernel-config.mount drwxr-xr-x 2 root root 0 Mar 17 08:16 sys-kernel-debug.mount drwxr-xr-x 2 root root 0 Mar 17 08:16 sys-kernel-tracing.mount drwxr-xr-x 22 root root 0 Mar 17 08:31 system.slice drwxr-xr-x 3 root root 0 Mar 17 08:16 user.slice
cgcreate
を利用して実際に cpu,memory サブシステムに関連づけたサブグループを作成してみると、サブグループ内にもサブシステムが作成されている
$ UUID=$(./uuidgen) $ sudo cgcreate -g cpu,memory:$UUID $ tree /sys/fs/cgroup/$UUID /sys/fs/cgroup/2632c499-93a1-4aed-9ab1-3f9fb677f868 ├── cgroup.controllers ├── cgroup.events ├── cgroup.freeze ├── cgroup.kill ├── cgroup.max.depth ├── cgroup.max.descendants ├── cgroup.pressure ├── cgroup.procs ├── cgroup.stat ├── cgroup.subtree_control ├── cgroup.threads ├── cgroup.type ├── cpu.idle ├── cpu.max ├── cpu.max.burst ├── cpu.pressure ├── cpu.stat ├── cpu.uclamp.max ├── cpu.uclamp.min ├── cpu.weight ├── cpu.weight.nice ├── cpuset.cpus ├── cpuset.cpus.effective ├── cpuset.cpus.partition ├── cpuset.mems ├── cpuset.mems.effective ├── io.max ├── io.pressure ├── io.prio.class ├── io.stat ├── io.weight ├── memory.current ├── memory.events ├── memory.events.local ├── memory.high ├── memory.low ├── memory.max ├── memory.min ├── memory.numa_stat ├── memory.oom.group ├── memory.peak ├── memory.pressure ├── memory.reclaim ├── memory.stat ├── memory.swap.current ├── memory.swap.events ├── memory.swap.high ├── memory.swap.max ├── memory.swap.peak ├── memory.zswap.current ├── memory.zswap.max ├── pids.current ├── pids.events ├── pids.max └── pids.peak 1 directory, 55 files
サブグループに所属するプロセスは /sys/fs/cgroup/<SUBGROUP>/cgroup.procs
で確認できる
$ sudo cgexec -g cpu:$UUID sleep 10 & [1] 7662 $ cat /sys/fs/cgroup/$UUID/cgroup.procs 7662
リソース制限については前回確認しているためスキップする
Capability
特権 (root) ユーザで動作するプロセスはすべての権限を持つため、実行しているプログラムに脆弱性があった場合の影響範囲が広い Capability を操作して権限を細分化し、プロセスに必要な権限だけを許可することにより、その影響範囲を狭めることができる
一般的に非特権ユーザは RAW ソケットを扱うことができないため ping
は実行できないが、SUID (Set User ID) により特権ユーザで動作するので、非特権ユーザでも RAW ソケットを扱うことができるらしい
試してみようとしたところ、ping
がインストールされていなかったので、まずはインストールから
$ sudo apt install -y iputils-ping $ which ping /usr/bin/ping
インストールした ping
に SUID を設定する
$ ls -l /usr/bin/ping -rwxr-xr-x 1 root root 81448 Nov 27 2022 /usr/bin/ping $ sudo chmod 4755 /usr/bin/ping $ ls -l /usr/bin/ping -rwsr-xr-x 1 root root 81448 Nov 27 2022 /usr/bin/ping
問題なく実行できる
$ ping -c1 127.0.0.1 PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data. 64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.136 ms --- 127.0.0.1 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.136/0.136/0.136/0.000 ms
ping
をコピーして SUID が設定されてない状態だと本来は動作しないはずが、なぜか動作してしまった
$ cp /usr/bin/ping . $ ls -l ping -rwxr-xr-x 1 xxxxx xxxxx 81448 Mar 17 09:05 ping $ ./ping -c1 -q 127.0.0.1 PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data. --- 127.0.0.1 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.113/0.113/0.113/0.000 ms
もちろん Capability は持ってない
$ getcap /usr/bin/ping /usr/bin/ping cap_net_raw=ep $ getcap ./ping
何やら net.ipv4.ping_group_range
というカーネルパラメータがあり、RAW ソケットを作成できるグループの範囲を設定できるらしい
ping を実行するのに CAP_NET_RAW は必要なくなっていた
実際に確認してみると gid のレンジが 0 ~ 2147483647 ととても広い
$ sysctl net.ipv4.ping_group_range net.ipv4.ping_group_range = 0 2147483647
現在のプロセスを実行している gid は 1000 なので該当する
$ id uid=501(xxxxx) gid=1000(xxxxx) groups=1000(xxxxx),995(docker)
gid のレンジを変更してみる
$ sudo sysctl -w net.ipv4.ping_group_range="1 0" net.ipv4.ping_group_range = 1 0
この状態で試してみると、RAW ソケットの権限がなく実行できない
$ ./ping -c1 -q 127.0.0.1 ./ping: socktype: SOCK_RAW ./ping: socket: Operation not permitted ./ping: => missing cap_net_raw+p capability or setuid?
RAW ソケットを扱う権限を付与すると、再度実行できるようになった
$ sudo setcap CAP_NET_RAW+ep ./ping $ ./ping -c1 -q 127.0.0.1 PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data. --- 127.0.0.1 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.314/0.314/0.314/0.000 ms
Capability は以下の Capability Set というものを利用して表現する
- Permitted
- Inheritable
- Effective
- Ambient
- Bounding Set
プロセスはすべての Capability Set を扱えるが、ファイルの場合は Permitted
、Inheritable
、Effective
の 3 つのみを利用できるとのこと
実際にカーネルがチェックする Capability Set は Effective
とのことだが、Capability Set のアルゴリズムを理解できていないため、ここは引き続き学習していこうと思う
第42回 Linuxカーネルのケーパビリティ[1] | gihyo.jp
ちなみに、Docker コンテナのデフォルトの Capability は以下の通り
$ capsh --decode=$(cat /proc/$(sudo docker container inspect $(sudo docker run --rm -d busybox sleep 10) -f '{{.State.Pid}}')/status | awk '/^CapEff:/{print $2}') 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
--privileged
オプションを付与したコンテナの場合は以下の通りで、現時点で 41 種類あるすべての Capability がセットされている
$ capsh --decode=$(cat /proc/$(sudo docker container inspect $(sudo docker run --privileged --rm -d busybox sleep 10) -f '{{.State.Pid}}')/status | awk '/^CapEff:/{print $2}') 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,cap_perfmon,cap_bpf,cap_checkpoint_restore
さいごに
コンテナ要素技術の Namespace、cgroup、Capability について深ぼってみて、「コンテナ」への理解が進んだと思う
次回はファイルシステムとネットワークについて学ぶ予定