EternalWindows
アクセスコントロール / セキュリティ記述子

Windowsを使用していて何らかのアクセスが拒否された場合、 その原因は現在のアカウントに問題があると考えてしまうことがあります。 この予想は、管理者のような高度なアカウントでログオンして実行すれば、 アクセスは成功するだろうという前提で成り立っているわけですが、 仮にそうしたアカウントでアクセスを行ったとしても、 無条件にアクセスが成功するわけではありません。 これは、オブジェクト自身がセキュリティ記述子と呼ばれるアクセス制御情報を維持しているからであり、 この情報がアカウントにアクセスを許可していないことには、 どのようなアカウントでもアクセスは拒否されてしまうことになります。

アプリケーション開発者にとって、セキュリティ記述子はある意味で身近な存在と言えます。 なぜなら、何らかのオブジェクトを作成する関数には、 セキュリティ記述子を受け取る引数が用意されていることが多いからです。 たとえば、ディレクトリを作成するCreateDirectoryの定義は次のようになっています。

BOOL WINAPI CreateDirectory(
  LPCTSTR lpPathName,
  LPSECURITY_ATTRIBUTES lpSecurityAttributes
);

第2引数の型であるSECURITY_ATTRIBUTES構造体は、次のように定義されています。

typedef struct _SECURITY_ATTRIBUTES {
  DWORD  nLength;
  LPVOID lpSecurityDescriptor;
  BOOL   bInheritHandle;
} SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;

このlpSecurityDescriptorメンバが、セキュリティ記述子を受け取るメンバになります。 通常、SECURITY_ATTRIBUTES構造体の引数にはNULLを指定することから、 オブジェクトにはデフォルトのセキュリティ記述子が指定されますが、 lpSecurityDescriptorを初期化して明示的にSECURITY_ATTRIBUTES構造体を指定すれば、 任意のアカウントのみにオブジェクトへのアクセスを許可させるようなことが可能になります。 こうした作業は、アクセスコントロールと呼ばれています。

オブジェクトからセキュリティ記述子を取得するには、GetNamedSecurityInfoを呼び出します。

DWORD WINAPI GetNamedSecurityInfo(
  LPTSTR pObjectName,
  SE_OBJECT_TYPE ObjectType,
  SECURITY_INFORMATION SecurityInfo,
  PSID *ppsidOwner,
  PSID *ppsidGroup,
  PACL *ppDacl,
  PACL *ppSacl,
  PSECURITY_DESCRIPTOR *ppSecurityDescriptor
);

pObjectNameは、セキュリティ記述子を取得したいオブジェクトの名前を指定します。 ObjectTypeは、オブジェクトの種類を表す定数を指定します。 SecurityInfoは、セキュリティ記述子から取得するデータの種類を表す定数を指定します。 ppsidOwnerは、所有者SIDを受け取る変数のアドレスを指定します。 ppsidGroupは、グループSIDを受け取る変数のアドレスを指定します。 ppDaclは、DACLを受け取る変数のアドレスを指定します。 ppSaclは、SACLを受け取る変数のアドレスを指定します。 ppSecurityDescriptorは、セキュリティ記述子を受け取る変数のアドレスを指定します。 戻り値は、関数が成功した場合にERROR_SUCCESSとなります。

SE_OBJECT_TYPEは、次のように定義されています。

typedef enum _SE_OBJECT_TYPE {
  SE_UNKNOWN_OBJECT_TYPE       = 0,
  SE_FILE_OBJECT,
  SE_SERVICE,
  SE_PRINTER,
  SE_REGISTRY_KEY,
  SE_LMSHARE,
  SE_KERNEL_OBJECT,
  SE_WINDOW_OBJECT,
  SE_DS_OBJECT,
  SE_DS_OBJECT_ALL,
  SE_PROVIDER_DEFINED_OBJECT,
  SE_WMIGUID_OBJECT,
  SE_REGISTRY_WOW64_32KEY 
} SE_OBJECT_TYPE;

各定数がどのようなオブジェクトを表すかは、名前が示す通りです。 SE_FILE_OBJECTはファイルやディレクトリを表し、 SE_LMSHAREは共有フォルダを表します。 SE_KERNEL_OBJECTはプロセスやスレッド、ミューテックスなどのカーネルオブジェクトを表し、 SE_WINDOW_OBJECTはデスクトップやウインドウステーションを表します。

GetNamedSecurityInfoの第3引数には、 セキュリティ記述子に格納されているデータを表す定数を指定します。

定数 意味
OWNER_SECURITY_INFORMATION 所有者SIDを取得する。 オブジェクトの所有者はDACLの内容に関係なく、DACLの書き換えが許可される。
GROUP_SECURITY_INFORMATION プライマイグループのSIDを取得する。 これは、POSIXサブシステムでのみ使用されため、通常は気にしなくてもよい。
DACL_SECURITY_INFORMATION DACLを取得する。 DACLには、特定のアカウントにアクセスを許可(または拒否)するためのACEが格納される。
SACL_SECURITY_INFORMATION SACLを取得する。 SACLには、特定のアカウントからのアクセスに対して、監査イベントを生成するためのACEが格納される。

特定の定数を指定する場合は、対応する引数にデータを受け取るためのアドレスを指定することになります。 たとえば、所有者SIDを取得する場合は、 第4引数にSIDを受け取る変数のアドレスを指定します。

