TenForward

技術ブログ。はてなダイアリーから移転しました

ケーパビリティバウンディングセット

今更感たっぷりな話題ですが、今頃ケーパビリティについて調べてます。これまで何度も調べてはよくわからなくて挫折を繰り返してるとこです。

ケーパビリティについては

udzura.hatenablog.jp

が詳しいのですが、「ケーパビリティバウンディングセット」の部分だけはちょっと良くわからんのですよね…(偉そうにスミマセン)。

そして、udzura さんのブログも Ambient については記載がまだありませんので、Ambient についてはこちら、

nojima.hatenablog.com

現時点では日本語ではここしかありません(勉強させてもらいました)。

そういうわけで、ケーパビリティバウンディングセットについて調べていてもなんかよくわからんので調べながらメモ。メモなので脈絡なかったり、急になんの前提もなく何かが出てくるかもしれません。

基本はマニュアル

ただでもややこしいのに英語だとなあ、という場合は日本語訳あるけど古いんですよね。ただ、このケーパビリティバウンディングセットの部分は順序が変わっていたりするものの文章の訳としてはそのまま使える部分がほとんどなので、英語苦手な私には参考になります。Ambient ケーパビリティの説明がない頃のやつなんですよね。

結局、マニュアルをすみずみまで読まないとダメということですね。すみません、つまみ食いしてました。man だから入門書のように書くわけにはいかないけど、機能は全部書かないといけない。そのあたりのバランスを取っている結果か、ひとつの機能についてあっちとこっちに書いてある(ときには別の man page にある)なんてことがあるんだなあと思ったりしてます。

まずはマニュアルの"Thread capability sets"の項にある"Bounding"の説明。

Bounding (per-thread since Linux 2.6.25)
       The capability bounding set is a mechanism that can be used to
       limit the capabilities that are gained during execve(2).

            :(略)

       For more details on the capability bounding set, see below.

まずここには

ケーパビリティバウンディングセットは execve(2) の間に得られるケーパビリティを制限するのに使えるメカニズムです。

とあります。ふむふむ、execve(2)の際に使う機能機能なんだな…

で、ケーパビリティあるあるで、execve(2) の際のケーパビリティ決定のアルゴリズム。これがなければ始まらないという感じですね。

P'(ambient)     = (file is privileged) ? 0 : P(ambient)

P'(permitted)   = (P(inheritable) & F(inheritable)) |
                  (F(permitted) & P(bounding)) | P'(ambient)

P'(effective)   = F(effective) ? P'(permitted) : P'(ambient)

P'(inheritable) = P(inheritable)    [i.e., unchanged]

P'(bounding)    = P(bounding)       [i.e., unchanged]

関係するのはP'(permitted)を決定している所です。

詳しくはこっちを見ろと書かれている "Capability bounding set" の項を見ると、

Capability bounding set

    :(略)

    * During an execve(2), the capability bounding set is ANDed with the
      file permitted capability set, and the result of this operation is
      assigned to the thread's permitted capability set.  The capability
      bounding set thus places a limit on the permitted capabilities that
      may be granted by an executable file.

ここは日本語訳と特に変化してないので、そっちを参照:

    * execve(2) 実行時に、ケーパビリティバウンディングセットと ファイルの許
      可ケーパビリティセットの論理和 (AND) を取ったものが、 そのスレッドの許
      可ケーパビリティセットに割り当てられる。つまり、ケーパビリティバウンディ
      ングセットは、実行ファイルが認めている許可ケーパビリティに対して制限を
      課す働きをする。

つまりファイルケーパビリティで設定されているケーパビリティであってもバウンディングセットで許可されていなければファイルケーパビリティの設定が無効になるということです。

ところが、さきほどのアルゴリズムを見ると、この制限したセット (F(permitted) & P(bounding)) は inheritable の条件である (P(inheritable) & F(inheritable)) との OR ですので、バウンディングセットで制限したとしてもプロセスとファイルの inheritable で許可されていれば、制限できないことになります。これは man の後の段落に書かれています。

Note that the bounding set masks the file permitted capabilities, but
not the inheritable capabilities.  If a thread maintains a capability
in its inheritable set that is not in its bounding set, then it can
still gain that capability in its permitted set by executing a file
that has the capability in its inheritable set.

日本語訳:

バウンディングセットがマスクを行うのは、継承可能ケーパビリティではなく、
ファイルの許可ケーパビリティのマスクを行う点に注意すること。あるスレッ
ドの継承可能セットにそのスレッドのバウンディングセットに 存在しないケー
パビリティが含まれている場合、そのスレッドは、継承可能セットに含まれ
るケーパビリティを持つファイルを実行することにより、許可セットに含ま
れるケーパビリティも獲得できるということである。

