POSIX ファイル・ケイパビリティー root の権限を分配する

より多くのユーザーに安全に root の権限を分け与える方法

Linux® は長年、ケイパビリティーを使用してきましたが、POSIX ファイル・ケイパビリティーが備わったのは最近のことです。POSIX ファイル・ケイパビリティーは root ユーザーの権限を、例えばファイルを読み取る機能や、別のユーザーが所有するプロセスをトレースする機能などの特権に分割します。このようなケイパビリティーをファイルに割り当てることで、特権を持たないユーザーでも、指定されたこれらの特権によってファイルを実行できるようにすることができます。この記事で、ケイパビリティーを使ったプログラミングの手法、そしてシステムの setuid root バイナリーがファイル・ケイパビリティーを使用できるようにする方法を学んでください。

Serge E. Hallyn (sergeh@us.ibm.com), Software Engineer, IBM

Serge Hallyn は、Linux カーネルおよびセキュリティーを専門とする IBM Linux Technology Center のメンバーです。College of William and Mary のコンピューター・サイエンスで博士号を取得した彼は、これまでに複数のセキュリティー・モジュールを作成して貢献してきました。現在彼が専門に取り組んでいるのは、仮想サーバーの機能、アプリケーションのチェックポイント/再起動、そして POSIX ファイル機能のサポートを追加することです。



2007年 10月 16日

一部のプログラムには、特権を持たないユーザーに代わって特権が必要な操作を実行しなければならないものがあります。その一例は、機密性の高い /etc/passwd および /etc/shadow ファイルに書き込む passwd プログラムです。UNIX® システムでは、このプログラムの制御はバイナリー・ファイルに setuid ビットをセットすることで行います。このビットがセットされていると UNIX システムは、passwd プログラムが実行されている間 (誰が実行したかには関わらず)、passwd プログラムはそのファイルを所有するユーザーに属しているという扱いをします。このファイルを所有するユーザーとは通常、root ユーザーです。passwd プログラムは非特権ユーザーによる書き込みが不可能であるだけでなく、ユーザーが行える操作についても非常に制約されているため、通常はこの設定で危険はありませんが、複雑なプログラムでは保存 uid を使って root ユーザーと非 root ユーザーを切り替えます。

POSIX ケイパビリティーは root 特権を分割し、root ユーザー特権のサブセットだけでタスクを実行できるようにします。このような特権をプログラムに追加させるのがファイル・ケイパビリティーで、これによってケイパビリティーが、はるかに使用しやすくなります。POSIX ケイパビリティーは長年、Linux で使用されてきました。ケイパビリティーを使用すると、root ユーザーとしての場合と比べて以下のメリットがあります。

exec(3)

Linux マニュアル・ページより: exec() ファミリーの関数は、現行のプロセス・イメージを新しいプロセス・イメージに置き換えます。詳細は、「参考文献」を参照してください。

  • ケイパビリティーが不注意にも不正に使用されないように、ケイパビリティーを実効セットからは削除できる一方、許可セットにはそのまま保持することができます。
  • 再取得できないケイパビリティーなど、不要になったすべてのケイパビリティーを許可セットから削除することができます。ケイパビリティーのほとんどは危険で、不正使用される恐れがあるのは確かです。けれどもアタッカーから利用することができるケイパビリティーを減らせば、システムへの被害を防ぐことになります。
  • 通常の実行可能ファイルの exec(3) によって、すべてのケイパビリティーがクリアされます (詳細は複雑なので、もうすぐ変更されるはずです。これについては、この記事の後で説明します)。

この記事では、プログラムが POSIX ケイパビリティーを活用するための方法、そしてプログラムに必要なケイパビリティーを調べ、必要とされるケイパビリティーをプログラムに割り当てる方法を説明します。

プロセス・ケイパビリティー

これまで長い間、POSIX ケイパビリティーはプロセスに割り当てることはできても、ファイルには割り当てられませんでした。そのためプログラムがその root 特権の一部を分け与え、残りの特権を維持するには、まずプログラムを root によって起動 (または、root が所有して setuid ビットをセット) する必要がありました。さらに、ケイパビリティーを与える順序も以下のように極めて特定されていました。

  1. プログラムがシステムに、ケイパビリティーを維持すると同時に実効 userid を変更する必要があることを通知します。この通知は prctl を使って行われます。
  2. プログラムが userid を root 以外の値に変更します。
  3. プログラムが必要なケイパビリティーのセットを構成し、これらのケイパビリティー・セットをプログラムのアクティブ・セットにします。