今回のプログラムは、GetNamedSecurityInfoを呼び出してオブジェクトの所有者SIDを取得します。 アクセスコントロール系の関数を呼び出す場合は、aclapi.hのインクルードが必要になります。

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

BOOL ConvertSidToName(PSID pSid, LPTSTR lpszName, DWORD dwSizeName);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR                szOwnerName[256];
	PSID                 pSidOwner;
	PSECURITY_DESCRIPTOR pSecurityDescriptor;

	if (GetNamedSecurityInfo(TEXT("sample.txt"), SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION, &pSidOwner, NULL, NULL, NULL, &pSecurityDescriptor) != ERROR_SUCCESS) {
		MessageBox(NULL, TEXT("セキュリティ記述子の取得に失敗しました。"), NULL, MB_ICONWARNING);
		return 0;
	}
	
	ConvertSidToName(pSidOwner, szOwnerName, sizeof(szOwnerName) / sizeof(TCHAR));
	MessageBox(NULL, szOwnerName, TEXT("オブジェクトの所有者"), MB_OK);

	LocalFree(pSecurityDescriptor);

	return 0;
}

BOOL ConvertSidToName(PSID pSid, LPTSTR lpszName, DWORD dwSizeName)
{
	TCHAR        szDomainName[256];
	DWORD        dwSizeDomain = sizeof(szDomainName) / sizeof(TCHAR);
	SID_NAME_USE sidName;

	return LookupAccountSid(NULL, pSid, lpszName, &dwSizeName, szDomainName, &dwSizeDomain, &sidName);
}

GetNamedSecurityInfoの第1引数にファイル名を指定しているため、 第2引数にはSE_FILE_OBJECTを指定します。 所有者SIDを取得するため、第3引数にはOWNER_SECURITY_INFORMATIONを指定し、 第4引数にSID型変数のアドレスを指定します。 関数の呼び出しに成功したら、取得したSIDをConvertSidToNameという自作関数に指定し、 SIDに対応するアカウント名を表示します。 アクセスコントロール系の関数を利用して確保されたメモリは、 LocalFreeで開放することになります。

GetSecurityInfoについて

GetNamedSecurityInfoは、オブジェクトの名前からセキュリティ記述子を取得しますが、 オブジェクトの中には名前を一般のアプリケーションに公開していないものもあります。 このようなオブジェクトからセキュリティ記述子を取得するには、 ハンドルを要求するGetSecurityInfoを呼び出します。

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

BOOL ConvertSidToName(PSID pSid, LPTSTR lpszName, DWORD dwSizeName);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR                szOwnerName[256];
	HANDLE               hFile;
	PSID                 pSidOwner;
	PSECURITY_DESCRIPTOR pSecurityDescriptor;
	
	hFile = CreateFile(TEXT("sample.txt"), READ_CONTROL, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
	if (hFile == INVALID_HANDLE_VALUE) {
		MessageBox(NULL, TEXT("ファイルのオープンに失敗しました。"), NULL, MB_ICONWARNING);
		return 0;
	}

	if (GetSecurityInfo(hFile, SE_FILE_OBJECT, OWNER_SECURITY_INFORMATION, &pSidOwner, NULL, NULL, NULL, &pSecurityDescriptor) != ERROR_SUCCESS) {
		MessageBox(NULL, TEXT("セキュリティ記述子の取得に失敗しました。"), NULL, MB_ICONWARNING);
		CloseHandle(hFile);
		return 0;
	}
	
	ConvertSidToName(pSidOwner, szOwnerName, sizeof(szOwnerName) / sizeof(TCHAR));
	MessageBox(NULL, szOwnerName, TEXT("オブジェクトの所有者"), MB_OK);

	LocalFree(pSecurityDescriptor);
	CloseHandle(hFile);

	return 0;
}

BOOL ConvertSidToName(PSID pSid, LPTSTR lpszName, DWORD dwSizeName)
{
	TCHAR        szDomainName[256];
	DWORD        dwSizeDomain = sizeof(szDomainName) / sizeof(TCHAR);
	SID_NAME_USE sidName;

	return LookupAccountSid(NULL, pSid, lpszName, &dwSizeName, szDomainName, &dwSizeDomain, &sidName);
}

GetSecurityInfoの第1引数はオブジェクトのハンドルであるため、 これを取得するためにCreateFileを呼び出しています。 第2引数に指定しているREAD_CONTROLは、 セキュリティ記述子の取得に必要なアクセス権です。 GetSecurityInfoの第2引数以降は、GetNamedSecurityInfoと同じ意味を持ちます。

GetSecurityInfoは、オブジェクトのハンドルを必要とするわけですが、 このハンドルを取得できない場合があることに注意してください。 たとえば、オブジェクトのDACLが空の場合、そのオブジェクトのセキュリティ記述子を取得するには、 呼び出し元がそのオブジェクトの所有者であることが必須です。 現に、GetNamedSecurityInfoでは空のDACLでもオブジェクトの所有者であれば、 セキュリティ記述子を取得することができます。 しかし、CreateFileは呼び出し元がオブジェクトの所有者であるのかを確かめないのか、 空のDACLであるときはハンドルを取得することはできません。 より厳密に述べると、呼び出し元にREAD_CONTROLとSYNCHRONIZEを許可するACEが存在しないと、 ハンドルを取得することはできません。



戻る