あくまでバウンディングセットはファイルケーパビリティの permitted を制限するだけで、inheritable で許可されれば、permitted に追加できてしまいます。これじゃあ制限できないのでは? と思いますが、ここで man の次の項目を見てみましょう。

* (Since Linux 2.6.25) The capability bounding set acts as a limiting
  superset for the capabilities that a thread can add to its inheri‐
  table set using capset(2).  This means that if a capability is not
  in the bounding set, then a thread can't add this capability to its
  inheritable set, even if it was in its permitted capabilities, and
  thereby cannot have this capability preserved in its permitted set
  when it execve(2)s a file that has the capability in its inherita‐
  ble set.

これも日本語訳

* (Linux 2.6.25 以降) ケーパビリティバウンディングセットは、スレッドが
  capset(2) により自身の継承可能セットに追加可能なケーパビリティの母集団
  を制限する役割を持つ。 スレッドに許可されたケーパビリティであっても、
  バウンディングセットに含まれていなければ、スレッドはそのケーパビリティ
  は自身の継承可能セットに追加できず、その結果、継承可能セットにそのケー
  パビリティを含むファイルを execve(2) する場合、そのケーパビリティを許
  可セットに持ち続けることができない、ということである。

プロセス(スレッド)は自身のケーパビリティを操作できます。しかし、バウンディングセットに含まれていないケーパビリティは自身の inheritable に追加できないということです。なので、そこから execve(2) してプログラムを実行する場合は、バウンディングセットに含まれていないケーパビリティはそれ以上持ち続けられない(継承できない)ということです。

つまり元々 inheritable にセットされていないけど、自身に capset(2) を使ってケーパビリティを設定しようと思っても、バウンディングセットに含まれていなければ inheritable に追加できません。結果的に execve(2) してもバウンディングセットに含まれていないケーパビリティを持てないということです。

と思って読んでたら、マニュアルの「ケーパビリティバウンディングセット」の最後に書いてあるやん。

Removing a capability from the bounding set does not remove it from
the thread's inheritable set.  However it does prevent the capability
from being added back into the thread's inheritable set in the
future.
バウンディングセットからケーパビリティを削除しても、スレッドの継承可
能セットからはそのケーパビリティは削除されない。しかしながら、バウン
ディングセットからの削除により、この先そのケーパビリティをスレッドの
継承可能セットに追加することはできなくなる。

capset(2)

ここで capset(2) を使ってのケーパビリティの追加を見てみましょう。同じくマニュアルの CAP_SETPCAP の解説、

CAP_SETPCAP
       If file capabilities are supported (i.e., since Linux 2.6.24):
       add any capability from the calling thread's bounding set to
       its inheritable set; drop capabilities from the bounding set
       (via prctl(2) PR_CAPBSET_DROP); make changes to the securebits
       flags.

          :(略)

日本語訳:

ファイルケーパビリティがサポートされている場合(Linux 2.6.24以降): 呼
び出し元スレッドのバウンディングセットの任意のケーパビリティを 自身の
継承可能ケーパビリティセットに追加できる。 (prctl(2) PR_CAPBSET_DROP
を使って) バウンディングセットからケーパビリティを削除できる。
securebits フラグを変更できる。

こはちょっとカーネルを追ってみました(5.3 kernel)。細かい所は(理解してないので :-p)すっとばしますが、まず capset システムコールkernel/capability.c にあります。その後は

sys_capset (kernel/capability.c)
  security_capset (security/security.c)
    cap_capset (security/commoncap.c) <- call_int_hook マクロ経由

という風に呼ばれ、実際の処理は security/commoncap.c 内の cap_capset 関数で行われているようです。

まずは CAP_SETPCAP を持っていない場合のチェックです。このチェックは cap_inh_is_capped 関数で行っており、 CAP_SETPCAP ケーパビリティを持っていれば 0 を、持っていなければ 1 を返します。

