capability

さと

capabilityには、英語の一般的な意味や経営学用語などで使用されていますが、本記事のcapabilityはLinux Kernel capabilityについてです。
なぜ、Linux Kernel capability(以降、ケイパビリティ)という記事を執筆したのかというと私の中で苦労した開発の中で上位に位置するためです。
当時の開発では、初めからケイパビリティを使って開発します。という事ではなく

ある機能○○とある機能△△を組み合わせて、□□ができるようにしたい。

と、お客様からの要望があり実現方法を調べたところケイパビリティにたどり着きました。
ケイパビリティを使えば実現はできそう?
調べてみると「うん。無理」と言いたくなるほどケイパビリティはめんどくさい。

当時、開発で使用していたカーネルは2.6.12でしたが、ケイパビリティのためにカーネルを3.10LTSに変更しました。
ケイパビリティ自体、カーネルに実装されたのは2.2からです。当時使用していたカーネル2.6.12ではケイパビリティが不十分でした。
ではどのバージョンのカーネルにするか?
カーネルを調べました。2.6.13から3.16まで調査検証した結果、3.10LTSに決まりました。
kernel3.10LTSのケイパビリティセットは、permitted (許可)とeffective (実効)とinheritable (継承)の三つですが、kernel4.3から、ambientが増えてさらに複雑になっています。
それらを踏まえて本題に入ります。

Linux Kernel capability

ある一般ユーザ権限のプロセスが動作するためにrootの一部の権限が必要だがrootの全権限を与えるとセキュリティ上の問題が発生する。
この問題を解決する一つの方法としてケイパビリティがあります。

root権限(以降、特権とする)を細分化し、いくつかのグループに分割したものをケイパビリティと呼びグループ毎に有効、無効を設定できます。
プロセスに特権すべてを与えるのではなく最小限のケイパビリティを与えて必要な処理を行わせます。
もしプロセスに脆弱性が発見され悪用されたとしても、そのプロセスが必要とする最小限のケイパビリティしか奪われないため、被害の範囲を小さくすることが可能になります。
また、rootプロセスは、全てのケイパビリティを持っています。rootプロセスにおいても必要のないケイパビリティを外すことでセキュリティを向上させることができます。

Capabilityの定義

Linux Kernel Capabilityは/usr/include/linux/capability.hに定義されています。

ケーパビリティセット

これはかなり面倒です。
ケイパビリティには「ケイパビリティバウンディングセット」「プロセス(スレッド)ケイパビリティセット」の2種類あります。
ケイパビリティバウンディングセットは、システム全体に対して適用されるケイパビリティセットで、最初にプロセスをroot権限で起動したときに、プロセスが持つケイパビリティのデフォルト値を決めるものです。
プロセス(スレッド)ケイパビリティセットは、PIDを要素とする構造体により定義されており、それぞれのPIDに対して以下の3種類のケイパビリティセットを含んでいます。

  • permitted (許可) プロセスが持つことを許可されているケーパビリティセット
  • effective (実効) プロセスの権限を判定するときに使われるケーパビリティセット
  • inheritable (継承) コマンドを実行するときに継承されるケーパビリティセット


これらの3種類のケイパビリティセットは、プロセスが実行された際に「ケイパビリティバウンディングセット」と「実行前にプロセスが持っていた3種類のプロセスケイパビリティセット」から計算されます。
以下のように定義すると

  • P 【実行前のプロセスのケーパビリティセットの値】
  • P’ 【実行後のプロセスのケーパビリティセットの値】
  • F 【ファイルケーパビリティセットの値】
  • cap_bset 【ケーパビリティバウンディングセットの値】

Linux Kernel 4.3以前の場合、以下のように計算ができます。

  • P'(permitted) = (P(inheritable) & F(inheritable)) | (F(permitted) & cap_bset)
  • P'(effective) = P'(permitted) & F(effective)
  • P'(inheritable) = P(inheritable)

Linux Kernel 4.3以降の場合、以下のように計算ができます。

  • P'(ambient) = (file is privileged) ? 0 : P(ambient)
  • P'(permitted) = (P(inheritable) & F(inheritable)) | (F(permitted) & cap_bset) | P'(ambient)
  • P'(effective) = F(effective) ? P'(permitted) : P'(ambient)
  • P'(inheritable) = P(inheritable)
  • P'(bounding) = P(bounding)

Linux Kernel 4.3以降、ambientケイパビリティセットが追加されたため、複雑になっています。

ケイパビリティの確認

一つのコンソールでpingを実行し続ける。

$ ping 192.168.1.1
PING 192.168.1.1 (192.168.1.1) 56(84) bytes of data.
64 bytes from 192.168.1.1: icmp_seq=1 ttl=63 time=18.9 ms
64 bytes from 192.168.1.1: icmp_seq=2 ttl=63 time=4.72 ms
64 bytes from 192.168.1.1: icmp_seq=3 ttl=63 time=5.72 ms
64 bytes from 192.168.1.1: icmp_seq=4 ttl=63 time=6.79 ms
64 bytes from 192.168.1.1: icmp_seq=5 ttl=63 time=45.5 ms
64 bytes from 192.168.1.1: icmp_seq=6 ttl=63 time=4.48 ms


別のコンソールにて上記pingプロセスの状態を調べてみる。

$ sudo cat /proc/$(pgrep ping | head -1)/status | grep -E ^Cap
CapInh: 0000000000000000 ...プロセス(スレッド)ケーパビリティセット inheritable (継承)
CapPrm: 0000000000003000 ...プロセス(スレッド)ケーパビリティセット permitted (許可)
CapEff: 0000000000000000 ...プロセス(スレッド)ケーパビリティセット effective (実効)
CapBnd: 0000003fffffffff ...ケーパビリティバウンディングセット
CapAmb: 0000000000000000

