Oracle Solaris セキュリティーサービス開発ガイド

第 2 章 特権付きアプリケーションの開発

この章では、特権付きアプリケーションを開発する方法について説明します。この章の内容は次のとおりです。

特権付きアプリケーション

「特権付きアプリケーション」とは、システム制御を無視し、特定のユーザー ID (UID)、グループ ID (GID)、承認、および特権の有無を検査できるアプリケーションのことです。これらのアクセス制御要素はシステム管理者によって割り当てられます。管理者がこれらのアクセス制御要素を使用する方法に関する一般的な説明については、『Solaris のシステム管理 (セキュリティサービス)』の第 8 章「役割と特権の使用 (概要)」を参照してください。

Solaris OS は、きめ細かい特権委託を可能にするために、次の 2 つの要素を開発者に提供します。

承認と特権の違いは、「だれが何を行えるか」というポリシーの適用レベルにあります。特権はカーネルレベルで適用されます。適切な特権を持たないプロセスは、特権付きアプリケーションで特定の操作を実行できません。承認は、ユーザーアプリケーションレベルでポリシーを適用します。承認は、特権付きアプリケーションにアクセスしたり、特権付きアプリケーションで特定の操作を実行したりする際に必要になる可能性があります。

特権について

特権は、通常であれば Solaris OS によって禁止されるような操作を実行できるように、特定のプロセスに付与される個別の権利です。大部分のプログラムは特権を使用しません。というのも、プログラムは一般に、システムのセキュリティーポリシーの境界の内側で動作するからです。

特権は管理者によって割り当てられます。特権は、プログラムの設計に従って有効化されます。ログイン時またはプロファイルシェル起動時には、シェル内で実行されるすべてのコマンドに対して、管理者による特権割り当てが適用されます。アプリケーション実行時には、特権のオン/オフがプログラム的に切り替えられます。exec(1) コマンドを使用して新しいプログラムが起動されると、そのプログラムは親プロセスの継承可能な特権のすべてを使用できる可能性があります。ただし、そのプログラムは新しい特権を 1 つも追加できません。

管理者が特権を割り当てる方法

コマンドに特権を割り当てるのは、システム管理者の責任です。特権の割り当てについての詳細は、『Solaris のシステム管理 (セキュリティサービス)』「特権 (概要)」を参照してください。

特権の実装方法

すべてのプロセスは次の 4 つの特権セットを備えており、これらによって、特定の特権を使用できるかどうかが決まります。

許可された特権セット

許可されたセットには、プロセスが使用できる可能性のあるすべての特権を含める必要があります。逆に言えば、使用すべきでない特権は、そのプログラムの許可されたセットに含めてはいけません。

プロセスが起動された場合、そのプロセスは親プロセスから許可された特権セットを継承します。一般に、ログイン時や新しいプロファイルシェルが起動される際、許可された特権の初期セットにはすべての特権が含まれます。このセット内の特権は管理者によって指定されます。子プロセスはそれぞれ、許可されたセットから特権を削除することはできますが、許可されたセットにほかの特権を追加することはできません。セキュリティー上、プログラムが決して使用しない特権は、許可されたセットから削除しておく必要があります。そうすることで、誤って割り当てた、または誤って継承された特権をプログラムが使用する心配がなくなります。

許可された特権セットから削除された特権は、実効セットからも自動的に削除されます。

継承可能な特権セット

ログイン時や新しいプロファイルシェルが起動される際、管理者によって指定された特権が継承可能なセットに含められます。これらの継承可能な特権は、exec(1) の呼び出し後に子プロセスに渡される可能性があります。プロセスは不要な特権を削除し、それらの特権が子プロセスに渡されるのを防止すべきです。たいていは、許可されたセットと継承可能なセットの内容は同じになります。ただし、継承可能なセットから削除された特権が許可されたセット内に残される場合もあります。

制限特権セット

