EternalWindows
アクセスコントロール / コンテナオブジェクト

フォルダやレジストリキーのような階層を持つオブジェクトは、コンテナオブジェクトと呼ばれることがあります。 コンテナオブジェクトに設定されているACEは、その下位に存在するコンテナオブジェクトとリーフオブジェクトに継承させることができるため、 コンテナオブジェクトとリーフオブジェクトのセキュリティは基本的に矛盾しません。 つまり、フォルダを読み取り専用にした場合は、その下位に作成したファイルも通常は読み取り専用になります。 ここで言うデフォルトとは、セキュリティ記述子を指定せずにファイルを作成することであり、 ACEが継承ACEであるかどうかは、次のコードで確認することができます。

GetAce(pDacl, i, (LPVOID *)&pAce);
if (pAce->Header.AceFlags & INHERITED_ACE) {
	// ACEが継承ACEのときに実行される
}

Header.AceFlagsがINHERITED_ACEを含む場合、 そのACEは継承ACEです。 つまり、明示的に設定されたACEではなく、 コンテナオブジェクトのACEを継承したACEです。

継承は、非常に便利なメカニズムです。 たとえば、フォルダ内の全てのファイルを読み取り専用にしたい場合、 各ファイルへ明示的にDACLを設定する必要はありません。 フォルダのDACLを読み取り専用にすれば、 その中に存在するファイルやフォルダのDACLも読み取り専用になるからです。 AddAccessAllowedAceExで継承フラグを格納したACEをDACLに追加すれば、 これをコンテナオブジェクトに設定することで、 ACEの継承が有効になります。

BOOL WINAPI AddAccessAllowedAceEx(
  PACL pAcl,
  DWORD dwAceRevision,
  DWORD AceFlags,
  DWORD AccessMask,
  PSID pSid
);

pAclは、ACLを指定します。 dwAceRevisionは、ACL_REVISIONを指定します。 AceFlagsは、継承フラグを指定します。 AccessMaskは、アクセスマスクを指定します。 pSidは、アクセスの許可対象とするアカウントのSIDを指定します。

今回のプログラムは、カレントディレクトに存在するsampleというフォルダに独自のDACLを設定します。 このDACLには、Administratorsに全てのアクセスを許可するACEとAuthenticated Usersに読み取りと実行を許可するACEが含まれます。

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

DWORD GetAclSize(PSID *ppSid, int nSidCount);
BOOL ConvertNameToSid(LPTSTR lpszName, PSID *ppSid);

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	int    i;
	int    nSidCount = 2;
	LPTSTR lpszAccountName[] = {TEXT("Administrators"), TEXT("Authenticated Users")};
	DWORD  dwAccessMask[] = {GENERIC_ALL, GENERIC_READ | GENERIC_EXECUTE};
	DWORD  dwDaclSize;
	PACL   pDacl;
	PSID   pSid[2];

	for (i = 0; i < nSidCount; i++)
		ConvertNameToSid(lpszAccountName[i], &pSid[i]);
	
	dwDaclSize = GetAclSize(pSid, nSidCount);
	pDacl = (PACL)LocalAlloc(LPTR, dwDaclSize);
	InitializeAcl(pDacl, dwDaclSize, ACL_REVISION);

	for (i = 0; i < nSidCount; i++)
		AddAccessAllowedAceEx(pDacl, ACL_REVISION, CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE, dwAccessMask[i], pSid[i]);

	if (SetNamedSecurityInfo(TEXT("sample"), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION, NULL, NULL, pDacl, NULL) == ERROR_SUCCESS)
		MessageBox(NULL, TEXT("DACLの設定に成功しました。"), TEXT("OK"), MB_OK);
	else
		MessageBox(NULL, TEXT("DACLの設定に失敗しました。"), NULL, MB_ICONWARNING);

	LocalFree(pDacl);

	for (i = 0; i < nSidCount; i++)
		LocalFree(pSid[i]);

	return 0;
}

DWORD GetAclSize(PSID *ppSid, int nSidCount)
{
	int   i;
	DWORD dwAclSize = 0;

	for (i = 0; i < nSidCount; i++) {
		dwAclSize += GetLengthSid(ppSid[i]);
		dwAclSize += sizeof(ACCESS_ALLOWED_ACE) - sizeof(DWORD);
	}

	dwAclSize += sizeof(ACL);
	
	return dwAclSize;
}

