Solaris Trusted Extensions 開発ガイド

第 4 章 印刷とラベル API

印刷は、ラベル対応でなければならないサービスの 1 つです。この章では、Trusted Extensions 用に開発されたマルチレベル印刷サービスの例を使用することによって Solaris Trusted Extensions ラベル API について説明します。

この章の内容は次のとおりです。

ラベル付けされた出力の印刷

通常、プリンタは共有リソースです。マルチレベル印刷によって、異なるセキュリティーレベルで作業するユーザーは、セキュリティーポリシーの制限に従いながら、プリンタを共有できます。印刷サービスもラベル対応なので、印刷されるドキュメントにラベルをはっきりと表記できます。

出力がラベル付けされるようにプリンタを構成するには、役割ベースアクセス制御 (RBAC) でシステム管理者役割になります。印刷ジョブが開始されるセッションラベルは、バナーページおよびトレーラページに印刷されます。セッションのラベルは、印刷されるすべてのページのヘッダーおよびフッターにも追加されます。ラベルは印刷アダプタによって印刷できます。Trusted Extensions 印刷アダプタは、印刷要求が開始されたホストラベルまたはゾーンラベルを特定します。アダプタは印刷ジョブとともにこのラベル情報を渡すので、印刷される出力のラベル付けが可能になります。

ラベル対応アプリケーションの設計

ほとんどのアプリケーションはラベル対応である必要はありません。したがって、大部分の Solaris ソフトウェアアプリケーションは、変更せずに Trusted Extensions で実行されます。Trusted Extensions のラベルベースアクセス制限は、Solaris OS 標準と整合して機能するように設計されています。マルチレベルポートにバインドするプロセスは、複数のラベルでデータを受信し、信頼されてセキュリティーポリシーが強制されるので、一般に、ラベル対応である必要があります。

たとえば、アプリケーションが必要なリソースより低位のラベルで実行されているために、アプリケーションがリソースにアクセスできないことがあります。ただし、そのリソースへアクセスしようとすることは特別なエラー条件にはなりません。代わりに、アプリケーションによって「ファイルが見つかりませんでした (File not found)」エラーが表示されます。あるいは、アクセスが許可されない高位のラベルを持つ情報にアプリケーションがアクセスしようとすることがあります。それに対し、セキュリティーポリシーでは、十分な特権がないアプリケーションはより高位のラベルを持つリソースの存在を認識できないとされています。そのため、アプリケーションのラベルより高位のラベルを持つリソースにアプリケーションがアクセスしようとした場合、そこで発生するエラー条件はラベルに固有ではありません。そのエラーメッセージは、存在しないリソースにアクセスしようとするアプリケーションに返されるエラーメッセージと同じです。「特別なエラー条件」がないことが、セキュリティーの原則の強制に役立ちます。

Trusted Extensions では、アプリケーションではなく、オペレーティングシステムがセキュリティーポリシーを強制します。このセキュリティーポリシーは「必須アクセス制御 (MAC) ポリシー」と呼ばれます。たとえば、アプリケーションは、保護されているリソースにアクセス可能であるか否かを判別しません。最終的にオペレーティングシステムが MAC ポリシーを強制します。リソースにアクセスするのに十分な特権をアプリケーションが持たない場合、そのリソースはアプリケーションで使用できません。そのため、アプリケーションは、ラベル付けされたリソースにアクセスするためにラベルに関して何も知る必要はありません。

同様に、ほとんどのラベル対応アプリケーションは、ラベル対応でないアプリケーションと一貫して動作するように設計する必要があります。ラベル対応のアプリケーションは、単一ラベルのみを含む環境、ラベル付けされていない環境、および複数のラベルを含む環境で基本的に同じ動作をする必要があります。単一ラベル環境の例は、特定ラベルのユーザーセッションが同じラベルでデバイスをマウントする場合です。「ラベル付けされていない環境」では、ラベルは明示的に設定されず、デフォルトラベルが tnrhdb データベースで指定されます。smtnrhdb(1M) のマニュアルページを参照してください。