制限セットを使えば、開発者は、プロセスが行使したり子プロセスに渡したりできる特権を制御できます。子プロセスや子孫プロセスが取得できるのは、制限セット内に含まれる特権だけです。setuid(0) 関数を実行する場合、そのアプリケーションが使用できる特権は、制限セットによって決まります。制限セットは exec(1) の実行時に適用されます。制限セットから特権を削除しても、exec(1) が実行されるまでは、ほかのセットには影響はありません。

実効特権セット

プロセスが実際に使用できる特権が、プロセスの実効セット内に収められます。プログラム起動時の実効セットは、許可されたセットと等しくなります。その後、実効セットは、許可されたセットと等しいか、あるいはそのサブセットになります。

実効セットを基本特権セットに制限することをお勧めします。中核特権を含む基本特権セットは、「特権の種類」に記述されています。プログラム内で使用しない特権をすべて削除します。必要になるまですべての基本特権をオフにしておきます。たとえば、file_dac_read 特権があれば、すべてのファイルを読み取れます。プログラム内にファイル読み取りルーチンが複数含まれる可能性があります。そうしたプログラムでは、最初にすべての特権をオフにしますが、適切な読み取りルーチンに対して file_dac_read をオンにします。したがって、プログラムが間違った読み取りルーチンに対して file_dac_read 特権を行使することはありえません。こうした方法は「特権の囲い込み」と呼ばれます。特権の囲い込みの例については、「特権のコーディング例」を参照してください。

スーパーユーザーモデルと特権モデルの互換性

従来のアプリケーションにも対応できるように、特権の実装は、スーパーユーザーモデルと特権モデルのどちらでも動作します。この対応は、PRIV_AWARE フラグを使用することで実現されます。このフラグは、プログラムが特権に対応していることを示します。PRIV_AWARE フラグは、オペレーティングシステムによって自動的に処理されます。

特権を認識しない子プロセスについて検討してください。このプロセスの PRIV_AWARE フラグは false になっています。親プロセスから継承された特権のすべてが、許可されたセットと実効セットに含まれます。子プロセスが UID を 0 に設定した場合、その実効セットと許可されたセットは、制限セット内の特権に制限されます。子プロセスはスーパーユーザーの力を完全には行使できません。したがって、特権に対応したプロセスの制限セットが、特権に対応していないすべての子プロセスのスーパーユーザー特権を制限することになります。子プロセスが特権セットをいずれか 1 つでも変更すると、PRIV_AWARE フラグが true に設定されます。

特権の種類

特権は、その適用範囲に基づいて論理的に次のように分類されます。

Solaris 特権と説明の完全な一覧については、privileges(5) のマニュアルページを参照してください。


注 –

Solaris は、ゾーン機能を提供します。この機能を使用して管理者はアプリケーション実行用の隔離された環境を設定できます。zones(5) のマニュアルページを参照してください。そのゾーン外のシステムのほかの動作はゾーン内のプロセスを監視したり干渉したりできないため、そのプロセスに対するすべての特権もゾーンに限定されます。ただし、必要な場合は、大域ゾーン以外で操作する特権が必要な大域ゾーン内のプロセスに PRIV_PROC_ZONE 特権を適用できます。


特権を使用したプログラミング

ここでは、特権を操作するためのインタフェースについて説明します。特権プログラミングインタフェースを使用するには、次のヘッダーファイルが必要になります。

#include <priv.h>

また、特権付きアプリケーションにおける特権インタフェースの使用例も示します。

特権のデータ型

特権インタフェースで使用される主なデータ型は、次のとおりです。

特権インタフェース

次の表は、特権を使用するためのインタフェースの一覧です。この表に続いて、主な特権インタフェースのいくつかについて説明します。

表 2–1 特権を使用するためのインタフェース

目的 

関数 

補足説明 

特権セットの取得と設定 

setppriv(2), getppriv(2), priv_set(3C), priv_ineffect(3C)

setppriv()getppriv() はシステム呼び出しです。priv_ineffect()priv_set() は簡易操作用のラッパーです。

特権の特定と変換 

