EternalWindows
セキュリティコンテキスト / MIC

プロセスのセキュリティコンテキストは、低ければ低いほど安全といえます。 極端に言えば、セキュリティ操作を必要としないアプリケーションは、 あらゆるオブジェクトへのアクセスが失敗するぐらいのセキュリティコンテキストであっても、 特に不都合が起こることはないはずです。 こう考えた場合、制限ユーザーとしてコードを実行させるUACは、 ときとして不十分であると言えます。 確かに制限ユーザーとして動作することにより、特定のパスへの書き込みや作成は制限されますが、 デスクトップ以下のフォルダやファイルには問題なくアクセスすることができるからです。 こうしたパスへのアクセスを必要としないアプリケーションは、 制限ユーザーより低いセキュリティコンテキストで動作しても問題ないため、 できればそのための処理を実行時に行った方がよいといえるでしょう。 Windows Vistaでは、整合性レベルというものをトークンとオブジェクトに設定することにより、 より低いセキュリティコンテキストを実現しています。

整合性レベルの考え方は、トークンに設定された整合性レベルがオブジェクトに設定された 整合性レベルを下回る場合、オブジェクトへのアクセスが制限されるというものです。 オブジェクトの整合性レベルは、SACL内の必須ラベルと呼ばれるACEに格納されており、 このACEのSIDとトークンのSIDが比較されることになります。 このような整合性レベルを利用したメカニズムは、MIC(Mandatory Integrity Control)と呼ばれ、 それはDACLをベースとした通常のアクセスコントロールよりも優先して実行されます。 このため、DACL内のACEによってアクセスが許可されていたとしても、 整合性レベルによる検証でアクセスが制限されてしまっては、 オブジェクトへのアクセスは失敗してしまうことになります。 また、整合性レベルによる検証で問題がなかったとしても、 その時点でアクセス成功と見なされるわけではありません。 アクセスが成功するためには、通常のアクセスチェックで許可される必要があります。 次に、定義されている整合性レベルを示します。

レベル SID 主なアカウント
Untrusted S-1-16-0 Anonymous(everyoneincludesanonymous無効時)
Low S-1-16-4096 Anonymous(everyoneincludesanonymous有効時)
Medium S-1-16-8192 標準ユーザーや制限されたユーザー
High S-1-16-12288 AdministratorsやBackup Operators
System S-1-16-16384 LocalSystemやLocalService

この表では、整合性レベルの低いものから順に示しています。 自分と同等、または低い整合性レベルに対してはアクセスの制限を受けることがないため、 MediumからMedium、HighからMediumといったアクセスでは、 整合性レベルが原因でアクセスに失敗することはありません。 ただし、MediumからHighのような自分より高い整合性レベルに対しては、 アクセスの制限を受けることがあります。 表からも分るように、整合性レベルはSIDで表すことができ、 RID(SIDの最後の要素)だけが各レベルによって異なっています。 よって、アプリケーション内ではRIDを確認することで、 整合性レベルを特定することができます。 なお、多くのオブジェクトには明示的に必須ラベルが設定されていませんが、 その場合はデフォルトの必須ラベルが存在するものと仮定されます。 デフォルトの必須ラベルは、整合性レベルがMediumであり、 Mask(後述)がSYSTEM_MANDATORY_LABEL_NO_WRITE_UPです。

多くのオブジェクトの整合性レベルがMediumであることから、 トークンの整合性レベルをLowにするとプロセスは安全に動作することができます。 たとえば、そのようなプロセスには、Internet Explorerがあります。 ただし、低い整合性レベルとして動作するにしても、 どうしてもオブジェクトへの書き込みが必要になる場合があります。 たとえば、Internet Explorerの場合であれば、 一時ファイルなどの保存が必要です。 このため、Internet Explorerが利用する特定のフォルダには、 整合性レベルがLowである必須ラベルが設定されています。 この点に注目して、オブジェクトのSACLから必須ラベルを取得する例を示します。