プロセスには、許可 (P)、継承 (I)、実効 (E) という 3 つのケイパビリティー・セットが割り当てられます。プロセスが fork するときには、親プロセスから子プロセスにケイパビリティー・セットがコピーされます。プロセスが新しいプログラムを実行すると、この後説明する公式に従って、プロセスの新しいケイパビリティー・セットが計算されます。

実効セットには、プロセスが現在使用できるケイパビリティーが含まれます。実効セットは必ず、許可セットのサブセットでなればなりません。実効セットが許可セットの範囲内に収まっている限り、プログラムは随時、実効セットの中身を変更することができます。継承セットは、exec() の実行後に新しいケイパビリティーを計算するためだけに使用されます。

リスト 1 に、ファイルの実行後にプロセスの新規ケイパビリティー・セットを決定する 3 つの公式を記載します。この 3 つの公式は POSIX ドラフトに準拠しています (IEEE Std 1003.1-2001 へのリンクは「参考文献」を参照)。

リスト 1. exec() 実行後の新規ケイパビリティー・セットの公式
pI' = pI
pP' = fP | (fI & pI)
pE' = pP' & fE

'」で終わる値は新しく算出された値、p で始まる値はプロセス・ケイパビリティー、f で始まる値はファイル・ケイパビリティーを示します。

プロセスの継承セットは親プロセスからそのまま引き継がれるため、いったんプロセスが継承セットからケイパビリティーを分け与えると、そのケイパビリティーを取り戻せなくなります (ただし、以下の SECURE_NOROOT についての説明を読んでください)。プロセスの新しい許可セットは、ファイルの継承セットとプロセスの継承セットの積集合の結果と、ファイルの許可セットとの和集合となります。プロセスの実効セットはプロセスの新しい許可セットとファイルの実効セットの論理積となります。厳密に言うと、Linux では fE はセットではなくブール値として扱われ、true の場合には pE'pP' に設定されます。falseの場合、pE' は最初、空になります。

プロセスがファイルの実行後にすべてのケイパビリティーを維持するためには、ケイパビリティーがファイルの許可セットまたは継承セットに含まれていなければなりません。これまで長い間、ファイル・ケイパビリティーを実装していなかった Linux では実行不可能な制約でしたが、その対策として「セキュア・モード」が実装されていました。このモードは以下の 2 つのビットで構成されます。

  • SECURE_NOROOT が設定されていない場合、プロセスがファイルを実行すると、そのファイルにはすべてのファイル・ケイパビリティー・セットが設定されているという前提で新しいケイパビリティー・セットが計算されます。具体的には、以下の結果のように計算されます。
    • ファイルの継承セットおよび許可セットは、プロセスの実 uid または実効 uid が 0 (root) であるか、またはファイルが setuid root を実行されていれば維持されます。
    • ファイルの実効セットは、プロセスの実効 uid が root であるか、またはファイルが setuid root を実行されていればすべて維持されます。
  • SECURE_NO_SETUID_FIXUP が設定されていない場合、プロセスが実 uid または実効 uid を 0 に、または 0 から切り替えると、ケイパビリティー・セットは以下のように変更されます。
    • プロセスが、その実効 uid を 0 から 0 以外の値に切り替えると、実効ケイパビリティー・セットがクリアされます。
    • プロセスが、その実 uid、実効 uid、または保存 uid の少なくとも 1 つの値がゼロに設定されているのを切り替えて、すべてゼロ以外の値にすると、許可ケイパビリティーと実効ケイパビリティーの両方がクリアされます。
    • プロセスがその実効 uid をゼロ以外の値から 0 に切り替えると、許可ケイパビリティーと同じ実効ケイパビリティーが設定されます。

この一連のルールが、root としてのプロセスにケイパビリティーを与えたり、あるいは setuid root ファイルを実行することによってプロセスにケイパビリティーを与えたりすることを可能にします。一方、SECURE_NO_SETUID_FIXUP のルールでは、プロセスが非 root になるとケイパビリティーを維持できないようになっていますが、SECURE_NOROOT が設定されていなければ、一部のケイパビリティーを分け与えた root プロセスでも別のプログラムを実行することによって簡単にケイパビリティーを再取得することができます。つまり、ケイパビリティーが有益に機能するためには、root プロセスがいくつかのケイパビリティーを確実に維持する一方で、その uid をゼロ以外の値に切り替えて元に戻らないようにできなければならないということです。