pingプロセスは、三つのケイパビリティセットとバウンディングセットを持っている事がわかります。
実行中のpingプロセスのCapPrm (許可)を確認してみます。
「CapPrm」の(0000000000003000)HEX を (00000000000000000011000000000000)BITと変換するとbit12とbit13が立っている事が確認できます。

#define CAP_NET_ADMIN 12と#define CAP_NET_RAW 13がpermitted (許可)されています。

つまり、このpingプロセスは「各種のネットワーク関係の操作を実行する」と「RAWソケットとPACKETソケットを使用する」が特権で実行できます。

ケーパビリティ操作コマンド

  • getcap:ケーパビリティ設定状況取得
  • setcap:ケーパビリティの設定

ケイパビリティ操作コマンドを使った動作確認1

通常、一般ユーザのlsコマンドでは/root配下が参照できません。

ケイパビリティのCAP_DAC_READ_SEARCHで読み込み時パーミッションチェックをスキップする権限をls2コマンドに付与する。

これにより、sudoコマンドを使わなくても/root配下を参照することができます。

$ ls /root.......①
ls: ディレクトリ '/root' を開くことが出来ません: 許可がありません
$ which ls
/bin/ls
$ cp /bin/ls ~/ls2.....②
$ getcap ~/ls2.....③
$ sudo setcap cap_dac_read_search=+ep ~/ls2....④
[sudo] user のパスワード:
$ getcap ~/ls2....⑤
/home/user/ls2 = cap_dac_read_search+ep
$ ~/ls2 -a /root....⑥
. .. .bash_history .bashrc .cache .gnupg .profile
$
$ cp ~/ls2 ~/ls3....⑦
$ ~/ls3 -a /root...⑧
/home/user/ls3: ディレクトリ '/root' を開くことが出来ません: 許可がありません
$
$ sudo setcap -r ~/ls2...⑨
$ ~/ls2 /root
/home/user/ls2: ディレクトリ '/root' を開くことが出来ません: 許可がありません

  1. lsコマンドで/root配下を参照⇒失敗
  2. lsコマンドをコピーしls2コマンドを作成
  3. ls2コマンドのケーパビリティ確認
  4. ls2コマンドにケーパビリティ(CAP_DAC_READ_SEARCH=+ep)を設定
    epは、ケーパビリティセットの「effective (実効) 」と「permitted (許可)」です。effectiveとpermittedをオンにします。
  5. 再度ls2コマンドのケーパビリティ確認
  6. ls2コマンドで/root以下を参照⇒成功
  7. ls2コマンドをコピーしてls3コマンド作成
  8. ls3コマンドで/root以下を参照⇒失敗
  9. ケーパビリティの削除

setuidでは、ファイルをコピーすると権限もコピーされるがケーパビリティを付与したファイル(ls2)をコピーしたファイル(ls3)には権限は付与されません。

ケイパビリティ操作コマンドを使った動作確認2

/bin/pingコマンドをコピーしたping2コマンドにケイパビリティ(CAP_NET_RAW)の付与と削除を行ってみる。

/bin/pingコマンドには、setuidは設定されていませんが、CAP_NET_RAWが設定されていますので一般ユーザも実行できます。

しかし、コピーしたping2コマンドは、/bin/pingコマンドに付与されているケイパビリティ(CAP_NET_RAW)はコピーされません。

$ cp /bin/ping ~/ping2...........①
$ getcap ~/ping2.................②
$ ~/ping2 192.168.1.1................③
ping: icmp open socket: Operation not permitted
$ sudo setcap cap_net_raw=+ep ~/ping2...........④
$ getcap ~/ping2..................⑤
.~/ping2 = cap_net_raw+p
$ ~/ping2 192.168.1.1 -c 1
PING 192.168.1.1 (192.168.1.1) 56(84) bytes of data.
64 bytes from 192.168.1.1: icmp_seq=1 ttl=128 time=1.95 ms

--- 192.168.1.1 ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 1.953/1.953/1.953/0.000 ms
$ sudo setcap -r ~/ping2.............⑦
$ getcap ~/ping2.....................⑧
$ ~/ping2 192.168.1.1 -c 1................⑨
ping: icmp open socket: Operation not permitted
  1. pingコマンドをコピーしping2とする
  2. コピーしたping2にケーパビリティは付与されていない事を確認
  3. 一般ユーザーでping2コマンドを実行⇒許可がなく失敗
  4. setcapを使ってケーパビリティをセット
    ソケットを使用するため、CAP_NET_RAWにpermittedを設定する。permittedをオンにします。
  5. コピーしたping2コマンドにケーパビリティは付与されている事を確認
  6. ping2コマンドを実行⇒成功
  7. ケーパビリティの削除
  8. ping2コマンドにケーパビリティは付与されていない事を確認
  9. ping2コマンドを実行⇒許可がなく失敗

まとめ

  • 一般ユーザ権限で動作するプロセスでもケイパビリティを設定することにより、部分的にrootの権限を付与し動作させることができる。
  • プロセス(スレッド)は、3つのケイパビリティセット+バウンディングセットを持っている。
  • ケイパビリティの権限はコピーできません。
この記事を書いた人
この記事を書いた人

Linux kernel/デバイスドライバ/スクリプト/バナナチップ

ベトナム人の友人が来日するたびにベトナムのバナナチップをお土産に持ってきてくれます。毎回です。別なものをお土産にと言いたいのですが、毎回違うメーカーのバナナチップを持ってくるので気になって食べてしまう私です。
「サトサン、バナナチップダヨ。」

さとをフォローする
さと
さとをフォローする
Tech BLOG

コメント

タイトルとURLをコピーしました