マルチレベル印刷サービスについて

印刷サービスは異なるラベルで動作するプロセスからの要求を受け入れるので、印刷はラベル対応である必要があります。通常、MAC は、ユーザーが作業しているのと同じラベルのリソースに対してのみアクセスを許可します。印刷要求が同じラベルでのみ発行される場合でも、印刷される出力の印刷ページにラベルを表示できるように、印刷はラベル対応にすべきです。

ラベルを処理するために、印刷サービスは次の基本機能を実行する必要があります。

get_peer_label() ラベル対応関数

lp/lib/lp/tx.c ファイルの get_peer_label() 関数は、Trusted Extensions におけるマルチレベル印刷のロジックを実装します。これ以降の各節でこの関数について説明し、順を追ってそれを実装します。

Trusted Extensions ソフトウェアでは、印刷サービスにおけるラベル処理のための多くのロジックは get_peer_label() 関数にあります。この関数は、ucred_t データ構造体の遠隔プロセスに関する資格を取得し、その資格からラベルを抽出します。

次は get_peer_label() のコードです。

int
get_peer_label(int fd, char **slabel)
{
	if (is_system_labeled()) {
		ucred_t *uc = NULL;
		m_label_t *sl;
		char *pslabel = NULL; /* peer's slabel */

		if ((fd < 0) || (slabel == NULL)) {
			errno = EINVAL;
			return (-1);
		}
	
		if (getpeerucred(fd, &uc) == -1)
			return (-1);
	
		sl = ucred_getlabel(uc);
		if (label_to_str(sl, &pslabel, M_INTERNAL, DEF_NAMES) != 0)
			syslog(LOG_WARNING, "label_to_str(): %m");
		ucred_free(uc);
	
		if (pslabel != NULL) {
			syslog(LOG_DEBUG, "get_peer_label(%d, %s): becomes %s",
				fd, (*slabel ? *slabel : "NULL"), pslabel);
			if (*slabel != NULL)
				free(*slabel);
			*slabel = strdup(pslabel);
		}
	}
	
	return (0);
}

ラベル付けされている環境で印刷サービスが実行されているか否かの判別

印刷サービスは、ラベル付けされている環境とラベル付けされていない環境で機能するように設計されています。そのため、印刷アプリケーションは、遠隔ホストのラベルが要求されるときと、そのラベルが適用されるか否かを決定する必要があります。印刷プロセスは、最初、それ自身の環境を検査します。プロセスがラベル対応の環境で実行されているか否かを確認します。

アプリケーションは、遠隔要求がラベル付けされているか否かを最初に判別しません。その代わりに、印刷アプリケーションはそれ自身の環境がラベル付けされているか否かを判別します。アプリケーションがラベル付けされているホストで実行されていない場合、MAC ポリシーによって、印刷アプリケーションはラベル付けされた要求を受信できません。

印刷サービスは is_system_labeled() 関数を使用して、ラベル付けされた環境でプロセスが実行されているか否かを判別します。この関数についての詳細は、is_system_labeled(3C) のマニュアルページを参照してください。

次のコード抜粋は、ラベル付けされた環境でアプリケーションが実行されているか否かをどのように判別するかを示します。