プロセスが次の setuid(2) 呼び出しでもケイパビリティーを維持できるようにするには、prctl(3) を使用するという方法があります。この場合、プロセスには以下の操作が可能になります。

  • root として開始する。その方法としては、root として認証するか、または setuid root バイナリーを実行します。
  • prctl(2) を呼び出して PR_SET_KEEPCAPS を設定する。これによって、setuid(2) でプロセスにケイパビリティーを維持させるようシステムに要求します。
  • setuid(2) または関連するシステム・コールを呼び出して userid を変更する。
  • cap_set_proc(3) を呼び出してケイパビリティーを分け与える。

これで、プロセスは root 特権で実行し続けられるようになります。プロセスのセキュリティーが侵害されるとしても、アタッカーは実効セットに存在するケイパビリティーしか使用することができません。また、cap_set_proc(3) の呼び出しでも、使用できるのは許可セットに含まれるケイパビリティーだけです。アタッカーがプログラムを別のファイルで実行させた場合には、ファイルのすべてのケイパビリティーが分け与えられるため、特権を持たないユーザーとしてファイルを実行することになります。

リスト 2 の exec_with_caps() 関数を見てください。setuid root プログラムはここに示された関数を使用して、指定した関数、指定した userid、そしてストリングとして指定した一連のケイパビリティーを使って実行を継続することができます。

リスト 2. ケイパビリティーを減らした上でのコードの実行
#include <sys/prctl.h>
#include <sys/capability.h>
#include <sys/types.h>
#include <stdio.h>

int printmycaps(void *d)
{
	cap_t cap = cap_get_proc();
	printf("Running with uid %d\n", getuid());
	printf("Running with capabilities: %s\n", cap_to_text(cap, NULL));
	cap_free(cap);
	return 0;
}

int exec_with_caps(int newuid, char *capstr, int (*f)(void *data), void *data)
{
	int ret;
	cap_t newcaps;

	ret = prctl(PR_SET_KEEPCAPS, 1);
	if (ret) {
		perror("prctl");
		return -1;
	}
	ret = setresuid(newuid, newuid, newuid);
	if (ret) {
		perror("setresuid");
		return -1;
	}
	newcaps = cap_from_text(capstr);
	ret = cap_set_proc(newcaps);
	if (ret) {
		perror("cap_set_proc");
		return -1;
	}
	cap_free(newcaps);
	f(data);
}

int main(int argc, char *argv[])
{
	if (argc < 2) {
		printf("Usage: %s <capability_list>\n",
			argv[0]);
		return 1;
	}
	return exec_with_caps(1000, argv[1], printmycaps, NULL);
}

このコードをテストするには、execwithcaps.c という名前のファイルにコードを貼り付け、root として以下のようにコンパイルして実行してください。

gcc -o execwithcaps execwithcaps.c -lcap
./execwithcaps cap_sys_admin=eip

ファイル・ケイパビリティー

ファイル・ケイパビリティーは現在、-mm カーネル・ツリーに実装されており、バージョン 2.6.24 までにはカーネルの主流になると期待されています。ファイル・ケイパビリティーを使うと、プログラムにケイパビリティーを割り当てることができます。例えば、ping プログラムが機能するには CAP_NET_RAW が必要なため、このプログラムは従来、setuid root プログラムとなっていましたが、ファイル・ケイパビリティーを使うと以下のようにしてプログラムに与える特権を減らすことができます。

chmod u-s /bin/ping
setfcaps -c cap_net_admin=p -e /bin/ping

上記に必要な最新バージョンの libcap ライブラリーと関連プログラムは、GoogleCode から入手することができます (「参考文献」にリンクを記載)。このコードは setuid ビットをバイナリーからクリアしてから、このプログラムに必要な CAP_NET_RAW 特権を割り当てています。これで、あらゆるユーザーが CAP_NET_RAW 特権を使用して ping を実行できるようになりますが、ping プログラムのセキュリティーが侵害されたとしても、アタッカーはこれ以外の特権を行使することができません。

ここで持ち上がってくるのは、特権を持たないユーザーが特定のプログラムを実行するのに必要な最小限のケイパビリティーを一体どのようにして判断するのかという疑問です。プログラムが 1 つしかなければ、アプリケーション、アプリケーションに動的にリンクされるライブラリー、そしてカーネル・ソースを探すという方法が有効ですが、この場合、すべての setuid root プログラムに対して同じ作業を繰り返すことになります。もちろん、特権のないユーザーにアプリケーションを root として実行できるようにする前であれば、この方法も悪い考えではありませんが、残念ながら現実的とは言えません。

プログラムが冗長で正常に動作している場合、単純に特権なしでプログラムを実行し、足りない特権をレポートさせるという方法も考えられます。この方法を ping で試してみましょう。

