2015年 8月 の投稿一覧

RHEL7でpingをreniceしようとすると失敗する話

Red Hat Enterprise Linux 7では、linuxのcapabilityを利用してsetuidの利用を少なくしています。capabilityについてあまり馴染みがない方も多いかと思います。トラブルシュート形式で、capabilityの利用時の特徴的なふるまいを見てみましょう。

レッドハットの森若です。今回は昔から存在するのですがあまり利用されていなかった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できないという謎の裏にはセキュリティを強化するための仕組みが隠れていました。あまり馴染みのない権限管理の仕組みがでてきたと思います。関連するドキュメントを記載しておきます。

man capabilities , (和訳)

man acl(和訳)

man chattr(和訳)

Performance Co-Pilotでパフォーマンス測定を簡単にしよう

Red Hat Enterprise Linux 7(そして6.7)にはパフォーマンス監視や記録のために、従来のsysstatなどに加えてPerformance Co-Pilot(PCP)が含まれました。PCPは特に複数台からなる大規模なシステムのパフォーマンス監視に便利な特長を多くもっています。今回はPCPをご紹介します。

レッドハットの森若です。Red Hat Enterprise Linux 7(そして6.7)にはパフォーマンス監視や記録のために、従来のsysstatなどに加えてPerformance Co-Pilot(PCP)が含まれました。

PCPは2000年にSGIがIRIX用に出荷してから現在まで15年ほどの歴史があリ、特に複数台からなる大規模なシステムのパフォーマンス監視に便利な特長を多くもっています。

Performance Co-Pilot(PCP)とは?

Performance Co-PilotはCPU使用率やディスクアクセスなどのパフォーマンスメトリクスを収集、分析するためのフレームワークです。データ収集のためのデーモンと、さまざまな形で出力するためのクライアントが提供されています。

PCPのアーキテクチャはデータ収集をするデーモンとクライアントにわかれています。クライアントはローカルホストだけでなく、リモートサーバへの接続ができるので、リモート環境や複数台からなる環境のパフォーマンス監視をごく自然におこなうことができます。

データ収集デーモンには、収集をおこないクライアントからの接続を受けつけるpmcdと、実際に測定対象からパフォーマンスデータを収集するpmdaの2種類のプログラムが登場します。pmdaは測定対象ごとに、kernel, linux, cisco, mounts, などのコンポーネントに分かれていて必要なものだけを選択して利用します。pmdaはユーザが新しく作成して拡張することもできます。

 

PCPの名前空間

PCPであつかうメトリクスは名前、データ型、単位の組み合わせで定義されており、名前はツリー状の構造を持っています。たとえばCPUの使用量であれば、kernel.percpu.cpu.user のようにPMDAの名前からはじまり、順に分類が細かくなっていきます。現在のシステムで利用可能な測定値の名前一覧はpminfoコマンドで取得できます。

各項目の説明はpminfo -t で、現在の値はpminfo -fで取得できます。各項目には複数のインスタンスを持つことができるので、たとえばディスク性能の項目についてはパーティション毎などのインスタンスが含まれます。以下はpminfoで項目の意味と値や単位を確認する例です。

$ pminfo -dtf disk.partitions.read

disk.partitions.read [read operations metric for storage partitions]
    Data Type: 32-bit unsigned int  InDom: 60.10 0xf00000a
    Semantics: counter  Units: count
    inst [0 or "sdb1"] value 29668
    inst [1 or "sda1"] value 257
    inst [2 or "sda2"] value 2
    inst [3 or "sda5"] value 120259
    inst [4 or "sr0"] value 0
    inst [5 or "sdc1"] value 358

特殊な項目としてハードウェアインベントリhinvがあり、この項目はCPU数やメモリの総量など基本的に変化しないメトリクスを保持します。

PCPのクライアント

PCPではあらかじめ様々な種類のクライアントが用意されています。クライアントの一部を眺めてみましょう。

GUIでグラフを作成するpmchart

vmstat風の出力を出してくれるpmstat

top風の出力をおこなうpmatop

オプションのクエリにしたがって任意のメトリクスを定期的に表示するpmval

トリガの設定とコマンド実行

クライアントの1つであるpmieでは専用の言語によりメトリクスを使った各種の条件でトリガーを設定し、コマンド実行やログ出力を行います。pmieconfコマンドであらかじめ用意されたテンプレートをカスタマイズしてpmieの設定を作成することもできます。

以下はpmieconfが生成する設定の例です。2分おきに測定し、ネットワークインタフェースのどれか1つでも帯域利用率が85%を越えていれば利用率とインタフェースをログに出力します。1度出力を行うと10分間は警告の出力を抑制します。

delta = 2 min;
per_netif.util =
some_inst (
    ( 100 * network.interface.total.bytes   /
       network.interface.baudrate   )
          > 85
    && network.interface.baudrate   > 0
) -> syslog 10 min "High network interface utilization" " %v%util[%i]@%h";

PCPのアーカイブ

PCPはsarとおなじように定期的に測定したパフォーマンスメトリクスをアーカイブへ保存することができます。クライアントとして実装されたpmloggerがアーカイブを作成します。それぞれのメトリクス毎にアーカイブへ記録する頻度を設定でき、たとえばCPU使用率は30秒ごとに、ディスク使用率は10分ごとに記録するような設定が可能です。pmloggerもリモート接続が可能なので、測定対象のサーバ上でもデータ収集用のサーバでも実行できます。

sarのアーカイブでは残念ながらバージョンやアーキテクチャによる非互換がありますが、PCPのアーカイブはOSやアーキテクチャから独立したフォーマットが策定されています。さらにPCPのバージョンからも独立しており、ドキュメントによれば15年前に保存されたアーカイブであっても最新版のPCPで問題なく読み込むことができるとのことです。このような特徴があるため、複数のバージョンやOSが混在している環境のパフォーマンス監視にも活躍します。

クライアントライブラリはリアルタイムの情報収集とアーカイブ参照の両方に対応しているので、ほとんどのクライアントはリアルタイムの確認と過去データのリプレイのどちらにも対応しています。過去のデータも現在のデータと同じように確認できるため、現在の性能と過去の性能を比較するような作業が容易になります。

他プログラムとの連携

PCPでは各種言語のライブラリが整備されており、たとえばLinuxサーバやDockerの管理インタフェースとしてRHEL7.1から導入されたcockpitも性能情報はPCP経由で取得しているなど、他のプログラムからの利用が容易になっています。ベンチマークなどで高度な分析をおこないたい場合にも、アーカイブをPythonなどのプログラムから読み込むことで様々な活用が可能となります。

関連情報

  • man PCPIntro