if (is_system_labeled()) {
	ucred_t *uc = NULL;
	m_label_t *sl;
	char *pslabel = NULL; /* peer's slabel */

	if ((fd < 0) || (slabel == NULL)) {
		errno = EINVAL;
		return (-1);
	}

Trusted Extensions が設定されたシステムで印刷アダプタプロセスが実行されている場合、is_system_labeled() 関数は遠隔プロセスから ucred_t 資格抽象を取得します。次に、遠隔プロセスの ucred_t データ構造体およびピアのラベルが NULL に設定されます。資格およびピアのラベルの値を返す関数がデータ構造体を満たします。そのデータ構造体については、次の節で説明します。

get_peer_label() ルーチン全体のソースは、get_peer_label() ラベル対応関数」を参照してください。

遠隔ホスト資格について

Solaris OS ネットワーク API は、プロセスの資格の抽象を提供します。この資格データは、ネットワーク接続を通じて使用できます。資格は、Solaris 10 リリースで導入された ucred_t データ構造体によって表現されます。この構造体はプロセスのラベルを含むことができます。

ucred API には、遠隔プロセスから ucred_t データ構造体を取得するための関数があります。この API は、ucred_t データ構造体からラベルを抽出するための関数も提供します。

資格と遠隔ホストラベルの取得

遠隔プロセスのラベルの取得は、2 ステップの手続きで実行されます。最初に、資格を取得します。次に、その資格からラベルを取得します。

資格は、遠隔プロセスの ucred_t データ構造体にあります。ラベルは、資格の m_label_t データ構造体にあります。遠隔プロセスの資格を取得したあとに、その資格からラベル情報を抽出します。

getpeerucred() 関数が、遠隔プロセスから ucred_t 資格データ構造体を取得します。ucred_getlabel() 関数が、ucred_t データ構造体からラベルを抽出します。get_peer_label() 関数では、次のように 2 ステップの手続きがコーディングされています。

if (getpeerucred(fd, &uc) == -1)
	return (-1);

sl = ucred_getlabel(uc);

get_peer_label() ルーチン全体のソースは、get_peer_label() ラベル対応関数」を参照してください。

この 2 つの関数については、getpeerucred(3C) および ucred_getlabel(3C) のマニュアルページを参照してください。

遠隔ホストのラベルのほかに、遠隔ホストのタイプも取得できます。遠隔ホストタイプを取得するには、tsol_getrhtype() ルーチンを使用します。「遠隔ホストタイプの取得」を参照してください。

label_to_str() 関数の使用

資格および遠隔ホストラベルを取得したあとに、アプリケーションは label_to_str() を使用してラベルデータ構造体を文字列に変換できます。ラベルデータ構造体の文字列形式は、アプリケーションによって使用できます。

Trusted Extensions 印刷サービスでは、ラベルは文字列として返されます。get_peer_label() 関数は、m_label_t データ構造体でlabel_to_str() を呼び出すことによって取得される文字列を返します。この文字列値は、get_peer_label() 関数の slabel パラメータ、char** slabel に返されます。

次のコード抜粋は、label_to_str() 関数がどのように使用されるかを示します。

sl = ucred_getlabel(uc);
if (label_to_str(sl, &pslabel, M_INTERNAL, DEF_NAMES) != 0)
	syslog(LOG_WARNING, "label_to_str(): %m");
ucred_free(uc);

if (pslabel != NULL) {
	syslog(LOG_DEBUG, "get_peer_label(%d, %s): becomes %s",
		fd, (*slabel ? *slabel : "NULL"), pslabel);
	if (*slabel != NULL)
		free(*slabel);
	*slabel = strdup(pslabel);
}

get_peer_label() ルーチン全体のソースは、get_peer_label() ラベル対応関数」を参照してください。

メモリー管理の処理

get_peer_label() ラベル対応関数」に示すように、多くの場合、ラベルは動的に割り当てられます。関数 str_to_label()label_to_str()getdevicerange()、およびその他の関数は、呼び出し元によって解放されなければならないメモリーを割り当てます。これらの関数の次のマニュアルページに、メモリー割り当ての要件が説明されています。

返されたラベル文字列の使用

get_peer_label() 関数は、遠隔ホストからラベルを抽出し、そのラベルを文字列として返します。典型的なラベル対応アプリケーションと同様に、印刷アプリケーションは次の目的でラベルを使用します。

プリンタのラベル範囲に対するラベル要求の検査

印刷アプリケーションには、ラベルを検査するためのコードが lp/cmd/lpsched/validate.c ファイルに含まれています。

アプリケーションのタイプによっては、2 つの特定ラベルを比較する必要があります。たとえば、アプリケーションは、あるラベルが別のラベルより厳密に優位であるか否かを判別しなければならないことがあります。このようなアプリケーションは、2 つのラベルを比較するための API 関数を使用します。

ただし、印刷アプリケーションは、ラベルの範囲を基準にしています。プリンタは、異なるラベルの範囲からの印刷要求を受け入れるように構成されます。そのため、印刷アプリケーションは、ラベルと範囲を照合する API 関数を使用します。アプリケーションは、遠隔ホストのラベルがプリンタによって許可されるラベルの範囲内にあることを検査します。

validate.c ファイルでは、印刷アプリケーションは blinrange() 関数を使用して、遠隔ホストのラベルとプリンタのラベル範囲を照合します。この検査は、次に示すように tsol_check_printer_label_range() 関数内で実行されます。

static int
tsol_check_printer_label_range(char *slabel, const char *printer)
{
	int			in_range = 0;
	int			err = 0;
	blrange_t		*range;
	m_label_t	*sl = NULL;

	if (slabel == NULL)
		return (0);

	if ((err =
	    (str_to_label(slabel, &sl, USER_CLEAR, L_NO_CORRECTION, &in_range)))
	    == -1) {
		/* プリンタ最大ラベルに対する str_to_label エラー */
		return (0);
	}
	if ((range = getdevicerange(printer)) == NULL) {
		m_label_free(sl);
		return (0);
	}

	/* blinrange は範囲内には 真 (1)、範囲外には偽 (0) を返す */
	in_range = blinrange(sl, range);

	m_label_free(sl);
	m_label_free(range->lower_bound);
	m_label_free(range->upper_bound);
	free(range);

	return (in_range);
}

tsol_check_printer_label_range() 関数は、get_peer_label() 関数によって返されるラベル、およびプリンタ名をパラメータとして受け取ります。

ラベルを比較する前に、tsol_check_printer_label_range()str_to_label() 関数を使用して文字列をラベルに変換します。

ラベルタイプが USER_CLEAR に設定され、これによって、関連付けられているオブジェクトの認可上限ラベルが生成されます。認可上限ラベルは、blinrange() 関数が実行する範囲検査で適切なレベルのラベルが使用されるようにします。

str_to_label() から取得される sl ラベルが検査されることによって、要求されるデバイスであるプリンタの範囲内に遠隔ホストのラベル slabel があるか否かを判別します。このラベルはプリンタのラベルと照合されます。プリンタの範囲は、選択したプリンタに対して getdevicerange() 関数を呼び出すことによって取得されます。範囲は blrange_t データ構造体として返されます。

blrange_t データ構造体のプリンタのラベル範囲は、要求元の認可上限ラベルとともに blinrange() 関数に渡されます。blinrange(3TSOL) のマニュアルページを参照してください。

次のコード抜粋は、validate.c ファイルの _validate() 関数を示します。この関数は、印刷要求を処理するためのプリンタの検索に使用されます。このコードは、要求に関連付けられているユーザー ID およびラベルを、各プリンタに関連付けられている許可ユーザーおよびラベル範囲のセットと照合します。

/*
 * 1 台のプリンタが指定された場合、要求はそのプリンタと照合される。
 * もっとも有用な情報をユーザーに示すことができるように、あとで
 * 受け入れ/拒否検査を実行する。
 */
if (pps) {
	(pc = &single)->pps = pps;

	/* プリンタはユーザーへのアクセスを許可するか? */
	if (!CHKU(prs, pps)) {
		ret = MDENYDEST;
		goto Return;
	}

	/* プリンタラベル範囲を検査する */
	if (is_system_labeled() && prs->secure->slabel != NULL) {
		if (tsol_check_printer_label_range(prs->secure->slabel,
			pps->printer->name) == 0) {
			ret = MDENYDEST;
			goto Return;
		}
	}