chmod u-s /bin/ping
setfcaps -r /bin/ping
su - myuser
ping google.com
	ping: icmp open socket: Operation not permitted

この方法は、icmp の実装について十分に理解していれば役立つはずですが、詳しい説明が提供されないことは確かです。

次に、strace を使ってプログラムを実行してみます (この場合も、suid ビットはセットしません)。strace はプログラムが使用したすべてのシステム・コールとそれぞれの戻り値をレポートするため、strace 出力の戻り値を調べれば、どの許可が足りないかがわかります。

strace -oping.out ping google.com
grep EPERM ping.out
   socket(PF_INET, SOCK_RAW, IPPROTO_ICMP) = -1 EPERM (Operation not permitted)

ここで足りないのは、SOCK_RAW 型のソケットを作成するための許可です。/usr/include/linux/capability.h をひと通り見ると、以下の行が見つかるはずです。

/* Allow use of RAW sockets */
/* Allow use of PACKET sockets */

#define CAP_NET_RAW          13

上記から明らかなのは、特権を持たないユーザーが ping を使用するために必要なケイパビリティーは CAP_NET_RAW だということです。しかしこの方法では、一部のプログラムは実際に行う必要のない多くの操作を試行して -EPERM によって拒否されることになります。さらに、ping を使用するのに必要なケイパビリティーが単純でないために推測できない可能性も考えられます。

これよりも実際的な別の方法は、カーネル内でケイパビリティーがチェックされる箇所にプローブを挿入することです。プローブが、拒否されたケイパビリティーについてのデバッグ情報を出力します。

開発者は kprobes を使って、関数の開始時 (jprobe)、関数の完了時 (kretprobe)、または任意のアドレス (kprobe) でコードを実行する小さなカーネル・モジュールを作成することができます。この機能を有効にすれば、カーネルが特定のプログラムを実行するために必要なケイパビリティーに関する情報を取得することができます (このセクションでは以降、カーネルで kprobes とファイル・ケイパビリティーの両方が有効に設定されていることを前提とします)。

リスト 3 のカーネル・モジュールは、cap_capable() カーネル関数の開始状況を表示するために jprobe を挿入しています。

リスト 3. capable_probe.c
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/kprobes.h>
#include <linux/sched.h>

static const char *probed_func = "cap_capable";

int cr_capable (struct task_struct *tsk, int cap)
{
	printk(KERN_NOTICE "%s: asking for capability %d for %s\n",
		__FUNCTION__, cap, tsk->comm);
	jprobe_return();
	return 0;
}

static struct jprobe jp = {
	.entry = JPROBE_ENTRY(cr_capable)
};

static int __init kprobe_init(void)
{
	int ret;
	jp.kp.symbol_name = (char *)probed_func;

	if ((ret = register_jprobe(&jp)) < 0) {
		printk("%s: register_jprobe failed, returned %d\n",
			__FUNCTION__, ret);
		return -1;
	}
	return 0;
}

static void __exit kprobe_exit(void)
{
	unregister_jprobe(&jp);
	printk("capable kprobes unregistered\n");
}

module_init(kprobe_init);
module_exit(kprobe_exit);

MODULE_LICENSE("GPL");

このカーネル・モジュールが挿入されると、cap_capable() の呼び出しはすべて、cr_capable() 関数の呼び出しに置き換えられます。この関数はケイパビリティーを必要とするプログラムの名前と、チェックされるケイパビリティーを出力した後、jprobe_return() を呼び出して実際の cap_capable() 呼び出しを実行します。

このモジュールをコンパイルするには、リスト 4 の Make ファイルを使用してください。

リスト 4. capable_probe の Make ファイル
obj-m := capable_probe.o
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
	$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
clean:
	rm -f *.mod.c *.ko *.o

次に、root として以下を実行します。

	/sbin/insmod capable_probe.ko

ここで、以下を実行してシステム・ログを調べます。

	tail -f /var/log/messages

別のウィンドウで、非 root として setuid ビットをセットせずに ping バイナリーを実行します。

	/bin/ping google.com

システム・ログには ping に関する複数のエントリーが含まれているはずです。これらのエントリーは、プログラムが使用としたケイパビリティーですが、このすべてが必要なわけではありません。/usr/include/linux/capability.h を相互参照して整数をケイパビリティー名に変換すると、ping が 21、13、7 を要求したことがわかります。

  • 21 は CAP_SYS_ADMIN です。この catch-all 機能は、どのプログラムにも許可しないでください。
  • 7 は CAP_SETUID です。ping には必要ありません。
  • 13 は CAP_NET_RAW です。ping にはこのケイパビリティーが必要です。

