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

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

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

前回はプロセスに設定されているケーパビリティと,ファイルにあらかじめ設定しておくファイルケーパビリティについて説明しました。

今回はまず,execve(2) システムコールを使ってプログラムを実行する際にケーパビリティがどのように変化するのかを説明したあと,Ambientケーパビリティとケーパビリティバウンディングセットについて説明します。

プログラム実行時のケーパビリティ

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後のケーパビリティの計算とファイルケーパビリティ

ここで一番複雑に見えるのは式(2)のPermittedケーパビリティの計算です。

式(1)のAmbientケーパビリティについてはあとで詳しく説明しますので,まずは式(2)を見てみましょう。

式(2)の最初の部分"P(inheritable) & F(inheritable)",ふたつめの部分"F(permitted) & P(bounding)",最後の部分P'(ambient)はORですので,いずれかで許可されれば,execve(2)後のPermittedケーパビリティで許可されます。

AmbientP'(ambient)を除いたいずれも,ファイルケーパビリティがそれぞれ関係しています。この式(2)を見れば,ファイルケーパビリティの3つのケーパビリティの役割は明確です。

ファイルケーパビリティで設定できるそれぞれのケーパビリティセットを紹介しながら,この式(2)の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)のように,ファイルケーパビリティのEffectiveケーパビリティが,

設定されている場合
アルゴリズムで計算したexecve(2)後のPermittedケーパビリティP'(permitted)の値がexecve(2)後のEffectiveケーパビリティP'(effective)に設定されます
設定されていない場合
execve(2)後のAmbientケーパビリティの値P'(ambient)execve(2)後のEffectiveケーパビリティP'(effective)に設定されます

Ambientケーパビリティ

ここまで説明したファイルケーパビリティを使えば,一般ユーザに必要なケーパビリティを与えてプロセスを実行できます。先のpingコマンドのように,システム上のユーザ誰にでも必要なケーパビリティを与えたいという場合にはファイルケーパビリティが有効です。

ところがファイルケーパビリティはファイルに属性を持たせますので,誰が実行した場合でもその特権を与えた状態でプロセスが実行されます。

セキュリティ的に必要な特権を与える範囲を最小限に限定したいという場合,例えば一般ユーザ権限で必要な特権を持ったプログラムは実行したいけれども,誰でもそのプログラムを実行できては困るという場合には対応できません。

親プロセスが持っているケーパビリティの一部だけを継承し,一般ユーザ権限でプロセスを実行できれば,不要に広い範囲にケーパビリティを与えることにはなりません。

このような場合に使うのがAmbientケーパビリティです。このAmbientケーパビリティは比較的新しい機能で,Linux 4.3で追加されました。

この機能が追加されるまで,先に紹介したアルゴリズムは次のように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ケーパビリティは,設定する時点でPermittedケーパビリティとInheritableケーパビリティの両方で目的のケーパビリティが有効にされていなければ設定できません。

また,PermittedケーパビリティとInheritableケーパビリティから目的のケーパビリティが削除されると,Ambientケーパビリティからもそのケーパビリティは削除されます。

そして,アルゴリズムの(1)のように,setuidsetgidファイルケーパビリティといった,ファイル自体に特権を与えるような設定がされていない場合にのみAmbientケーパビリティが子プロセスに継承されます。

そして(2⁠⁠,⁠3)の式のようにファイルケーパビリティに関わらず,Ambientケーパビリティは継承されます。

先に書いたような目的の場合に,誰もが期待するように動作するすっきりとした機能です。

Ambientケーパビリティは説明だけでも比較的わかりやすい機能かもしれませんが,一応実行例を示しておきましょう。

前回の実行例のように,ファイルケーパビリティもsetuidも設定されていないpingコマンドをAmbientケーパビリティを使って実行してみましょう。

UbuntuやCentOS 8でインストールされるlibcap 2.25にはAmbientケーパビリティを操作する機能がありませんので,次の例はPlamo Linux 7.1上でlibcap 2.27を使っています。

$ 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コマンドは指定した順でオプションが処理されますので,指定する順番が変わるとエラーになる可能性があります。

オプションを与えた順に処理されますので,この例のように実行すると,

  1. Ambientケーパビリティを設定するために
    • cap_setpcapを親プロセスcapshコマンド)に設定(このケーパビリティがないとIhneritableにケーパビリティを設定できません)
    • uid=1000でコマンドを実行するためにcap_setuid,cap_setgidを親プロセスに設定
    • Permitted,InheritableケーパビリティがないとAmbientに設定できないのでcap_net_rawを親プロセスに設定
  2. --keep=1はあとで説明するsecurebitsフラグを設定(このフラグを設定する際にもcap_setpcapが必要)
  3. 一般ユーザ権限で実行するために--uid=1000を指定
  4. --addamb=cap_net_rawpingコマンドの実行に必要なcap_net_rawをAmbientケーパビリティに設定
  5. capsh実行時の状態を確認するために--printオプションを指定

Current行でオプションで設定したケーパビリティが設定されていること,Ambient set行でcap_net_rawが設定されていることが確認できます。

Ambientケーパビリティを設定したので,pingコマンドが実行できています。

著者プロフィール

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

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

Plamo Linuxメンテナ

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