EternalWindows
アクセスコントロール / DACLの設定

今回から実際にオブジェクトへのアクセスを制限する作業、 即ちアクセスコントロールを行います。 既に述べてきたように、オブジェクトへのアクセスを制御するのはDACLであるため、 まずはこのDACLを作成する必要があります。 アプリケーションはDACLのためのメモリを確保し、 これをInitializeAclで初期化することになります。

BOOL WINAPI InitializeAcl(
  PACL pAcl,
  DWORD nAclLength,
  DWORD dwAclRevision
);

pAclは、ACLを指定します。 nAclLengthは、ACLのサイズを指定します。 ACLの最高サイズは、64KBです。 dwAclRevisionは、ACL_REVISIONを指定します。

InitializeAclはメモリをACL用に初期化するだけであり、 まだこの時点ではACLに1つもACEが追加されていません。 このようなACLは空のACLと呼ばれ、これをオブジェクトに設定した場合は、 原則誰もそのオブジェクトにアクセスすることができなくなってしまいます。 これではアクセスを制限しすぎですから、 適切なACEをAddAccessAllowedAceで追加することになります。

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

pAclは、ACLを指定します。 dwAceRevisionは、ACL_REVISIONを指定します。 AccessMaskは、アクセスマスクを指定します。 汎用アクセス権を指定した場合は、関数内部で適切にマッピングされます。 pSidは、アクセスの許可対象とするアカウントのSIDを指定します。 実際にアクセスが成功するかは、AccessMaskの内容で決定します。

オブジェクトにDACLを設定するには、SetNamedSecurityInfoを呼び出します。

DWORD WINAPI SetNamedSecurityInfo(
  LPTSTR pObjectName,
  SE_OBJECT_TYPE ObjectType,
  SECURITY_INFORMATION SecurityInfo,
  PSID psidOwner,
  PSID psidGroup,
  PACL pDacl,
  PACL pSacl
);

pObjectNameは、オブジェクトの名前を指定します。 ObjectTypeは、オブジェクトの種類を表す定数を指定します。 SecurityInfoは、設定する情報の種類を表す定数を指定します。 psidOwnerは、オブジェクトの所有者SIDを指定します。 psidGroupは、グループSIDを指定します。 pDaclは、DACLを指定します。 pSaclは、SACLを指定します。 戻り値は、関数が成功した場合にERROR_SUCCESSとなります。 第1引数にオブジェクトのハンドルを指定したい場合は、SetSecurityInfoを呼び出すようにします。

今回のプログラムは、カレントディレクトリに存在するsample.txtを 現在ログオンしているユーザーのみが読み取り可能なファイルに設定します。 空のファイルを読み取り専用にしても仕方がないので、 何らかのデータを事前に書き込んでおいてください。

#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)
{
	TCHAR szAccountName[256];
	DWORD dwAccessMask = GENERIC_READ;
	DWORD dwDaclSize;
	DWORD dwSize;
	PSID  pSid;
	PACL  pDacl;

	dwSize = sizeof(szAccountName) / sizeof(TCHAR);
	GetUserName(szAccountName, &dwSize);
	ConvertNameToSid(szAccountName, &pSid);
	
	dwDaclSize = GetAclSize(&pSid, 1);
	pDacl = (PACL)LocalAlloc(LPTR, dwDaclSize);
	InitializeAcl(pDacl, dwDaclSize, ACL_REVISION);

	AddAccessAllowedAce(pDacl, ACL_REVISION, dwAccessMask, pSid);

	if (SetNamedSecurityInfo(TEXT("sample.txt"), 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(pSid);
	LocalFree(pDacl);

	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);
}

まず、ConvertNameToSidという自作関数を呼び出して、現在のユーザーのSIDを取得します。 次に、GetAclSizeという自作関数を呼び出して、ACEを格納するために必要なメモリを取得します。 第1引数はSIDの配列であり、第2引数は配列の要素数です。 今回は、ACLに追加するACEが1つだけであるため、SIDを1つ指定しています。 関数の内部は、次のようになっています。

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;
}

ACLのサイズは、ACL構造体のサイズと1つ以上のACEのサイズを足した値になります。 1つのACEのサイズは、SIDの長さとACCESS_ALLOWED_ACE構造体のサイズ(SIDメンバのサイズを除く)を足した値であるため、 それぞれの値をdwAclSizeに加算しています。 ACLのサイズを取得したら、次のコードが実行されます。