priv_str_to_set(3C), priv_set_to_str(3C), priv_getbyname(3C), priv_getbynum(3C), priv_getsetbyname(3C), priv_getsetbynum(3C)

これらの関数は、指定された特権または特権セットを特定の名前または数字にマッピングします。  

特権セットの操作 

priv_allocset(3C), priv_freeset(3C), priv_emptyset(3C), priv_fillset(3C), priv_isemptyset(3C), priv_isfullset(3C), priv_isequalset(3C), priv_issubset(3C), priv_intersect(3C), priv_union(3C), priv_inverse(3C), priv_addset(3C), priv_copyset(3C), priv_delset(3C), priv_ismember(3C)

これらの関数は、特権のメモリー割り当て、テスト、および各種セット操作に関する機能を提供します。 

プロセスフラグの取得と設定 

getpflags(2), setpflags(2)

PRIV_AWARE プロセスフラグは、プロセスが特権を理解するのか、それともプロセスがスーパーユーザーモデルの下で実行されるのかを示します。PRIV_DEBUG は特権のデバッグ時に使用されます。 

低レベルの資格操作 

ucred_get(3C)

これらのルーチンは、デバッグ、低レベルのシステム呼び出し、およびカーネル呼び出しを行う際に使用されます。 

setppriv(): 特権設定用

特権設定用の主要関数は、 setppriv() です。その構文は次のとおりです。

int setppriv(priv_op_t op, priv_ptype_t which, \
const priv_set_t *set);

op は、実行する特権操作を表します。op パラメータには、次の 3 つの値のいずれかを指定できます。

which には、変更する特権セットの種類を指定します。次のいずれかを指定します。

set には、変更操作で使用される特権を指定します。

さらに、簡易関数 priv_set() が提供されています。

priv_str_to_set(): 特権マッピング用

これらの関数は、特権名を数値にマッピングする場合に役立ちます。 priv_str_to_set() は、このファミリの一般的な関数です。priv_str_to_set() の構文は次のとおりです。

priv_set_t *priv_str_to_set(const char *buf, const char *set, \
const char **endptr);

priv_str_to_set() は特権名の文字列を引数に取りますが、その文字列は buf に指定されます。priv_str_to_set() が返す特権値のセットは、4 つの特権セットのいずれかと組み合わせることができます。**endptr は、構文解析エラーのデバッグ時に使用できます。buf には次のキーワードを含めることができます。

特権のコーディング例

ここでは、スーパーユーザーモデルを使って特権を囲い込みする方法と、最小特権モデルを使って特権を囲い込みする方法を比較します。

スーパーユーザーモデルでの特権の囲い込み

次の例では、スーパーユーザーモデルで特権操作を囲い込みする方法を示します。


例 2–1 スーパーユーザー特権の囲い込み例

/* Program start */
uid = getuid();
seteuid(uid);

/* Privilege bracketing */
seteuid(0);
/* Code requiring superuser capability */
...
/* End of code requiring superuser capability */
seteuid(uid);
...
/* Give up superuser ability permanently */
setreuid(uid,uid);

最小特権モデルでの特権の囲い込み

この例では、最小特権モデルで特権操作を囲い込みする方法を示します。この例では、次のように仮定します。

コードリストに続いて、この例の説明があります。


注 –

このソースコード例は、Sun ダウンロードセンターからダウンロードすることも可能です。http://www.sun.com/download/products.xml?id=41912db5 を参照してください。



例 2–2 最小特権の囲い込み例

1  #include <priv.h>
2  /* Always use the basic set. The Basic set might grow in future
3   * releases and potentially retrict actions that are currently
4   * unrestricted */
5  priv_set_t *temp = priv_str_to_set("basic", ",", NULL);

6  /* PRIV_FILE_DAC_READ is needed in this example */
7  (void) priv_addset(temp, PRIV_FILE_DAC_READ);

8  /* PRIV_PROC_EXEC is no longer needed after program starts */
9  (void) priv_delset(temp, PRIV_PROC_EXEC);

10 /* Compute the set of privileges that are never needed */
11  priv_inverse(temp);

