レッドハットの森若です。今回は昔から存在するのですがあまり利用されていなかったCapabilityについて、RHEL7で活用されているという話です。
謎のエラー
以下の操作は、RHEL6では問題なく動作するのですがRHEL7では失敗します。
$ ping localhost &>/dev/null & #バックグラウンドでping $ renice 15 $! # nice値を設定して優先度を下げる
今回はなぜこのような違いが発生するのか、その背景を見ていきましょう。
RHEL7でこの操作をすると、以下のようなエラーが出力されます。
$ renice 15 $! renice: failed to set priority for 4609 (process ID): Operation not permitted
権限管理を追跡
rootでreniceをやってみます。
# renice 15 4609 4609 (process ID) old priority 0, new priority 15
今度は成功しました。エラーメッセージも「Operation not permitted(操作が許可されていない)」というものでしたが、renice処理そのものが何かの理由でできなくなっているわけではなく、なにか権限の問題で失敗しているようです。
もう一度同じことをしてpingプロセスの状態を見てみます。
$ ping localhost &> /dev/null & [2] 2850 $ cat /proc/$!/status Name: ping State: S (sleeping) Tgid: 2850 Ngid: 0 Pid: 2850 PPid: 2693 TracerPid: 0 Uid: 1000 1000 1000 1000 Gid: 1000 1000 1000 1000 FDSize: 256 Groups: 10 1000 VmPeak: 116500 kB VmSize: 116476 kB VmLck: 0 kB VmPin: 0 kB VmHWM: 996 kB VmRSS: 996 kB VmData: 264 kB VmStk: 136 kB VmExe: 40 kB VmLib: 2160 kB VmPTE: 60 kB VmSwap: 0 kB Threads: 1 SigQ: 0/15086 SigPnd: 0000000000000000 ShdPnd: 0000000000000000 SigBlk: 0000000000000000 SigIgn: 0000000000000000 SigCgt: 0000000000002006 CapInh: 0000000000000000 CapPrm: 0000000000003000 CapEff: 0000000000000000 CapBnd: 0000001fffffffff Seccomp: 0 Cpus_allowed: f Cpus_allowed_list: 0-3 Mems_allowed: 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000001 Mems_allowed_list: 0 voluntary_ctxt_switches: 15 nonvoluntary_ctxt_switches: 1
reniceはPermission deniedと出て失敗するので権限の問題らしいですが、uid, gidは自分のもの(1000)のままです。特に変なところがありません。これは通常のuid, gidから独立した権限管理が影響していそうです。
statで/usr/bin/pingの状態を確認してみましょう。
$ stat /usr/bin/ping File: ‘/usr/bin/ping’ Size: 44896 Blocks: 88 IO Block: 4096 regular file Device: fd04h/64772d Inode: 21449341 Links: 1 Access: (0755/-rwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root) Context: system_u:object_r:ping_exec_t:s0 Access: 2015-08-28 19:40:27.213100343 +0900 Modify: 2015-06-08 15:42:30.000000000 +0900 Change: 2015-07-24 12:37:03.285137514 +0900 Birth: -
setuidはついていませんね。uid, gidが同じであっても有効な権限管理としては、ファイルシステムのattribute, SELinux, ACL, Capability があります。
ファイルシステムのattributeは、ファイルの操作を(uid, gid, パーミッションの設定が同一であっても)制限することができます。今回はファイルのアクセスについての話ではないので影響しなさそうです。念のためlsattrコマンドでattributeを確認すると、どのフラグも立っていないので何も設定されていないことがわかります。
$ lsattr /usr/bin/ping ---------------- /usr/bin/ping
SELinuxの場合、特別に記録を禁止されていなければ/var/log/audit/audit.log にログが残ります。またRHEL7ではシステムログにもSELinuxでアクセスが禁止された旨の出力がおこなわれますが、reniceが失敗したような記録はありません。念のため setenforce 0 としてSELinuxをPermissiveにして実験しても同じようにreniceが失敗します。
ACLでしょうか? これもファイルシステムのattributeと同様ファイルの操作にひもづくので望み薄ですが一応確認しましょう。getfaclコマンドで確認してみると、通常のパーミッション以上の設定は行われていないことがわかります。
$ getfacl /usr/bin/ping getfacl: Removing leading '/' from absolute path names # file: usr/bin/ping # owner: root # group: root user::rwx group::r-x other::r-x
Capabilityはどうでしょう? これは追加の権限を与えるものです。getcapコマンドでファイルのCapabilityを確認すると、cap_net_admin, cap_net_rawという権限が付与されていました。これがあやしいですね。
$ getcap /usr/bin/ping /usr/bin/ping = cap_net_admin,cap_net_raw+p
Capabilityとは?
Capability とは何でしょう? rootユーザの権限を細かく分割したものだと考えることができます。rootユーザは一般ユーザでは禁止されている様々な操作が可能ですが、それぞれを分類し、名前をつけて個別に付与するなどの管理ができるようにしたものがCapabilityです。詳しくはman capabilities(7)を確認してみましょう。rootが持っている特権的な操作が列挙・分類されているので、capabilityを利用する予定がない人にも面白いとおもいます。
RHEL6ではpingにsetuidを使ってroot権限を利用していました。RHEL7ではcapabilityにより最小限の権限を付与しています。
RHEL6ではsetuidを使い、実行ファイルpingのownerがrootユーザなのでpingを実行するとrootユーザの権限が自動的に与えられます。root権限が必要な処理をしたあとsetuid(),setgid()によりpingプロセス自身を元のユーザの権限に制限しています。
RHEL7ではcapabilityを使い、ネットワーク管理に必要な操作を行う権限と、RAWソケットをopenして任意のパケットを送信する権限が与えられます。
この変更はセキュリティを改善するために導入されました。
pingの実装にセキュリティ上の問題があり、任意のコマンド実行が可能だと仮定してみましょう。この時rootユーザ権限で動作していると、/etc/shadowを一般ユーザが覗き見したり、/bin/bashを書き替えて何か悪いことをすることができてしまいます。
一方RHEL7のCapabilityでは、ネットワーク設定の変更やパケットの送信までは可能ですがファイルへのアクセスなどは元のユーザの権限のままです。このため、pingに未知の問題があったとしても被害が限定されます。
さきほどの/proc/$!/status での出力をよくみると、以下のような行があってCapabilityが追加されていることがわかります。
CapPrm: 0000000000003000
なぜreniceに失敗するのか?
さてRHEL7のpingはCapabilityで権限が追加されていることがわかりました。ではなぜreniceに失敗するのでしょうか?
該当するソースコードをlinux kernelのsecurity/commoncap.c から引用します。__task_cred(p)がreniceの対象になるプロセス(今回の例ではping)、current_cred() がreniceを実行しているプロセス(今回の例ではrenice)です。この2つの権限を比較して、サブセットになっていなければ失敗するようになっています。
/* * Rationale: code calling task_setscheduler, task_setioprio, and * task_setnice, assumes that * . if capable(cap_sys_nice), then those actions should be allowed * . if not capable(cap_sys_nice), but acting on your own processes, * then those actions should be allowed * This is insufficient now since you can call code without suid, but * yet with increased caps. * So we check for increased caps on the target process. */ static int cap_safe_nice(struct task_struct *p) { int is_subset; rcu_read_lock(); is_subset = cap_issubset(__task_cred(p)->cap_permitted, current_cred()->cap_permitted); rcu_read_unlock(); if (!is_subset && !capable(CAP_SYS_NICE)) return -EPERM; return 0; }
一方のRHEL6では、一旦root権限を得るものの(この時はreniceできません)、その後setuid(), setgid()にて権限を元のユーザのものに戻すため、reniceをすることができます。
関連資料
ということでpingをreniceできないという謎の裏にはセキュリティを強化するための仕組みが隠れていました。あまり馴染みのない権限管理の仕組みがでてきたと思います。関連するドキュメントを記載しておきます。