LXCで学ぶコンテナ入門 -軽量仮想化環境を実現する技術

第43回 Linuxカーネルのケーパビリティ[2]

この記事を読むのに必要な時間:およそ 6 分

securebitsフラグ

通常は,特権を持ったプロセス(スレッド)のUIDが,0から特権を持たない0以外に変化する際,そのプロセスはケーパビリティを失います。特権を持たないプロセスになるわけですから,ケーパビリティを失うのはセキュリティの観点から言っても納得できる動きです。

先の実行例ではroot権限で実行するcapshを--uidオプションを使って0から1000に変更しようとしています。何もしなければ,せっかく--capsオプションで与えたケーパビリティが失われてしまいます。

この実行例のように,UIDを変更する際でもケーパビリティを保持し続けたままにしたいケースがあります。その他にもroot権限で実行されているプロセスのケーパビリティに関する扱いを変えたい場合があります。

そのときのための機能として,カーネルではsecurebitsフラグが実装されています。securebitsフラグはpcrtl(2)システムコールを使って指定し,ケーパビリティと同様にスレッドごとに設定されます。このフラグを設定するにはCAP_SETPCAPケーパビリティが必要です。

先の例で--keep=1というオプションを指定したのが,UIDが0から1000に変わる際にもケーパビリティを維持する指定です。コマンド実行の結果に"secure-keep-caps: yes (unlocked)"という行があるのがsecurebitsにフラグが設定されていることを示しています。

securebitsフラグに指定できるフラグは現時点では4つほどあります。先の実行例でSecurebitsという行があり,そのあとに4つある項目がsecurebitsに設定できるフラグです。それぞれの機能について詳しくはcapabilities(7)をご覧ください。

バウンディングセット

最後にバウンディングセットについて少し詳しく説明しておきましょう。

バウンディングセットにはふたつの役割があり,スレッドごとに設定されます(2.6.24 以前はシステム全体で共通の値でした⁠⁠。

  • execve(2)実行時に取得できるケーパビリティを制限する役割
  • capset(2)システムコールでスレッドのケーパビリティを設定する際に制限をかける役割

まずはひとつめの役割を説明しましょう。

上にあげたプログラム実行時のアルゴリズムのP'(permitted)の式(2)で"F(permitted) & P(bounding)"とあるように,ファイルケーパビリティでPermittedケーパビリティが指定されていても,バウンディングセットで許可されていないケーパビリティは許可されません。

$ 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に設定するファイルケーパビリティで,Permittedケーパビリティにcap_net_rawを設定して実行している例です。Permittedケーパビリティが有効になっているので一般ユーザーでも実行できていますが,次のようにバウンディングセットでcap_net_rawを落としたシェルから実行すると実行できません。

$ 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)でプログラムを実行する際に取得できるケーパビリティを制限できます。

しかし先に説明した通り,同じ式(2)にある"P(inheritable) & F(inheritable)"とのORですので,この式で許可されていれば,バウンディングセットで許可されていなくてもケーパビリティを獲得できてしまいます。

また同様にAmbientP'(Ambient)ケーパビリティセットともORですので,Ambientケーパビリティで許可されているケーパビリティも獲得できます。

pingに対してファイルケーパビリティでInheritableケーパビリティを設定して,プロセスの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_net_rawを無効にしたにも関わらず,ファイルとプロセスでInheritableケーパビリティで有効になっているためにpingコマンドは実行できました。

これではプロセスが取得できるケーパビリティを制限できないではないか,と思われるかもしれません。しかし,バウンディングセットにはもうひとつ役割があります。

もうひとつの役割は,capset(2)システムコールでスレッドが自身でケーパビリティを設定する際に制限をかける役割です。capset(2)でInheritableケーパビリティを追加する場合,バウンディングセットで設定されているケーパビリティのみInheritableケーパビリティに追加できます。

先の実行例のcapshに指定するオプションの順を少し変えて,--inhでInheritableケーパビリティを設定する前に,--dropでバウンディングセットからcap_net_rawを削除してみましょう。

$ sudo capsh --caps="cap_setpcap,cap_setuid+eip" --drop="cap_net_raw" --inh="cap_net_raw" 
Unable to set inheritable capabilities: Operation not permitted

このようにバウンディングセットで許可していないと,Inheritableケーパビリティに追加しようとするとエラーになります。

つまり,一度バウンディングセットからケーパビリティが削除されると,それ以降はプロセスのInheritableケーパビリティP(inheritable)にそのケーパビリティを追加できません。ファイルケーパビリティのInheritableケーパビリティF(inheritable)で許可をしたとしても,"P(inheritable) & F(inheritable)"の計算でケーパビリティは許可されないことになりますので,結局execve(2)実行後はそのケーパビリティはPermittedケーパビリティP'(permitted)には持てないことになります。

以上のようにバウンディングセットはexecve(2)の前後や,その子孫で取得できるケーパビリティを制限する役割を持っています。

まとめ

ここまでで,Linuxカーネルが持つケーパビリティの機能について一通り説明しました。

ケーパビリティは複雑な機能です。マニュアルをすみずみまで行ったり来たりしながら読まないとなかなか理解できないと思います。実は筆者は理解しようとして何度も挫折しており,今回の記事を書くために色々調べたり試したりしてようやく納得できた気がしています。

今回のケーパビリティの記事を書くに当たっては,筆者は理解に自信がなかったため,udzuraさんと,コンテナに関するすばらしい記事をお書きの@hayajoさんにレビューをしていただき,役に立つフィードバックをいただきました。ありがとうございました。

また,日本語でケーパビリティについて解説している色々な記事を参考にしましたので,最後に参考文献として挙げておきます。今回の記事でわかりづらい部分が合った場合に参照すると理解が進むかもしれません。

次回は,今回のケーパビリティの記事を書くきっかけとなった,ファイルケーパビリティのコンテナ関連の機能ついて紹介する予定です。

参考文献

ケーパビリティで権限を少しだけ与える (いますぐ実践! Linux システム管理)
実行例が豊富です
コンテナ技術入門 - 仮想化との違いを知り、要素技術を触って学ぼう (エンジニアHub)
今回レビューいただいた@hayajoさんの記事
明日使えない Linux の capabilities の話 (@nojima's blog)
Ambientケーパビリティについてわかりやすく書かれています
Linux Capability - ケーパビリティについての整理 (ローファイ日記)
udzura さんの記事

著者プロフィール

加藤泰文(かとうやすふみ)

2009年頃にLinuxカーネルのcgroup機能に興味を持って以来,Linuxのコンテナ関連の最新情報を追っかけたり,コンテナの勉強会を開いたりして勉強しています。英語力のない自分用にLXCのmanページを日本語訳していたところ,あっさり本家にマージされてしまい,それ以来日本語訳のパッチを送り続けています。

Plamo Linuxメンテナ

Twitter:@ten_forward
技術系のブログ:http://tenforward.hatenablog.com/