このケイパビリティーを許可すると ping が成功するかどうか試してみましょう。

	setfcaps -c cap_net_raw=p -e /bin/ping
	(become non root user)
	ping google.com

期待どおり、ping は成功します。

厄介な問題

既存のソフトウェアはたいてい、多くの UNIX バリアントでほとんど変更を加えなくても可能な限りセキュアになるように作成されています。それに加え、ディストリビューションには独自のパッチが適用されていることがあり、それによって root setuid ビットをファイル・ケイパビリティーに置き換えられない場合があります。

Fedora でのそのようなプログラムの例は at です。at プログラムでは、ジョブが後で実行されるようにユーザーがスケジュールすることができます。以下の例は、電話会議を思い出させるメッセージを午後 2 時に表示する簡単な方法です。

echo "xterm -display :0.0 -e \
\"echo Call customer 555-5555; echo ^V^G; sleep 10m\" " | \
at 14:00

at プログラムはあらゆる UNIX システムに有効で、すべてのユーザーが使用できます。ユーザーは /var/spool にある共通ジョブ・スプールを共有するため、セキュリティーは最大の重要事項ですが、セキュリティーも多くのシステムで機能するようにコード化されています。つまり、ケイパビリティーなどのシステム固有のセキュリティー・メカニズムは利用されません。それにも関わらず、このプログラムは setuid(2) を使用して特権を減らそうとします。その上、Fedora パッケージは PAM モジュール用のパッチを追加します。

setuid root を使わずに root 以外のユーザーによって at を実行可能にできるかどうかを調べる最も手軽な方法は、setuid ビットをクリアしてから、このプログラムにすべてのケイパビリティーを許可することです。

chmod u-s /usr/bin/at
setfcaps -c all=p -e /usr/bin/at
su - (non root user)
/usr/bin/at

上記では -c all=p を指定して、/usr/bin/at での完全な許可 (強制) ケイパビリティー・セットを要求しています。したがって、このプログラムを実行するすべてのユーザーは、root のすべての特権でプログラムを実行することになります。しかし Fedora 7 で /usr/bin/at を実行すると、以下の結果になります。

You do not have permission to run at.

理由はソース・コードをダウンロードして調べれば明らかにわかりますが、その詳細はこの演習には役に立ちません。ファイル・ケイパビリティーで at を使用できるようにソース・コードを変更することはもちろん可能ですが、Fedora にただ単にファイル・ケイパビリティーを割り当てるだけでは setuid ビットを置き換えることは不可能です。

ファイル・ケイパビリティーの詳細

これまでは、実行可能ファイルに割り当てるケイパビリティーにかなり特有のフォーマットを使用してきました。ping に使用したフォーマットは以下のとおりです。

	setfcaps -c cap_net_raw=p -e /bin/ping

setfcaps は、拡張属性 security.capability を設定することによってターゲット・ファイルのケイパビリティーを設定するプログラムです。-c フラグの後には、いくぶん流動的なフォーマットでケイパビリティーのリストが続きます。

	capability_list=capability_set(s)

capability_set には ip を、capability_list には有効なあらゆるケイパビリティーを含められます。この 2 つのケイパビリティーのタイプはそれぞれ継承セットと許可セットを表し、セットごとに異なるケイパビリティー・リストを指定することができます。-e または -d フラグについては、前者は許可セットに含まれるケイパビリティーがプログラムの起動時の実効セットに含まれることを示し、後者は含まれないことを示します。ケイパビリティーがプログラムの実効セットに含まれない場合、プログラムがケイパビリティーを利用するには、ケイパビリティーを認識して、その実効セット自体のビットをアクティブにしなければなりません。

今まで必要とするケイパビリティーは継承セットではなく許可セットで表明してきましたが、実は、ケイパビリティーではそれよりも巧妙かつ強力な操作が可能です。リスト 1 を思い出してもらうため、ここにもう一度記載します。

リスト 1 の転記. exec() 実行後の新規ケイパビリティー・セットの公式
pI' = pI
pP' = fP | (fI & pI)
pE' = pP' & fE

ファイルの継承セットは、プロセスのどの継承ケイパビリティーをプロセスの新しい許可セットに含められるかを指定します。ファイルの継承セットに含まれるのが cap_dac_override のみの場合は、このケイパビリティーだけしかプロセスの新規許可セットに継承させることができません。

