前回はプロセスに設定されているケーパビリティと、
今回はまず、execve(2) システムコールを使ってプログラムを実行する際にケーパビリティがどのように変化するのかを説明したあと、
プログラム実行時のケーパビリティ
Linux上で実行されるプログラムは、fork(2)やclone(2)システムコールを使って親プロセスを複製して生成し、execve(2)システムコールで目的のプログラムを実行します。
このexecve(2)でプログラムを実行する際に、
P'(ambient) = (file is privileged) ? 0 : P(ambient) ...(1)
P'(permitted) = (P(inheritable) & F(inheritable)) |
(F(permitted) & P(bounding)) | P'(ambient) ...(2)
P'(effective) = F(effective) ? P'(permitted) : P'(ambient) ...(3)
P'(inheritable) = P(inheritable)
P'(bounding) = P(bounding)
ここで、
P(): execve(2) 前のスレッドのケーパビリティセット
P'(): execve(2) 後のスレッドのケーパビリティセット
F(): ファイルケーパビリティセット
を示します。
execve後のケーパビリティの計算とファイルケーパビリティ
ここで一番複雑に見えるのは式
式(1)
式(2)P(inheritable) & F(inheritable)"、F(permitted) & P(bounding)"、P'(ambient)はORですので、execve(2)後のPermittedケーパビリティで許可されます。
AmbientP'(ambient))
ファイルケーパビリティで設定できるそれぞれのケーパビリティセットを紹介しながら、
- Permitted
- ここで許可したケーパビリティは、
Inheritableケーパビリティでの許可の有無に関わず execve(2)後のPermittedケーパビリティ( P'(permitted))で許可されます。ただし、 バウンディングセット ( P(bounding))で許可されている場合のみです。あとでバウンディングセットの部分で詳しく説明します - Inheritable
- ここで許可したケーパビリティは、
プロセスの execve(2)前のInheritableケーパビリティ( P(inheritable))で許可されていれば、 execve(2)後のPermittedケーパビリティ( P'(permitted))で許可されます - Effective
- ファイルケーパビリティのEffectiveケーパビリティは、
他のふたつと違って0 or 1の単一の値です
式(3)
- 設定されている場合
- アルゴリズムで計算した
execve(2)後のPermittedケーパビリティ( P'(permitted))の値が execve(2)後のEffectiveケーパビリティ( P'(effective))に設定されます - 設定されていない場合
execve(2)後のAmbientケーパビリティの値( P'(ambient))が execve(2)後のEffectiveケーパビリティ( P'(effective))に設定されます
Ambientケーパビリティ
ここまで説明したファイルケーパビリティを使えば、pingコマンドのように、
ところがファイルケーパビリティはファイルに属性を持たせますので、
セキュリティ的に必要な特権を与える範囲を最小限に限定したいという場合、
親プロセスが持っているケーパビリティの一部だけを継承し、
このような場合に使うのがAmbientケーパビリティです。このAmbientケーパビリティは比較的新しい機能で、
この機能が追加されるまで、
P'(permitted) = (P(inheritable) & F(inheritable)) |
(F(permitted) & cap_bset)
P'(effective) = F(effective) ? P'(permitted) : 0
P'(inheritable) = P(inheritable)
(cap_bsetはバウンディングセット)
このアルゴリズムでもInheritableケーパビリティがありますので、
なぜなら、execve(2)で生成したプロセスにケーパビリティを与えるには、P'(permitted)で目的のケーパビリティセットを有効にできません。その結果、P'(effective)でもケーパビリティを有効にできません。
ファイルケーパビリティを設定してしまえば、
そこで登場したのがAmbientケーパビリティです。Ambientケーパビリティは特権を持たないプロセスのexecve(2)の前後で継承されるケーパビリティです。
Ambientケーパビリティは、
また、
そして、setuid、setgid、
そして
先に書いたような目的の場合に、
Ambientケーパビリティは説明だけでも比較的わかりやすい機能かもしれませんが、
前回の実行例のように、setuidも設定されていないpingコマンドをAmbientケーパビリティを使って実行してみましょう。
UbuntuやCentOS 8でインストールされるlibcap 2.
$ cp /bin/ping . (コピーしたのでファイルケーパビリティは外れる) $ /sbin/getcap ./ping (ファイルケーパビリティは設定されていない) $ sudo capsh --caps="cap_setpcap,cap_setuid,cap_setgid+ep cap_net_raw+ip" \ (1) > --keep=1 \ (2) > --uid=1000 \ (3) > --addamb="cap_net_raw" \ (4) > --print \ (5) > -- -c "./ping -c 1 127.0.0.1" Current: = cap_net_raw+ip cap_setgid,cap_setuid,cap_setpcap+p (1で指定したケーパビリティが設定されている) Bounding set =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 Ambient set =cap_net_raw (Ambientケーパビリティが設定されている) Securebits: 020/0x10/5'b10000 secure-noroot: no (unlocked) secure-no-suid-fixup: no (unlocked) secure-keep-caps: yes (unlocked) (--keepオプションを指定したのでyesになっている) secure-no-ambient-raise: no (unlocked) uid=1000(karma) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),5(tty),6(disk),10(wheel),11(floppy) 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.023 ms --- 127.0.0.1 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.023/0.023/0.023/0.000 ms (pingコマンドが実行できた)
引数も実行結果も長いのでかえってわかりづらいかもしれませんね(^^)。何をやっているのかを簡単に説明しましょう。capshコマンドは指定した順でオプションが処理されますので、
オプションを与えた順に処理されますので、
- Ambientケーパビリティを設定するために
cap_を親プロセスsetpcap ( capshコマンド)に設定 (このケーパビリティがないとIhneritableにケーパビリティを設定できません) - uid=1000でコマンドを実行するために
cap_を親プロセスに設定setuid,cap_ setgid - Permitted、
InheritableケーパビリティがないとAmbientに設定できないので cap_を親プロセスに設定net_ raw
--keep=1はあとで説明するsecurebitsフラグを設定(このフラグを設定する際にも cap_が必要)setpcap - 一般ユーザ権限で実行するために
--uid=1000を指定 --addamb=cap_でnet_ raw pingコマンドの実行に必要なcap_をAmbientケーパビリティに設定net_ raw - capsh実行時の状態を確認するために
--printオプションを指定
Current行でオプションで設定したケーパビリティが設定されていること、Ambient set行でcap_が設定されていることが確認できます。
Ambientケーパビリティを設定したので、pingコマンドが実行できています。
securebitsフラグ
通常は、
先の実行例ではroot権限で実行するcapshを--uidオプションを使って0から1000に変更しようとしています。何もしなければ、--capsオプションで与えたケーパビリティが失われてしまいます。
この実行例のように、
そのときのための機能として、pcrtl(2)システムコールを使って指定し、CAP_ケーパビリティが必要です。
先の例で--keep=1というオプションを指定したのが、secure-keep-caps: yes (unlocked)"という行があるのがsecurebitsにフラグが設定されていることを示しています。
securebitsフラグに指定できるフラグは現時点では4つほどあります。先の実行例でSecurebitsという行があり、capabilities(7)をご覧ください。
バウンディングセット
最後にバウンディングセットについて少し詳しく説明しておきましょう。
バウンディングセットにはふたつの役割があり、
execve(2)実行時に取得できるケーパビリティを制限する役割capset(2)システムコールでスレッドのケーパビリティを設定する際に制限をかける役割
まずはひとつめの役割を説明しましょう。
上にあげたプログラム実行時のアルゴリズムのP'(permitted)の式F(permitted) & P(bounding)"とあるように、
$ cp /bin/ping . $ sudo setcap "cap_net_raw+p" ./ping (permittedにcap_net_rawを設定) $ id -u 1000 $ ./ping -c 1 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.007 ms --- 127.0.0.1 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.007/0.007/0.007/0.000 ms
上の例はコピーしたpingに設定するファイルケーパビリティで、cap_を設定して実行している例です。Permittedケーパビリティが有効になっているので一般ユーザーでも実行できていますが、cap_を落としたシェルから実行すると実行できません。
$ sudo capsh --drop="cap_net_raw" --uid=1000 -- $ grep Cap /proc/self/status CapInh: 0000000000000000 CapPrm: 0000000000000000 CapEff: 0000000000000000 CapBnd: 0000003fffffdfff CapAmb: 0000000000000000 (バウンディングセットからcap_net_rawは落とされている) $ id -u 1000 $ getcap ./ping (ファイルケーパビリティでcap_net_rawは設定されている) ./ping = cap_net_raw+p $ ./ping 127.0.0.1 ping: socket: Operation not permitted
このようにexecve(2)でプログラムを実行する際に取得できるケーパビリティを制限できます。
しかし先に説明した通り、P(inheritable) & F(inheritable)"とのORですので、
また同様にAmbientP'(Ambient))
pingに対してファイルケーパビリティでInheritableケーパビリティを設定して、
$ sudo setcap "cap_net_raw+pi" ./ping (permittedとinheritableを設定) $ sudo capsh --caps="cap_setpcap,cap_setuid+eip" --inh="cap_net_raw" --drop="cap_net_raw" --uid=1000 -- (inheritableにcap_net_rawを設定しつつ、バウンディングセットからはcap_net_rawを落としてシェルを実行) $ grep Cap /proc/self/status CapInh: 0000000000002000 CapPrm: 0000000000000000 CapEff: 0000000000000000 CapBnd: 0000003fffffdfff CapAmb: 0000000000000000 (cap_net_rawはバウンディングセットで設定されていないがinheritableでは設定されている) $ getpcaps $$ (getpcapsコマンドでもinheritableが設定されていることを確認) Capabilities for `8346': = cap_net_raw+i $ ./ping -c 1 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.008 ms --- 127.0.0.1 ping statistics --- 1 packets transmitted, 1 received, 0% packet loss, time 0ms rtt min/avg/max/mdev = 0.008/0.008/0.008/0.000 ms
以上のようにバウンディングセットでcap_を無効にしたにも関わらず、pingコマンドは実行できました。
これではプロセスが取得できるケーパビリティを制限できないではないか、
もうひとつの役割は、capset(2)システムコールでスレッドが自身でケーパビリティを設定する際に制限をかける役割です。capset(2)でInheritableケーパビリティを追加する場合、
先の実行例のcapshに指定するオプションの順を少し変えて、--inhでInheritableケーパビリティを設定する前に、--dropでバウンディングセットからcap_を削除してみましょう。
$ sudo capsh --caps="cap_setpcap,cap_setuid+eip" --drop="cap_net_raw" --inh="cap_net_raw" Unable to set inheritable capabilities: Operation not permitted
このようにバウンディングセットで許可していないと、
つまり、P(inheritable))F(inheritable))P(inheritable) & F(inheritable)"の計算でケーパビリティは許可されないことになりますので、execve(2)実行後はそのケーパビリティはPermittedケーパビリティP'(permitted))
以上のようにバウンディングセットはexecve(2)の前後や、
まとめ
ここまでで、
ケーパビリティは複雑な機能です。マニュアルをすみずみまで行ったり来たりしながら読まないとなかなか理解できないと思います。実は筆者は理解しようとして何度も挫折しており、
今回のケーパビリティの記事を書くに当たっては、
また、
次回は、
参考文献
- ケーパビリティで権限を少しだけ与える (いますぐ実践! Linux システム管理)
- 実行例が豊富です
- コンテナ技術入門 - 仮想化との違いを知り、要素技術を触って学ぼう (エンジニアHub)
- 今回レビューいただいた@hayajoさんの記事
- 明日使えない Linux の capabilities の話 (@nojima's blog)
- Ambientケーパビリティについてわかりやすく書かれています
- Linux Capability - ケーパビリティについての整理 (ローファイ日記)
- udzura さんの記事