#include <windows.h>
#include <aclapi.h>
#include <shlobj.h>

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	PACL                        pSacl;
	PSECURITY_DESCRIPTOR        pSecurityDescriptor;
	PSYSTEM_MANDATORY_LABEL_ACE pAce;
	LPWSTR                      lpszFilePath;
	TCHAR                       szBuf[256];
	
	SHGetKnownFolderPath(FOLDERID_LocalAppDataLow, 0, NULL, &lpszFilePath);

	GetNamedSecurityInfoW(lpszFilePath, SE_FILE_OBJECT, LABEL_SECURITY_INFORMATION, NULL, NULL, NULL, &pSacl, &pSecurityDescriptor);
	if (pSacl == NULL) {
		MessageBox(NULL, TEXT("オブジェクトは必須ラベルを持っていません。"), NULL, MB_ICONWARNING);
		CoTaskMemFree(lpszFilePath);
		return 0;
	}

	if (GetAce(pSacl, 0, (LPVOID *)&pAce)) {
		DWORD dwRid = *GetSidSubAuthority(&pAce->SidStart, 0);
		TCHAR szName[256];

		if (dwRid == SECURITY_MANDATORY_UNTRUSTED_RID)
			lstrcpy(szName, TEXT("Untrusted"));
		else if (dwRid == SECURITY_MANDATORY_LOW_RID)
			lstrcpy(szName, TEXT("Low"));
		else if (dwRid == SECURITY_MANDATORY_MEDIUM_RID)
			lstrcpy(szName, TEXT("Medium"));
		else if (dwRid == SECURITY_MANDATORY_HIGH_RID)
			lstrcpy(szName, TEXT("High"));
		else if (dwRid == SECURITY_MANDATORY_SYSTEM_RID)
			lstrcpy(szName, TEXT("System"));
		else
			;
		wsprintf(szBuf, TEXT("mask %d, level %s"), pAce->Mask, szName);
		MessageBox(NULL, szBuf, TEXT("OK"), MB_OK);
	}
	
	LocalFree(pSecurityDescriptor);
	CoTaskMemFree(lpszFilePath);

	return 0;
}

SHGetKnownFolderPathにFOLDERID_LocalAppDataLowを指定すれば、 Internet Explorerが使用するフォルダのパスを取得できます。 このパスをGetNamedSecurityInfoに指定し、 第3引数にLABEL_SECURITY_INFORMATIONを指定することで、 必須ラベルを含んだSACLを取得できるようになります。 ここで少し注意しなければならないのは、 SACLが存在しなかった場合でも関数が成功するという点です。 そのため、SACLがNULLであるかどうかで、関数の成否を確認しています。 GetAceはACLからACEを取得する関数で、 第1引数に対象とするACL、第2引数に取得するACEのインデックス、 第3引数が取得したACEを表すポインタとなります。 GetSidSubAuthorityは、第1引数に指定されたSIDから、 第2引数の位置に存在するサブ機関値のアドレスを取得します。 整合性レベルを表すSIDのサブ機関値は1つであり、 これがRIDということになるため、0を指定しています。

SACLにおいて整合性レベルを定義するACEは必須ラベルと呼ばれ、 SYSTEM_MANDATORY_LABEL_ACE構造体で表されます。

typedef struct _SYSTEM_MANDATORY_LABEL_ACE {
  ACE_HEADER Header;
  ACCESS_MASK Mask;
  DWORD SidStart;
} SYSTEM_MANDATORY_LABEL_ACE,  *PSYSTEM_MANDATORY_LABEL_ACE;

Headerは、ACE_HEADER構造体が格納されています。 ACE_HEADER.AceTypeは、SYSTEM_MANDATORY_LABEL_ACE_TYPEとなります。 Maskは、オブジェクトに対して制限するアクセスの種類が格納されています。 SidStartは、整合性レベルを表すSIDへのアドレスが格納されています。 たとえば、これがMediumであれば、これ以下の整合性レベルであるLowなどは、 アクセスの制限を受けることになります。 Maskに指定される定数の種類を次に示します。

定数 意味
SYSTEM_MANDATORY_LABEL_NO_WRITE_UP オブジェクトへの書き込みを許可しない。この定数は明示的に指定しなくても、常に有効である。
SYSTEM_MANDATORY_LABEL_NO_READ_UP オブジェクトへの読み取りを許可しない。
SYSTEM_MANDATORY_LABEL_NO_EXECUTE_UP オブジェクトの実行を許可しない。

1つの例を挙げて考えてみましょう。 あるオブジェクトのSACLに、SYSTEM_MANDATORY_LABEL_NO_READ_UPとMediumレベルのSIDを含んだ 必須ラベルが設定されているとしています。 まず、トークンの整合性レベルがMedium以上である場合、 整合性レベルが原因でこのオブジェクトへのアクセスを失敗することはありません。 ただし、整合性レベルがMedium以下のLowやUntrustedである場合、 オブジェクトへの書き込みは無条件に失敗します。 また、MaskとしてSYSTEM_MANDATORY_LABEL_NO_READ_UPが指定されているため、 読み取りアクセスも失敗することになります。 ただし、実行については成功することになります。