ファイルの許可セットは、タスクの継承セットに含まれるかどうかには関わらず、新規許可セットに強制的に継承されます。そのため、許可セットは「強制」セットと呼ばれることもあります。

ファイルの実効ビットは、タスクの新しい許可セットのビットを新しい実効セットに含めるかどうかを指示します。つまり、プログラムが明示的に cap_set_proc(3) の使用を要求しなくても、実際にケイパビリティーを行使できるかどうかが指定されます。

SECURE_NOROOT が設定されていない場合、システムは root ユーザーに対していくつかの変更を行うことを思い出してください。具体的には、システムは実行中のファイルで継承 (fI)、許可 (fP)、実効 (fE) セットが完全に設定されているように振る舞うということです。そのため、バイナリーの fI セットは、ケイパビリティー・セットが空ではない非 root プロセスにしか有効でありません。つまり、ケイパビリティーを維持しながら非 root ユーザーになったプログラムには、前述のように振る舞うことなく上記の公式が適用されます。今後、SECURE_NOROOT がプロセス単位の設定となり、プロセス・ツリーが実際のケイパビリティーを使用するか、root ユーザーに対する特権付与モデルを使用するかを選択できるようになる可能性はあります。しかし、この記事を書いている時点では、この設定はシステム全体に適用され、あらゆる実用的システムはデフォルトで root ユーザーが常にすべての権限を持つように設定されます。

これらのセットの相互作用を説明する例として、管理者が以下のコマンドを使って /bin/some_program にファイル・ケイパビリティーを設定したとします。

	setfcaps -c cap_sys_admin=i,cap_dac_read_search=p -e \
	/bin/some_program

root 以外のユーザーがすべてのケイパビリティーを使用しているときにこのプログラムを実行すると、継承セット pIfI に対してマスクされるため、ケイパビリティー・セットは cap_sys_admin だけになります。続いて fP はこのセットとの和集合をとられ、中間結果は cap_sys_admin+cap_dac_read_search となり、このセットがタスクの新しい許可セットになります。

さらに、実効ビットが有効になっているため、タスクの新しい実効セットには、この新しい許可セット内の両方のビットが含まれることになります。

その一方、まるで特権を持たないユーザーがこの同じプログラムを実行すると、空の継承セットが fI に対してマスクされるため、セットは空になります。このセットが fP と結合されると結果は cap_dac_read_search となり、これがタスクの新しい許可セットになります。実効ビットは有効になっているので新規許可セットが新しい実効セットにコピーされますが、その結果は変わらず cap_dac_read_search です。

いずれの場合にしても、ファイルの実効ビットがセットされていないとしたら、タスクは cap_set_proc(3) を使って必要なビットを許可セットから実効セットにコピーすることになります。


要約と演習

要約すると、以下のようになります。

  • ファイルの実効ビットは、プログラムがデフォルトで許可されたケイパビリティーを使用できるかどうかを指定します。
  • ファイルの許可セットは、最終的なプロセスのなかで常に有効になります。
  • ファイルの継承セットは、親プロセスの継承セットから新しい許可セットに継承することができます。

これまで説明してきた内容を明らかにするため、リスト 5 とリスト 6 のプログラムで実験してみます。リスト 5 のprint_caps は、プログラムの実行で使用されているケイパビリティー・セットを出力するだけのプログラムです。リスト 6 の exec_as_nonroot_priv は root ユーザーとして実行されるよう意図されています。このプログラムは次回の setuid(2) でケイパビリティーを維持するよう要求してから、最初のコマンドライン引数として指定された非 root ユーザーになります。そしてケイパビリティー・セットを 2 番目のコマンドライン引数に指定されたセットに設定してから、3 番目のコマンドライン引数として指定されたプログラムを実行します。

リスト 5. print_caps.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/capability.h>

int main(int argc, char *argv[])
{
	cap_t cap = cap_get_proc();

	if (!cap) {
		perror("cap_get_proc");
		exit(1);
	}
	printf("%s: running with caps %s\n", argv[0], cap_to_text(cap, NULL));
	cap_free(cap);
	return 0;
}
リスト 6. exec_as_nonroot_priv.c
#include <sys/prctl.h>
#include <sys/capability.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>

void printmycaps(void)
{
	cap_t cap = cap_get_proc();

	if (!cap) {
		perror("cap_get_proc");
		return;
	}
	printf("%s\n",  cap_to_text(cap, NULL));
	cap_free(cap);
}

