Linuxケーパビリティ(2/3)
ケーパビリティバウンディングセットを操作するのではなく、個々のプロセスごとに与えられるケーパビリティ――プロセスケーパビリティを調節したい場合、あらかじめCAP_SETPCAPが与えられている必要がある。しかし実際に調べてみると分かる通り(調べ方は後述)、通常のカーネルにおいては、すべてのプロセスの親であるinitからしてCAP_SETPCAPが与えられていない。またケーパビリティバウンディングセットにおいてもCAP_SETPCAPは無効になっているから、すべてのプロセスについてCAP_SETPCAPが与えられるチャンスはない。
これは缶詰の中に缶切りが入っている状態だ。したがってプロセスケーパビリティを調節してシステム運用をしたい場合には、まずカーネルに手を加える必要がある。具体的にはinclude/linux/capability.hのCAP_INIT_EFF_SETとCAP_INIT_INH_SETをLinux kernel capabilities FAQの説明の通りに変更する。
--- capability.h.orig 2003-07-14 14:07:59.000000000 +0900 +++ capability.h 2003-08-06 03:45:05.000000000 +0900 @@ -303,8 +303,8 @@ #define CAP_EMPTY_SET to_cap_t(0) #define CAP_FULL_SET to_cap_t(~0) -#define CAP_INIT_EFF_SET to_cap_t(~0 & ~CAP_TO_MASK(CAP_SETPCAP)) -#define CAP_INIT_INH_SET to_cap_t(0) +#define CAP_INIT_EFF_SET to_cap_t(~0) +#define CAP_INIT_INH_SET to_cap_t(~0) #define CAP_TO_MASK(x) (1 << (x)) #define cap_raise(c, flag) (cap_t(c) |= CAP_TO_MASK(flag))
変更した後、カーネルの再構築を行い、システムを再起動する。この時点では変更前と何ら変わったところは見られないはずだ。それではプロセスごとのケーパビリティを設定する方法を学ぶことにしよう。
ケーパビリティセット
各プロセスのプロセスケーパビリティが決定される根拠となるものは、プロセスケーパビリティセットとファイルケーパビリティセットという二つのケーパビリティセットである。二つのケーパビリティセットのうち、プロセスケーパビリティセットはプロセスに対して設定されるものでeffectiveセット、inheritableセット、permittedセットの三種類のセットがある。それぞれのセットの意味は以下の通りだ。
- effectiveセット
- このセットに含まれているケーパビリティがそのプロセスの実行時の権限となる。たとえ他のセットに含まれているケーパビリティがあっても、このセットになければそのケーパビリティに関連する操作を行うことはできない。
- inheritableセット
- このセットに含まれているケーパビリティは、そのプロセスにおいてexecした後でも引き継がれる。このセットに含まれていないケーパビリティはexecには失われる(ケーパビリティの継承のメカニズムの詳細については後述)。
- permittedセット
- このセットに含まれているケーパビリティであれば、effectiveセットとinheritableセットに任意に入れることができる。逆にpermittedセットにないケーパビリティについては自力で有効にすることはできないため、permittedセットから抜いてしまったケーパビリティを後で復帰するということはできない(ただし、そのケーパビリティがまだinheritableセットにあり、かつfork & execして特権をもった別のプロセスを生成し、そのプロセスにpermittedセットを操作させることで復帰することは可能。後述のケーパビリティの継承のメカニズムを参照)。
もう一方のファイルケーパビリティセットは実行ファイルに対して設定されるもので、allowedセット、forcedセット、effectiveセットの三種類のセットがある。それぞれの意味は以下の通りだ。
- allowedセット
- exec前から引き継がれたケーパビリティに対するマスク。
- forcedセット
- exec前から引き継がれたどうかによらず与えられるケーパビリティ。
- effectiveセット
- exec後のプロセスに設定されるeffectiveセットに対するマスク。
本来はこれらのファイルケーパビリティセットについては何らかの方法でファイルに対して結び付けておくべきものなのであるが、現状はケーパビリティを格納しておく場所が用意されていないため、次のような形でエミュレートされる。
- 特権がある場合かsuid rootプログラムが起動される場合、各セットにすべてのケーパビリティが入れられる。
- それ以外の場合(特権がない場合)には各セットはいずれも空にされる。
あるプログラムを実行したとき、そのプログラムのための三つのプロセスケーパビリティセットにどのようなケーパビリティが設定されるかは、execする前のプロセスケーパビリティセットとexecされるプログラムのファイルケーパビリティセットから決定される。次にそのメカニズムを説明しよう。
ケーパビリティの継承
execのタイミングで次のようにプロセスケーパビリティセットが調整される。プロセスの実際の動作が始まるのはこの調整の後になる。
P'(permitted) = (P(inheritable) & F(allowed)) | (F(forced) & cap_bset)
P'(effective) = P'(permitted) & F(effective)
P'(inheritable) = P(inheritable)
(変化しない)
ここでP(name)
はexecする前のケーパビリティセットを、P'(name)
はexec後のケーパビリティセットを、F(name)
はファイルケーパビリティセットを、cap_bset
はケーパビリティバウンディングセットを、それぞれ表している。
ここから分かるように、ケーパビリティバウンディングセットのすべてのケーパビリティが有効(改造カーネルの起動時の状態)になっていると、特権さえあればexecを介することによってすべてのケーパビリティを得ることができてしまう(F(forced)
とcap_bset
がそれぞれオール1のためP'(permitted)
もオール1になり、さらにP'(effective)もオール1になる
)。これではいくらプロセスケーパビリティを調節しても台無しだ。したがって、プロセスケーパビリティを使ったシステム運用を行うことを決定したら、システム起動時のできるだけ早い時期に/proc/sys/kernel/cap-boundに0を書き込むようにするか、少なくとも本当に必要なケーパビリティ以外を無効にしておかなければならない。
ちなみに、通常のカーネルにおいて、結果的にケーパビリティバウンディングセットによってプロセスのケーパビリティを支配できるというのも、上のような調整によるものである。つまりinheritableセットがオール0であるため、P'(permitted)
およびP'(effective)
はcap_bset
によって決定されることになる。要するに引き継ぐものが何もないため、結果的にケーパビリティバウンディングセットの値が適用されるというわけだ。