整合性レベルが低いプロセスからのアクセスを制限しないためには、 オブジェクトの整合性レベルをそのプロセスに合わせて低くすればよいことになります。 明示的に必須ラベルを追加するには、AddMandatoryAceを呼び出します。

BOOL WINAPI AddMandatoryAce(
  PACL pAcl,
  DWORD dwAceRevision,
  DWORD AceFlags,
  DWORD MandatoryPolicy,
  PSID pLabelSid
);

pAclは、必須ラベルを追加したいACLを指定します。 このACLは、最終的にSACLとしてセキュリティ記述子に設定されることになります。 dwAceRevisionは、修正レベルを表す定数を指定します。 AceFlagsは、継承に関する定数を指定します。 MandatoryPolicyは、SYSTEM_MANDATORY_LABEL_ACE.Maskとなる値を指定します。 pLabelSidは、SYSTEM_MANDATORY_LABEL_ACE.SidとなるSIDを指定します。

次に、既存ファイルに対して必須ラベルを設定する例を示します。 このファイルには、整合性レベルがLowのプロセスからでも書き込むことができるようになります。

#include <windows.h>
#include <aclapi.h>

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	PACL  pSacl;
	PSID  pSid;
	DWORD dwSaclSize;
	DWORD dwSidSize;
	DWORD dwResult;

	dwSaclSize = 256;
	pSacl = (PACL)LocalAlloc(LPTR, dwSaclSize);
	InitializeAcl(pSacl, dwSaclSize, ACL_REVISION);

	dwSidSize = SECURITY_MAX_SID_SIZE;
	pSid = (PSID)LocalAlloc(LPTR, dwSidSize);
	CreateWellKnownSid(WinLowLabelSid, NULL, pSid, &dwSidSize);

	if (!AddMandatoryAce(pSacl, ACL_REVISION, CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE, SYSTEM_MANDATORY_LABEL_NO_WRITE_UP, pSid)) {
		MessageBox(NULL, TEXT("ACEの追加に失敗しました。"), NULL, MB_ICONWARNING);
		LocalFree(pSid);
		LocalFree(pSacl);
		return 0;
	}

	dwResult = SetNamedSecurityInfo(TEXT("sample.txt"), SE_FILE_OBJECT, LABEL_SECURITY_INFORMATION, NULL, NULL, NULL, pSacl);
	if (dwResult == ERROR_SUCCESS)
		MessageBox(NULL, TEXT("必須ラベルを設定しました。"), TEXT("OK"), MB_OK);
	else
		MessageBox(NULL, TEXT("必須ラベルの設定に失敗しました。"), NULL, MB_ICONWARNING);

	LocalFree(pSid);
	LocalFree(pSacl);

	return 0;
}

必須ラベルをオブジェクトに設定するためには、ACLから作成することになります。 まず、ACL用のメモリを確保し、それをInitializeAclで初期化します。 続いて、必須ラベルに含まれるSIDを作成するために、CreateWellKnownSidを呼び出すことになります。 第1引数は、作成するSIDの種類を表す定数を指定することになっており、 WinLowLabelSidを指定した場合は整合性レベルがLowのSIDが作成されることになります。 AddMandatoryAceの第4引数にSYSTEM_MANDATORY_LABEL_NO_WRITE_UPを指定していることから、 整合性レベルがLow以下のプロセスはオブジェクトに書き込みを行うことはできません。 ただし、この定数はデフォルトで必ず有効になるため、明示的に指定しなくても問題ありません。 SetNamedSecurityInfoにLABEL_SECURITY_INFORMATIONを指定することで、 第7引数に指定したACLが必須ラベルを含んだSACLであると解釈されます。

上記で設定した必須ラベルが正しく機能しているかを確認するためには、 トークンの整合性レベルをLowにして動作する必要があります。 以下にその例を示します。

#include <windows.h>
#include <aclapi.h>