int main(int argc, char *argv[])
{
	cap_t cur;
	int ret;
	int newuid;

	if (argc<4) {
		printf("Usage: %s <uid> <capset>"
			"<program_to_run>\n", argv[0]);
		exit(1);
	}
	ret = prctl(PR_SET_KEEPCAPS, 1);
	if (ret) {
		perror("prctl");
		return 1;
	}
	newuid = atoi(argv[1]);
	printf("Capabilities before setuid: ");
	printmycaps();
	ret = setresuid(newuid, newuid, newuid);
	if (ret) {
		perror("setresuid");
		return 1;
	}
	printf("Capabilities after setuid, before capset: ");
	printmycaps();
	cur = cap_from_text(argv[2]);
	ret = cap_set_proc(cur);
	if (ret) {
		perror("cap_set_proc");
		return 1;
	}
	printf("Capabilities after capset: ");
	cap_free(cur);
	printmycaps();
	ret = execl(argv[3], argv[3], NULL);
	if (ret)
		perror("exec");
}

この 2 つのプログラムを使って、継承および許可ファイル・ケイパビリティーの効果を確認することにします。この確認作業は、ファイル・ケイパビリティーを print_caps に設定し、次に exec_as_nonroot_priv を使って慎重にセットアップされた初期プロセス・ケイパビリティー・セットで print_caps を実行するという手順で行います。まず、以下のように print_caps の許可セットだけにケイパビリティーを設定してください。

gcc -o print_caps print_caps.c -lcap
setfcaps -c cap_dac_override=p -d print_caps

非 root ユーザーとして print_caps を実行します。

su - (username)
./print_caps

今度は root として、exec_as_nonroot_priv を使って print_caps を実行します。

./exec_as_nonroot_priv 1000 cap_dac_override=eip ./print_caps

いずれの場合も、cap_dac_override=p を指定して print_caps を実行しました。実効セットが空であることに注目してください。これはつまり、print_capscap_set_proc(3) を使用してからでないと、cap_dac_override ケイパビリティーを利用できないということです。これを変えるには、setflags に実効ビットをセットする -e フラグを使用します。

setfcaps -c cap_dac_override=p -e print_caps

print_capsfI は空なので、プロセスの pI に含まれるケイパビリティーは 1 つも pP' にプルされません。pP' の唯一のビットは、ファイルの強制セット fP からのものです。

さらに興味深いテストとして、継承ファイル・ケイパビリティーの効果をテストして、print_caps をもう一度、非 root ユーザーとして exec_as_nonroot_priv プログラムから実行してください。

setfcaps -c cap_dac_override=i -e print_caps
su - (nonroot_user)
	./print_caps
	exit
./exec_as_nonroot_priv 1000 cap_dac_override=eip ./print_caps

今度は、非 root ユーザーのケイパビリティー・セットが空になる一方、root ユーザーとして開始されたプロセスの許可セットと実効セットには cap_dac_override が含まれる結果となっています。

print_caps を、今度は exec_as_nonroot_priv を使わずに単純に root ユーザーとして再度実行してください。すると、ケイパビリティー・セットがフルになるはずです。root ユーザーはプログラムを実行した後、ファイル・ケイパビリティーとは関係なく常に完全なケイパビリティー・セットを受け取ります。exec_as_nonroot_privprint_caps を root ユーザーとしては実行しません。代わりに root ユーザーの特権を使って、継承ケイパビリティーを持つ非 root プロセスをセットアップします。


まとめ

この記事を読んで、プログラムに必要なケイパビリティーを判断する方法、ケイパビリティーを設定する方法、そしてファイル・ケイパビリティーを使っていくつかの巧妙な操作を行う方法がわかったはずです。

ケイパビリティーは常に慎重に扱ってください。ケイパビリティーが危険な root 特権であることには変わりありません。その一方で、sendmail でのケイパビリティーのバグに関する実験 (「参考文献」にリンクを記載) が示すように、提供するケイパビリティーが少なすぎても同じく危険です。いずれにせよ、setuid root に代わってファイル・ケイパビリティーを賢明にシステムのバイナリーに適用することがシステムの保護に役立ちます。

参考文献