pDacl = (PACL)HeapAlloc(GetProcessHeap(), 0, dwDaclSize);
InitializeAcl(pDacl, dwDaclSize, ACL_REVISION);

ACLのためのメモリを確保し、それをInitializeAclでACL用に初期化します。 これでpDaclに、AddAccessAllowedAceでACEを追加できるようになります。

AddAccessAllowedAce(pDacl, ACL_REVISION, dwAccessMask, pSid);

pSidは現在のユーザーを表しており、 dwAccessMaskはGENERIC_READであるため、 現在のユーザーのみに読み取りを許可するACEがpDaclに追加されることになります。 DACLの初期化が終われば、SetNamedSecurityInfoでオブジェクトにDACLを設定します。

if (SetNamedSecurityInfo(TEXT("sample.txt"), 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);

DACLを設定するため、第3引数にDACL_SECURITY_INFORMATIONを指定し、 第6引数にDACLを指定します。 第3引数にPROTECTED_DACL_SECURITY_INFORMATIONを指定した場合は、 既存のDACLは第6引数に指定したDACLに完全に書き換えられることになります。 このDACLには、書き込みを許可するACEが格納されていませんから、 書き込みに関しては、アカウントの種類に関係なく失敗することになります。

オブジェクトへのアクセスは、UACが原因で失敗することがあります。 たとえば、現在のユーザーがAdministratorsグループのメンバで、 さらにUACが有効である場合、szAccountNameにAdministratorsを指定して今回のプログラムを実行すると、 sample.txtは読み取ることができなくなります。 これは、UACによってトークンに格納されているAdministratorsのSIDが無効にされているからであり、 アクセスチェックの際にAdministratorsのSIDが参照されないためです。 現在のユーザーに対して確実にアクセスを許可したい場合は、 そのユーザーがメンバであるグループではなく、 そのユーザーのSIDをACEに設定するようにします。

DACLを元に戻す

オブジェクトに設定されたDACLを前の状態に戻すことは、 その前のDACLを保存していない限り原則不可能です。 しかし、ファイルのようなオブジェクトはフォルダという階層構造上に存在しており、 既定ではフォルダのACEを継承するようになっています。 よって、このACEを継承するようにSetNamedSecurityInfoを呼び出せば、 オブジェクトのDACLは既定の状態に戻ります。

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

int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev, LPSTR lpszCmdLine, int nCmdShow)
{
	PACL pDacl;

	pDacl = (PACL)HeapAlloc(GetProcessHeap(), 0, sizeof(ACL));
	InitializeAcl(pDacl, sizeof(ACL), ACL_REVISION);

	if (SetNamedSecurityInfo(TEXT("sample.txt"), SE_FILE_OBJECT, DACL_SECURITY_INFORMATION | UNPROTECTED_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);
	
	HeapFree(GetProcessHeap(), 0, pDacl);

	return 0;
}

SetNamedSecurityInfoの第3引数にUNPROTECTED_DACL_SECURITY_INFORMATIONを指定した場合、 オブジェクトのDACLにはコンテナオブジェクト(今回の場合、上位のフォルダ)のACEが含まれるようになります。 ここでさらに、第6引数に空のDACLを指定しているため、 独自のACEがオブジェクトのDACLに含まれることはなく、 DACLは継承ACEだけで構成されることになります。 つまり、セキュリティ記述子を指定せずにファイルを作成した場合と同じDACLになります。

上記コードで、UNPROTECTED_DACL_SECURITY_INFORMATIONを取り除くと、 コンテナオブジェクトのACEは継承せず、第6引数の内容のみが考慮されます。 つまり、オブジェクトに空のDACLが設定されることになります。 空のDACLが設定された場合は、全ての特殊アクセス権の指定が失敗するので注意する必要があります。 ちなみに、第6引数にNULLを指定した場合は、 PROTECTED_DACL_SECURITY_INFORMATIONやUNPROTECTED_DACL_SECURITY_INFORMATIONなど関係なく、 NULL DACLが設定されることになります。 NULL DACLが設定されたオブジェクトにはあらゆるアクセスが可能となるため、 これについても注意する必要があります。



戻る