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

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

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

前回からだいぶ間隔が空いてしまいました。前回の最後に案内したudzuraさんのCRIUに関する記事はもう少し時間がかかるようですので,もうしばらく私が担当したいと思います。

今回から数回は,Linuxカーネルに実装されているケーパビリティについて説明します。ケーパビリティは2.2カーネルのころから実装されてきているかなり古くからある機能で,コンテナ向けの機能ではなく一般的に使われている機能です。もちろん,コンテナの安全性を高めるための重要な機能でもあります。

setuid

一般的にはUNIX系のOSでは,プロセスはroot権限(実効ユーザIDが0)で実行される特権プロセスと,一般ユーザ権限で実行される(実効ユーザIDが0以外の)非特権プロセスに分けられます。

一般ユーザは,通常はそのユーザの権限でプログラムを実行すれば良いのですが,一般ユーザが実行するプログラムであっても処理の内容によっては特権が必要な場合があります。例えば,ネットワークの疎通確認に良く使うpingコマンドは特権が必要で,Linuxディストリビューションによっては次のようにsetuidされており,root権限で実行するようになっています(例はUbuntu 18.04⁠⁠。

cmd
$ ls -l $(which ping)
-rwsr-xr-x 1 root root 64424 Jun 28 20:05 /bin/ping
   ↑(このsがsetuidされている印)

ケーパビリティ

先に紹介したpingコマンドは,パケット送信にRawソケットを使います。このRawソケットを使う場合には特権が必要ですのでsetuidされており,root権限で実行されていたのでした。

逆に言うとRawソケットを使うのに必要な権限のみが必要なのに,広範囲に渡るrootが持つ強い権限すべてをpingコマンドに渡していることになります。これはセキュリティ上の観点から望ましいことではありません。

Linuxにはこのような場合に使える,rootが持っている絶対的な権限を細かく分け,必要な権限だけを与える仕組みが存在します。これがケーパビリティ(capability)です。ケーパビリティはスレッドごとに設定され,スレッドは自身が持つケーパビリティを変更できます。

例えば,CAP_NET_RAWというケーパビリティがあればRawソケットを使用できます。Linuxカーネルでは,このような細分化されたケーパビリティが多数定義されており,/usr/include/linux/capability.hで定義されています。

$ grep "#define CAP_" /usr/include/linux/capability.h 
#define CAP_CHOWN            0
#define CAP_DAC_OVERRIDE     1
#define CAP_DAC_READ_SEARCH  2
#define CAP_FOWNER           3
#define CAP_FSETID           4
#define CAP_KILL             5
    :(略)

ケーパビリティの実装は結構古くて,2.2カーネル以降で使えます。しかし,カーネルの進化とともに新たなケーパビリティが追加されているので,カーネルのバージョンによって指定できるケーパビリティが異なります。どのようなケーパビリティが定義されており,それぞれのケーパビリティがどのような権限に対応しているのか,いつのカーネルから使えるようになったかについては,マニュアルcapabilities(7)に載っています。

プロセスのケーパビリティ

プロセス(実際はスレッド)はそれぞれ4種類のケーパビリティセットというものを持っています。ケーパビリティセットは,内部的にはビット列で,ケーパビリティを持っていれば1がセットされます。

Permitted
EffectiveとInheritableで持つことを許されるケーパビリティセット
Inheritable
execve(2)した際に継承できるケーパビリティセット
Effective
実際にカーネルがスレッドの実行権限を判定するのに使うケーパビリティセット
Ambient
特権のない(setuid/setgidされていない)プログラムをexecve(2)した際に子プロセスに継承されるケーパビリティセット(Linux 4.3以降で使用可能)

さらに,プロセスごとに取得できるケーパビリティセットを制限するためのCapability bounding Set(以降,バウンディングセット)というものがあります。

実際にカーネルがプロセスが持つ権限をチェックする時は,Effectiveケーパビリティをチェックします。つまりEffectiveで許可されていない操作はできません。

そして,ケーパビリティが変化する機会は3種類あります。

  • capset(2):システムコールでケーパビリティを設定する
  • execve(2):システムコールの前後でケーパビリティが変化する
  • prctl(2):システムコールでAmbientケーパビリティやバウンディングセットを設定する

execve(2)システムコールはプログラムを実行するためのシステムコールです。このシステムコールとケーパビリティの関係については次回で説明します。

実行中のプロセスが持つケーパビリティを確認する

では実際にプロセスでケーパビリティがどのように設定されているかを見てみましょう。

プロセス(スレッド)が持つケーパビリティは/proc/<PID>/statusファイルで確認できます。例えばPIDが1であるinitのPermittedCapPrm⁠,EffectiveCapEff⁠,バウンディングセットCapBndは,次のようにすべて1が設定された状態になっています。

$ grep Cap /proc/1/status
CapInh: 0000000000000000
CapPrm: 0000003fffffffff
CapEff: 0000003fffffffff
CapBnd: 0000003fffffffff
CapAmb: 0000000000000000

特定のケーパビリティだけが設定されている,ホストの時刻合わせを行うためのデーモンであるntpdは次のようになっています。

$ grep Cap /proc/$(pgrep ntpd)/status
CapInh: 0000000000000000
CapPrm: 0000000002000400
CapEff: 0000000002000400
CapBnd: 0000003fffffffff
CapAmb: 0000000000000000

上記の例のように status ファイルでケーパビリティを確認できます。しかし,このファイルを見ただけでは何かのビットが立っていることはわかっても,それぞれのビットが意味するケーパビリティを覚えていないと,プロセスが何のケーパビリティを持っているかはわかりません。

もう少しわかりやすくプロセスのケーパビリティセットを確認したい場合はlibcapに含まれるgetpcapsコマンドが使えます(Ubuntuの場合はlibcap2-binパッケージに含まれています⁠⁠。

$ getpcaps $(pgrep ntpd)
Capabilities for `21935': = cap_net_bind_service,cap_sys_time+ep

ポート番号1024番未満を使うには特権が必要です。しかし,ntpdは一般ユーザであるntpユーザ権限で動作しており,本来であればntpdで使うポートである123番ポートは使えないはずです。しかし,特権ポートが使えるケーパビリティであるcap_net_bind_serviceを持っているので123番ポートを使えます。

また,システムクロックを設定できるケーパビリティであるcap_sys_timeを持っていますので,本来は一般ユーザではできないシステムクロックの設定ができるわけです。

つまりntpdは必要最低限のケーパビリティのみを持った状態で実行されているということです。不要な特権を持たずにデーモンが実行されていますので,より安全性が高まるというわけです。

著者プロフィール

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

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

Plamo Linuxメンテナ

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