BOOL ConvertNameToSid(LPTSTR lpszName, PSID *ppSid)
{
	TCHAR        szDomainName[256];
	DWORD        dwSizeDomain = sizeof(szDomainName) / sizeof(TCHAR);
	DWORD        dwSizeSid = 0;
	SID_NAME_USE sidName;

	LookupAccountName(NULL, lpszName, NULL, &dwSizeSid, szDomainName, &dwSizeDomain, &sidName);

	*ppSid = (PSID)LocalAlloc(LPTR, dwSizeSid);

	return LookupAccountName(NULL, lpszName, *ppSid, &dwSizeSid, szDomainName, &dwSizeDomain, &sidName);
}

2つのアカウントにアクセスを許可するため、lpszAccountNameを配列として宣言しています。 また、アクセスマスクはそれぞれのアカウントで異なるので、こちらも配列として宣言しています。 ConvertNameToSidでSIDを2つ初期化し、これをGetAclSizeに指定して、 2つのACEを格納できるだけのサイズを取得します。 そして、DACLのためのメモリを確保し、AddAccessAllowedAceExでACEを追加します。 Authenticated UsersのACEのアクセスマスクには、GENERIC_WRITEが含まれていないため、 一般ユーザーではフォルダ内に存在するファイルに書き込むことができなくなります。 また、フォルダ内にファイルを作成することもできません。

AddAccessAllowedAceExの第3引数にCONTAINER_INHERIT_ACEを指定した場合、 コンテナオブジェクトの下位に存在する(作成される)コンテナオブジェクトは、 上位のコンテナオブジェクトのACEを継承します。 また、OBJECT_INHERIT_ACEを指定した場合は、 コンテナオブジェクトの下位に存在する(作成される)リーフオブジェクトは、 上位のコンテナオブジェクトのACEを継承します。 コンテナオブジェクトのACEが継承フラグを持たない場合は、 下位のオブジェクトにデフォルトDACLが割り当てられます。

TreeResetNamedSecurityInfoについて

コンテナオブジェクトのDACLを変更した場合、 その下位に存在するオブジェクトのDACLも変更されると説明してきましたが、 実際には1つの例外があります。 それは、オブジェクトに明示的にDACLが設定されている場合であり、 この場合はコンテナオブジェクトのACEを継承しませんから、 コンテナオブジェクトに合してオブジェクトのDACLが変更されることはありません。 こうした明示的なDACLが設定されたオブジェクトを考慮する場合は、 TreeResetNamedSecurityInfoを呼び出すことになります。

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

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	TCHAR           szAccountName[256];
	DWORD           dwSize;
	DWORD           dwResult;
	EXPLICIT_ACCESS explicitAccess;
	PACL            pDacl;

	dwSize = sizeof(szAccountName) / sizeof(TCHAR);
	GetUserName(szAccountName, &dwSize);
	BuildExplicitAccessWithName(&explicitAccess, szAccountName, GENERIC_ALL, GRANT_ACCESS, CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE);	
	SetEntriesInAcl(1, &explicitAccess, NULL, &pDacl);
	
	dwResult = TreeResetNamedSecurityInfoW(L"sample", SE_FILE_OBJECT, DACL_SECURITY_INFORMATION | PROTECTED_DACL_SECURITY_INFORMATION,
		NULL, NULL, pDacl, NULL, FALSE, NULL, ProgressInvokeNever, NULL);

	if (dwResult == ERROR_SUCCESS)
		MessageBox(NULL, TEXT("DACLの設定に成功しました。"), TEXT("OK"), MB_OK);
	else
		MessageBox(NULL, TEXT("DACLの設定に失敗しました。"), NULL, MB_ICONWARNING);
		
	LocalFree(pDacl);

	return 0;
}

TreeSetNamedSecurityInfoは、第1引数に指定したコンテナオブジェクトの下位に存在する全てのオブジェクトのDACLを変更します。 明示的にUNICODE版の関数を呼び出しているのは、ANSI版の関数がサポートされていないためです。 第1引数から第7引数まではSetNamedSecurityInfoと同じ意味を持ち、 第8引数は明示的に設定されたDACLのACEを維持するかどうかです。 TRUEを指定した場合は、既存のACEが維持されたまま新しいACEが設定されますが、 FALSEを指定した場合は第6引数のDACLで完全に上書きされることになります。 第9引数は、オブジェクトにDACLが設定される度に呼ばれるプログレス関数のアドレスを指定できますが、 第10引数にProgressInvokeNeverを指定している場合はNULLを指定します。 第11引数はプログレス関数に引数として渡したいデータを指定しますが、 第9引数がNULLであるため、こちらもNULLを指定します。 TreeSetNamedSecurityInfoの第8引数には、NULL DACLや空のDACLを指定することはできません。



戻る