12  /* Remove the set of unneeded privs from Permitted (and by
13   * implication from Effective) */
14  (void) setppriv(PRIV_OFF, PRIV_PERMITTED, temp);

15  /* Remove unneeded priv set from Limit to be safe */
16  (void) setppriv(PRIV_OFF, PRIV_LIMIT, temp);

17  /* Done with temp */
18  priv_freeset(temp);

19  /* Now get rid of the euid that brought us extra privs */
20  (void) seteuid(getuid());

21  /* Toggle PRIV_FILE_DAC_READ off while it is unneeded */
22  priv_set(PRIV_OFF, PRIV_EFFECTIVE, PRIV_FILE_DAC_READ, NULL);

23  /* Toggle PRIV_FILE_DAC_READ on when special privilege is needed*/
24  priv_set(PRIV_ON, PRIV_EFFECTIVE, PRIV_FILE_DAC_READ, NULL);

25  fd = open("/some/retricted/file", O_RDONLY);

26  /* Toggle PRIV_FILE_DAC_READ off after it has been used */
27  priv_set(PRIV_OFF, PRIV_EFFECTIVE, PRIV_FILE_DAC_READ, NULL);

28  /* Remove PRIV_FILE_DAC_READ when it is no longer needed */
29  priv_set(PRIV_OFF, PRIV_ALLSETS, PRIV_FILE_DAC_READ, NULL);

このプログラムでは、temp という名前の変数が定義されます。temp 変数は、このプログラムで不要な特権のセットを判定します。まず 5 行目で、temp が基本特権セットを含むように定義されています。7 行目で、file_dac_read 特権が temp に追加されています。proc_exec 特権は、新しいプロセスに対して exec(1) を実行するために必要ですが、このプログラムでは許可されていません。したがって、9 行目で、proc_exectemp から削除されています。これで、exec(1) コマンドを使って新しいプロセスを実行できなくなります。

この時点で temp に含まれているのは、このプログラムが必要とする特権だけ、つまり基本セット + file_dac_read - proc_exec です。11 行目では、priv_inverse() 関数が、temp の反転を計算し、temp の値をその反転値にリセットしています。この反転は、指定されたセット (この場合は temp) を可能なすべての特権のセットから差し引いた結果です。11 行目を実行した結果、temp には、このプログラムが必要としない特権が含まれています。14 行目で、temp によって定義された不要な特権が、許可されたセットから差し引かれています。この削除の結果、それらの特権が実効セットからも削除されます。16 行目で、不要な特権が制限セットから削除されています。18 行目で、temp 変数は解放されています。というのも、temp は以後使用しないからです。

このプログラムは特権に対応しています。したがって、このプログラムでは setuid は使用されず、20 行目で実効 UID がユーザーの実際の UID にリセットされています。

22 行目では、file_dac_read 特権を実効セットから削除することで、この特権が無効化されています。実際のプログラムでは、file_dac_read が必要とされる前に、何らかの処理が行われます。このサンプルプログラムでは、25 行目でファイルを読み取る際に file_dac_read が必要となります。 したがって、24 行目で file_dac_read が有効化されています。 ファイルの読み取り後すぐに、file_dac_read が実効セットから再度削除されています。すべてのファイルの読み取りが完了すると、すべての特権セット内の file_dac_read をオフにすることで、file_dac_read が完全に削除されています。

次の表は、プログラム実行中の特権セットの遷移を示したものです。行番号が表示されています。

表 2–2 特権セットの遷移

ステップ 

temp セット

許可された特権セット 

実効特権セット 

制限特権セット 

初期状態 

– 

すべて 

すべて 

すべて 

5 行目 – temp を基本特権に設定します。

基本 

すべて 

すべて 

すべて 

7 行目 – file_dac_readtemp に追加します。

基本 + file_dac_read

すべて 

すべて 

すべて  

9 行目 – proc_exectemp から削除します。

基本 + file_dac_readproc_exec

すべて 

すべて 

すべて  

11 行目 – temp を反転します。