void ShowIntegrityLevel(HANDLE hToken);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	HANDLE                hFile;
	HANDLE                hToken;
	TOKEN_MANDATORY_LABEL mandatoryLabel;
	PSID                  pSid; 
	DWORD                 dwSidSize;
	DWORD                 dwWriteByte;

	if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY | TOKEN_ADJUST_DEFAULT, &hToken)) {
		MessageBox(NULL, TEXT("トークンのハンドルの取得に失敗しました。"), NULL, MB_ICONWARNING);
		return 0;
	}
	
	ShowIntegrityLevel(hToken);

	dwSidSize = SECURITY_MAX_SID_SIZE;
	pSid = (PSID)LocalAlloc(LPTR, dwSidSize);
	CreateWellKnownSid(WinLowLabelSid, NULL, pSid, &dwSidSize);
	
	mandatoryLabel.Label.Attributes = SE_GROUP_INTEGRITY;
	mandatoryLabel.Label.Sid = pSid;

	if (!SetTokenInformation(hToken, TokenIntegrityLevel, &mandatoryLabel, sizeof(TOKEN_MANDATORY_LABEL) + GetLengthSid(pSid))) {
		MessageBox(NULL, TEXT("整合性レベルの設定に失敗しました。"), NULL, MB_ICONWARNING);
		LocalFree(pSid);
		CloseHandle(hToken);
		return 0;
	}
	
	hFile = CreateFile(TEXT("sample.txt"), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hFile == INVALID_HANDLE_VALUE) {
		if (GetLastError() == ERROR_ACCESS_DENIED)
			MessageBox(NULL, TEXT("アクセスが拒否されました。"), NULL, MB_ICONWARNING);
		LocalFree(pSid);
		CloseHandle(hToken);
		return 0;
	}
	
	WriteFile(hFile, "data", 4, &dwWriteByte, NULL);
	CloseHandle(hFile);
	
	MessageBox(NULL, TEXT("ファイルへ書き込みました。"), TEXT("OK"), MB_OK);
	LocalFree(pSid);
	CloseHandle(hToken);
	
	return 0;
}

void ShowIntegrityLevel(HANDLE hToken)
{
	TCHAR                  szName[256];
	TCHAR                  szDomainName[256];
	DWORD                  dwSizeName;
	DWORD                  dwSizeDomain;
	DWORD                  dwLength;
	SID_NAME_USE           sidName;
	PTOKEN_MANDATORY_LABEL pMandatoryLabel;
	
	GetTokenInformation(hToken, TokenIntegrityLevel, NULL, 0, &dwLength);
	pMandatoryLabel = (PTOKEN_MANDATORY_LABEL)LocalAlloc(LPTR, dwLength);
	GetTokenInformation(hToken, TokenIntegrityLevel, pMandatoryLabel, dwLength, &dwLength);
	
	dwSizeName = sizeof(szName) / sizeof(TCHAR);
	dwSizeDomain = sizeof(szDomainName) / sizeof(TCHAR);
	LookupAccountSid(NULL, pMandatoryLabel->Label.Sid, szName, &dwSizeName, szDomainName, &dwSizeDomain, &sidName);

	MessageBox(NULL, szName, TEXT("OK"), MB_OK);
	
	LocalFree(pMandatoryLabel);
}

ShowIntegrityLevelという関数は、現在の整合性レベルを取得して表示します。 これは、変更前の整合性レベルを確認するために行っています。 内部構造としては、TokenIntegrityLevelを指定してGetTokenInformationを呼び出し、 TOKEN_MANDATORY_LABEL構造体を取得します。 この構造体には、整合性レベルを表すSIDが含まれているため、 LookupAccountSidに指定することで整合性レベルを表した文字列を取得することができます。 整合性レベルを設定するには、SetTokenInformationにTokenIntegrityLevelを指定します。 CreateWellKnownSidでLowを表すSIDを作成し、これをTOKEN_MANDATORY_LABEL構造体に指定したので、 トークンの整合性レベルはLowとなります。 この結果、プロセスは整合性レベルがLowより高いMediumなどのオブジェクトに対して、 書き込みを行うことができなくなります。 先に示したプログラムでは、sample.txtに整合性レベルがLowの必須ラベルを設定したため、 整合性レベルが原因でこのファイルの書き込みに失敗することはなくなります。

最後に、デフォルトの必須ラベルについて補足しておきます。 オブジェクトに必須ラベルを明示的に設定していない場合は、 整合性レベルがMediumとして検証されると説明しましたが、 オブジェクトを作成したプロセスの整合性レベルがLowである場合は、 これは当てはまりません。 この場合、整合性レベルがLowである必須ラベルが明示的に設定されることになります。 整合性レベルがLowであるプロセスからMediumのオブジェクトへは書き込めないため、 LowであるプロセスがMediumのオブジェクトを作成するようなことがあってはなりません。


戻る