int cap_capset(struct cred *new,
               const struct cred *old,
               const kernel_cap_t *effective,
               const kernel_cap_t *inheritable,
               const kernel_cap_t *permitted)
{
        if (cap_inh_is_capped() &&
            !cap_issubset(*inheritable,
                          cap_combine(old->cap_inheritable,
                                      old->cap_permitted)))
                /* incapable of using this inheritable set */
                return -EPERM;

新たに設定しようとしている inheritable が、現在の "inheritable | permitted" のサブセットであるかどうかをチェックし、そうでなければエラーとなります。

先のチェックが CAP_SETPCAP がないときのお話でしたので、次は CAP_SETPCAP を持っている場合です。

        if (!cap_issubset(*inheritable,
                          cap_combine(old->cap_inheritable,
                                      old->cap_bset)))
                /* no new pI capabilities outside bounding set */
                return -EPERM;

新たに設定しようとしている inheritable が、現在の "inheritable | bset" (bset=バウンディングセット) のサブセットであるかどうかをチェックし、そうでなければエラーとなります。先に引用したマニュアルの「スレッドに許可されたケーパビリティであっても、バウンディングセットに含まれていなければ、スレッドはそのケーパビリティは自身の継承可能セットに追加できず」というのがこの部分でしょうか。

次は新たな permitted のチェックは、

        /* verify restrictions on target's new Permitted set */
        if (!cap_issubset(*permitted, old->cap_permitted))
                return -EPERM;

シンプルに現在の permitted のサブセットであれば OK です。

新しい effective のチェックは、

        /* verify the _new_Effective_ is a subset of the _new_Permitted_ */
        if (!cap_issubset(*effective, *permitted))
                return -EPERM;

新しい permitted のサブセットであれば OK です。

ここまでくれば新たなケーパビリティセットを設定できるということですので、次の 3 行で新たなケーパビリティとして設定しています。

        new->cap_effective   = *effective;
        new->cap_inheritable = *inheritable;
        new->cap_permitted   = *permitted;

最後に Ambient ケーパビリティです。

        /*
         * Mask off ambient bits that are no longer both permitted and
         * inheritable.
         */
        new->cap_ambient = cap_intersect(new->cap_ambient,
                                         cap_intersect(*permitted,
                                                       *inheritable));
        if (WARN_ON(!cap_ambient_invariant_ok(new)))
                return -EINVAL;
        return 0;
}

新たに決定したケーパビリティセットのうち permitted と inherited の両方で有効になっていないケーパビリティは ambient としては許可されない(マニュアルのAmbientの説明部分参照)ので、permitted & inherited の値と AND を取ります。(new->cap_ambient はどこで設定されてるん? <力尽きたので教えて)

マニュアルの "Capability bounding set" の残りの部分で現在のカーネルに相当する部分は日本語訳で OK です(わざわざ転載する必要もないけど説明として引用しておきます)。

カーネルのバージョンにより、ケーパビリティバウンディングセットはシス
テム共通の属性の場合と、プロセス単位の属性の場合がある。

Linux 2.6.25 以降のケーパビリティバウンディングセット

Linux 2.6.25 以降では、「ケーパビリティバウンディングセット」はスレッ
ド単位の属性である (システム共通のケーパビリティバウンディングセットは
もはや存在しない)。

バウンディングセットは fork(2) 時にはスレッドの親プロセスから継承され、
execve(2) の前後では保持される。

スレッドが CAP_SETPCAP ケーパビリティを持っている場合、そのスレッドは
prctl(2) の PR_CAPBSET_DROP 操作を使って自身のケーパビリティバウンディ
ングセットからケーパビリティを削除することができる。いったんケーパビ
リティをバウンディングセットから削除してしまうと、スレッドはそのケー
パビリティを再度セットすることはできない。 prctl(2) の PR_CAPBSET_READ
操作を使うことで、スレッドがあるケーパビリティが自身のバウンディングセッ
トに含まれているかを知ることができる。

バウンディングセットからのケーパビリティの削除がサポートされるのは、
カーネルのコンパイル時にファイルケーパビリティが有効になっている場合
だけである。Linux 2.6.33 より前のカーネルでは、ファイルケーパビリティ
は 設定オプション CONFIG_SECURITY_FILE_CAPABILITIES で切り替えられる追
加の機能であった。Linux 2.6.33 以降では、この設定オプションは削除され、
ファイルケーパビリティは常にカーネルに組込まれるようになった。ファイ
ルケーパビリティがカーネルにコンパイル時に組み込まれている場合、(全て
のプロセスの先祖である) init プロセスはバウンディングセットで全てのケー
パビリティが セットされた状態で開始する。ファイルケーパビリティが有効
になっていない場合には、 init はバウンディングセットで CAP_SETPCAP 以
外の全てのケーパビリティがセットされた状態で開始する。このようになっ
ているのは、 CAP_SETPCAP ケーパビリティがファイルケーパビリティがサポー
トされていない場合には 違った意味を持つからである。

バウンディングセットからケーパビリティを削除しても、スレッドの継承可
能セットからはそのケーパビリティは削除されない。しかしながら、バウン
ディングセットからの削除により、この先そのケーパビリティをスレッドの
継承可能セットに追加すること はできなくなる。

まとめ

ケーパビリティバウンディングセットは、

  • ファイルケーパビリティに設定されている permitted セットを制限する。しかし、プロセスとファイルの inherited で許可されていれば制限が効かない
  • capset(2) で、スレッドのケーパビリティバウンディングセットで設定されているケーパビリティしか、inherited に追加できない

と理解しました。(間違ってたら優しく教えてください)