前回はプロセスに設定されているケーパビリティと、
今回はまず、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 さんの記事