すべて – (基本 + file_dac_readproc_exec)

すべて 

すべて 

すべて  

14 行目 – 許可されたセット内の不要な特権をオフにします。 

すべて – (基本 + file_dac_readproc_exec)

基本 + file_dac_readproc_exec

基本 + file_dac_readproc_exec

すべて  

16 行目 – 制限セット内の不要な特権をオフにします。 

すべて – (基本 + file_dac_readproc_exec)

基本 + file_dac_readproc_exec

基本 + file_dac_readproc_exec

基本 + file_dac_readproc_exec

18 行目 – temp ファイルを解放します。

– 

基本 + file_dac_readproc_exec

基本 + file_dac_readproc_exec

基本 + file_dac_readproc_exec

22 行目 – 必要になるまで file_dac_read をオフにします。

– 

基本 – proc_exec

基本 – proc_exec

基本 + file_dac_readproc_exec

24 行目 – 必要に応じて file_dac_read をオンにします。

– 

基本 + file_dac_readproc_exec

基本 + file_dac_readproc_exec

基本 + file_dac_readproc_exec

27 行目 – read() 操作後にfile_dac_read をオフにします。

– 

基本 – proc_exec

基本 – proc_exec

基本 + file_dac_readproc_exec

29 行目 – 不要になった file_dac_read をすべてのセットから削除します。

– 

基本 – proc_exec

基本 – proc_exec

基本 – proc_exec

特権付きアプリケーション開発のガイドライン

ここでは、特権付きアプリケーションを開発する際の注意点を次に列挙します。

承認について

承認は、/etc/security/auth_attr ファイル内に格納されます。承認を使用するアプリケーションを開発するには、次の手順に従います。

  1. /etc/security/auth_attr 内で、1 つまたは複数の適切な承認を検索します。

  2. プログラム開始時に、chkauthattr(3SECDB) 関数を使って必要な承認の有無を検査します。chkauthattr() 関数は、次の場所で順に承認を検索します。

    • policy.conf(4) データベース内の AUTHS_GRANTED キー – AUTHS_GRANTED は、デフォルトで割り当てられた承認を示します。

    • policy.conf(4) データベース内の PROFS_GRANTED キー – PROFS_GRANTED は、デフォルトで割り当てられた権利プロファイルを示します。chkauthattr() は、これらの権利プロファイルに指定された承認が含まれているかどうかを検査します。

    • user_attr(4) データベース – このデータベースには、ユーザーに割り当てられたセキュリティー属性が格納されています。

    • prof_attr(4) データベース – このデータベースには、ユーザーに割り当てられた権利プロファイルが格納されています。

    chkauthattr() 実行時に正しい承認がこれらのどの場所にも見つからなかった場合、そのユーザーはプログラムへのアクセスを拒否されます。

  3. このアプリケーションで必要な承認を管理者に知らせます。管理者への通知は、マニュアルページやその他の文書を通じて行えます。


例 2–3 承認の検査

次のコードは、chkauthattr() 関数を使ってあるユーザーの承認を検査する方法を示したものです。この場合、プログラムは solaris.job.admin 承認の有無を検査しています。このユーザーがこの承認を持っている場合、このユーザーはほかのユーザーのファイルを読み書きできます。この承認がない場合、このユーザーが操作できるのは、自身が所有するファイルだけになります。


/* Define override privileges */
priv_set_t *override_privs = priv_allocset();

/* Clear privilege set before adding privileges. */
priv_set(PRIV_OFF, PRIV_EFFECTIVE, PRIV_FILE_DAC_READ,
			priv_FILE_DAC_WRITE, NULL);

priv_addset(override_privs, PRIV_FILE_DAC_READ);
priv_addset(override_privs, PRIV_FILE_DAC_WRITE);

if (!chkauthattr("solaris.jobs.admin", username)) {
    /* turn off privileges */
    setppriv(PRIV_OFF, PRIV_EFFECTIVE, override_privs);
}
/* Authorized users continue to run with privileges */
/* Other users can read or write to their own files only */