学ぶために

  • developerWorks の連載「セキュアなプログラマー」では、以下の記事で setuid() について説明しています。
    • 入力に目を光らす」(developerWorks、2003年12月) では、データがプログラムに到達するための各種経路について、そしてこれらの経路を扱う方法を説明しています。
    • 特権を最小限にとどめる」(developerWorks、2004年5月) では、システム・ユーザーが困ることなく特権を最小化する方法を説明しています。
    • 安全にコンポーネントを呼ぶ」(developerWorks、2004年12月) では、アタッカーがコンポーネントの呼び出しを悪用できないようにする方法を解説しています。
  • Linux をセキュアにする 第 3 回: システムを強化する」(developerWorks、2005年4月) を読んで、Linux システムを攻撃に強くする方法を学んでください。これには、ブート・プロセスとローカル・ファイルシステムのセキュリティー保護、サービスとデーモンのロックアップ、クォータと制限の実施、強制アクセス制御の有効化、および新しいソフトウェアでセキュリティーを更新するときに取り込まれる恐れがあるセキュリティー脆弱性の認識が含まれます。
  • Speaking UNIX, Part 8」(developerWorks、2007年4月) では、プロセスを制御し、多数のコマンドを使ってシステムの内部を調べる方法を紹介しています。
  • Linux カーネルの徹底調査」(developerWorks、2007年6月) は、Linux の構造を初歩から学ぶのに最適な記事です。
  • sendmail ケイパビリティーのバグについて読んでください。
  • Linux マニュアル・ページ: exec() ファミリーの関数は、現行のプロセス・イメージを新しいプロセス・イメージに置き換えます。
  • Linux マニュアル・ページ: プロセスでの操作、prctl() を呼び出すには、最初の引数で操作内容を記述します (<linux/prctl.h> に値を定義)。最初のパラメーターがその他のパラメーターの意味を決定します。
  • Linux マニュアル・ページ: setuid() は現行プロセスの実効ユーザー ID を設定します。呼び出し側プロセスの実効 UID が root の場合、実 UID と保存 set-user-ID も設定されます。Linuxでは、setuid() は _POSIX_SAVED_IDS 機能を備えた POSIX バージョンのように実装されています。これによって (root 以外の) set-user-ID プログラムにそのユーザーの特権をすべて与え、特権の必要のない処理を行い、安全に元の実効ユーザー ID を取り戻すことが可能になります。
  • Linux マニュアル・ページ: cap_set_proc() は、cap_p によって識別されるケイパビリティー状態を持つあらゆるケイパビリティーに対してすべてのケイパビリティー・フラグを設定します。プロセスの新しいケイパビリティー状態は、この関数から正常に戻った時点での cap_p のコンテンツによってのみ決定されます。cap_p のいずれかのフラグが呼び出し側プロセスに現在許可されていないケイパビリティーに対して設定されている場合、関数は失敗し、プロセスのケイパビリティー状態はそのまま変更されません。
  • POSIX Threads Programming は、コンセプトの概要から、ハイブリッド MPI/Pthread の開発方法などの話題までを網羅した優れたチュートリアルです。
  • IEEE Std 1003.1-2001 としても知られる POSIX は、コマンド・インタープリター (つまり、「シェル」) や共通ユーティリティー・プログラムを含め、ソース・コード・レベルでのアプリケーションの移植性をサポートする標準オペレーティング・システムのインターフェースと環境を定義します。この規格は、アプリケーション開発者とシステム実装者の両方を対象としています。
  • Using ReiserFS with Linux」(developerWorks、2006年4月) は、「冒険好きな人たちのためのもう 1 つの高度なファイルシステム」を取り上げた記事です。
  • Differentiating UNIX and Linux」(developerWorks、2006年3月) では、Linux と UNIX でのファイルシステム・サポートの違いを分かりやすく解説しています。「Filesystem support」セクションを参照してください。
  • ファイルシステムについての詳細は、「System Administration Toolkit: Migrating and moving UNIX filesystems」(developerWorks、2006年7月) を読んでください。この記事では、ライブ・システムでファイルシステム全体を転送する方法を、作成、コピー、そして再有効化の手順をまじえて説明しています。
  • developerWorks Linux ゾーンに豊富に揃った Linux 開発者向けの資料を調べてください。記事とチュートリアルの人気ランキングも要チェックです。
  • developerWorks に掲載されているすべての Linux のヒントLinux チュートリアルを参照してください。
  • developerWorks technical events and webcasts で最新情報を入手してください。

製品や技術を入手するために

  • GoogleCode で libcap ライブラリーと関連プログラムを入手してください。
  • 柔軟性の高いユーザー認証機構、Linux PAM により、開発者は認証方式に依存しないプログラムを作成できます (つまり、「新規デバイス」がすべての認証サポート・プログラムの記録と匹敵する必要はありません)。
  • developerWorks から直接ダウンロードできる IBM トライアル・ソフトウェアを使用して、Linux で次の開発プロジェクトを構築してください。

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Linux, AIX and UNIX
ArticleID=270040
ArticleTitle=POSIX ファイル・ケイパビリティー root の権限を分配する
publish-date=10162007