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

前節では、SetNamedSecurityInfoを呼び出して既存オブジェクトにDACLを設定しましたが、 DACLの設定はオブジェクトの作成時に行うこともできます。 オブジェクトを作成する関数には、セキュリティ記述子を要求する引数が含まれていることが多いため、 この引数にDACLを格納したセキュリティ記述子を指定すればよいことになります。 次に示すInitializeSecurityDescriptorは、メモリをセキュリティ記述子用に初期化します。

BOOL WINAPI InitializeSecurityDescriptor(
  PSECURITY_DESCRIPTOR pSecurityDescriptor,
  DWORD dwRevision
);

pSecurityDescriptorは、SECURITY_DESCRIPTOR構造体のアドレスを指定します。 dwRevisionは、SECURITY_DESCRIPTOR_REVISIONを指定します。

InitializeSecurityDescriptorを呼び出した時点では、 セキュリティ記述子には何もデータが格納されていません。 この場合、セキュリティ記述子にはNULL DACLが格納されていると表現されることがありますが、 NULL DACLではオブジェクトへのアクセスを全て許可してしまうことになるため、 適切なACEを格納したDACLを設定する必要があります。 セキュリティ記述子にDACLを設定するには、SetSecurityDescriptorDaclを呼び出します。

BOOL WINAPI SetSecurityDescriptorDacl(
  PSECURITY_DESCRIPTOR pSecurityDescriptor,
  BOOL bDaclPresent,
  PACL pDacl,
  BOOL bDaclDefaulted
);

pSecurityDescriptorは、SECURITY_DESCRIPTOR構造体のアドレスを指定します。 bDaclPresentは、DACLを設定するかどうかを示します。 TRUEを指定すると、pDaclがセキュリティ記述子に設定されます。 pDaclは、DACLを指定します。 bDaclDefaultedは、DACLが既定のものであるどうかを示します。 独自のDACLを指定する場合は、FALSEを指定します。

InitializeSecurityDescriptorで初期化されたセキュリティ記述子には、 オブジェクトの所有者SIDも含まれていません。 オブジェクトの所有者であれば、オブジェクトのDACLの内容に関係なく、 DACLを書き換える権利が与えられるため、 間違ったDACLを設定してしまったときのことを考えて、 所有者SIDを設定しておいたほうがよいでしょう。 セキュリティ記述子に所有者SIDを設定するには、SetSecurityDescriptorOwnerを呼び出します。

BOOL WINAPI SetSecurityDescriptorOwner(
  PSECURITY_DESCRIPTOR pSecurityDescriptor,
  PSID pOwner,
  BOOL bOwnerDefaulted
);

pSecurityDescriptorは、SECURITY_DESCRIPTOR構造体のアドレスを指定します。 pOwnerは、所有者SIDを指定します。 bOwnerDefaultedは、所有者SIDが既定のものであるどうかを示します。 独自の所有者SIDを指定する場合は、FALSEを指定します。

今回のプログラムは、現在のユーザーだけに全てのアクセスを許可するファイルを作成します。

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

BOOL CreateSecureFile(LPTSTR lpszFileName, PSID pSidOwner, PACL pDacl);
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_ALL;
	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 (CreateSecureFile(TEXT("sample.txt"), pSid, pDacl))
		MessageBox(NULL, TEXT("ファイルを作成しました。"), TEXT("OK"), MB_OK);
	else
		MessageBox(NULL, TEXT("ファイルの作成に失敗しました。"), NULL, MB_ICONWARNING);

	LocalFree(pSid);
	LocalFree(pDacl);

	return 0;
}

BOOL CreateSecureFile(LPTSTR lpszFileName, PSID pSidOwner, PACL pDacl)
{
	HANDLE               hFile;
	SECURITY_ATTRIBUTES  securityAttributes;
	PSECURITY_DESCRIPTOR pSecurityDescriptor;
	
	pSecurityDescriptor = LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH);
	InitializeSecurityDescriptor(pSecurityDescriptor, SECURITY_DESCRIPTOR_REVISION);

	if (pDacl != NULL)
		SetSecurityDescriptorDacl(pSecurityDescriptor, TRUE, pDacl, FALSE);

	if (pSidOwner != NULL)
		SetSecurityDescriptorOwner(pSecurityDescriptor, pSidOwner, FALSE);
	
	securityAttributes.nLength              = sizeof(SECURITY_ATTRIBUTES);
	securityAttributes.lpSecurityDescriptor = pSecurityDescriptor;
	securityAttributes.bInheritHandle       = FALSE;

	hFile = CreateFile(lpszFileName, GENERIC_WRITE, 0, &securityAttributes, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

	LocalFree(pSecurityDescriptor);

	return hFile == INVALID_HANDLE_VALUE;
}

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

DACLを作成するまでの流れは前節と同一です。 まず、ACEに設定するSIDを作成し、次にACEを格納できるだけのDACLのサイズを求めます。 そして、このDACLにACEを追加することで、独自のDACLが完成することになります。 今回のACEのアクセスマスクに、前節と同じようにGENERIC_READを指定すると 作成したファイルに一切書き込みを行うことができなくなるため、 今回は全てのアクセスを許可するGENERIC_ALLを指定します。 自作関数のCreateSecureFileは、内部でセキュリティ記述子を作成し、 これをCreateFileに指定しています。

BOOL CreateSecureFile(LPTSTR lpszFileName, PSID pSidOwner, PACL pDacl)
{
	HANDLE               hFile;
	SECURITY_ATTRIBUTES  securityAttributes;
	PSECURITY_DESCRIPTOR pSecurityDescriptor;
	
	pSecurityDescriptor = LocalAlloc(LPTR, SECURITY_DESCRIPTOR_MIN_LENGTH);
	InitializeSecurityDescriptor(pSecurityDescriptor, SECURITY_DESCRIPTOR_REVISION);

	if (pDacl != NULL)
		SetSecurityDescriptorDacl(pSecurityDescriptor, TRUE, pDacl, FALSE);

	if (pSidOwner != NULL)
		SetSecurityDescriptorOwner(pSecurityDescriptor, pSidOwner, FALSE);
	
	securityAttributes.nLength              = sizeof(SECURITY_ATTRIBUTES);
	securityAttributes.lpSecurityDescriptor = pSecurityDescriptor;
	securityAttributes.bInheritHandle       = FALSE;

	hFile = CreateFile(lpszFileName, GENERIC_WRITE, 0, &securityAttributes, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);

	LocalFree(pSecurityDescriptor);

	return hFile == INVALID_HANDLE_VALUE;
}

まず、LocalAllocでセキュリティ記述子に必要なメモリを確保し、 そのメモリをInitializeSecurityDescriptorで初期化します。 次に、空のセキュリティ記述子にDACLと所有者SIDを設定するべく、 SetSecurityDescriptorDaclとSetSecurityDescriptorOwnerを呼び出します。 ただし、これらのデータを必ず指定しなければならない決まりはないため、 NULLが渡された場合はデータを設定しないようにしています。 セキュリティ記述子が完成したら、これをSECURITY_ATTRIBUTES構造体のlpSecurityDescriptorメンバに指定します。 これで後は、SECURITY_ATTRIBUTES構造体をCreateFileに指定すれば、 作成したファイルには独自のセキュリティ記述子が設定されることになります。

ファイルの削除について

ここでは、ファイルの削除がどのような仕組みで行われるのかを説明します。 まず、ファイルの削除に必要なアクセス権は、DELETEとFILE_DELETE_CHILDです。 これらがアクセスマスク内で両方とも1になっている場合は、 そのファイルを削除することができます。 FILE_GENERIC_WRITEやFILE_GENERIC_READには先のアクセス権が含まれないため、 次のようなコードを実行すると、sample.txtは削除することができないように思えます。

#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;
	DWORD           dwAccessMask = FILE_GENERIC_WRITE | FILE_GENERIC_READ;

	dwSize = sizeof(szAccountName) / sizeof(TCHAR);
	GetUserName(szAccountName, &dwSize);
	BuildExplicitAccessWithName(&explicitAccess, szAccountName, dwAccessMask, GRANT_ACCESS, 0);	
	SetEntriesInAcl(1, &explicitAccess, NULL, &pDacl);
	
	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(pDacl);

	return 0;
}

このコードは、FILE_GENERIC_WRITEとFILE_GENERIC_READを許可するACEをsample.txtに設定していますが、 実際にはsample.txtは削除することができます。 ファイルのようなオブジェクトは、フォルダというコンテナオブジェクトの中に存在しているわけですが、 このフォルダのFILE_DELETE_CHILDが1になっている場合は、下位に存在するファイルの削除を許可することになっているからです。 つまり、ファイルの削除というのは、そのファイル自身のDELETEとFILE_DELETE_CHILDが1になっているのに加えて、 フォルダのFILE_DELETE_CHILDも1になっている必要があります。

次のコードは、フォルダにFILE_GENERIC_WRITEとFILE_GENERIC_READを指定しています。 これらの定数には、FILE_DELETE_CHILDは含まれていませんから、 下位に存在するファイルの削除は許可されないことになります。

#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;
	DWORD           dwAccessMask = FILE_GENERIC_WRITE | FILE_GENERIC_READ;

	dwSize = sizeof(szAccountName) / sizeof(TCHAR);
	GetUserName(szAccountName, &dwSize);
	BuildExplicitAccessWithName(&explicitAccess, szAccountName, dwAccessMask, GRANT_ACCESS, CONTAINER_INHERIT_ACE | OBJECT_INHERIT_ACE);	
	SetEntriesInAcl(1, &explicitAccess, NULL, &pDacl);
	
	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);

	return 0;
}

確かにこのコードを実行するとフォルダ内のファイルは削除することができなくなりますが、 フォルダ内にファイルを作成することは可能になっています。 つまり、新しく作成したファイルを削除できないという問題があります。 それならば、ファイルの作成を許可しなければよいように思えますが、 これをするとファイルの書き込みも許可されないことになります。 理由は、次の定義から分かります。

#define FILE_WRITE_DATA           ( 0x0002 )    // file & pipe
#define FILE_ADD_FILE             ( 0x0002 )    // directory

FILE_WRITE_DATAはファイルの書き込みに必要なアクセス権で、 FILE_ADD_FILEはフォルダの中にファイルを作成する場合に必要なアクセス権です。 これらの定数は同一の値として定義されているため、 フォルダにFILE_ADD_FILEを指定しなかった場合は、 ファイルにFILE_WRITE_DATAが指定されることもなくなります。 ファイルの作成を禁止したいのにも関わらず、既存ファイルへの書き込みも禁止されるのは残念なことですが、 ファイルを無制限に作成できてしまう事態も問題ですから、 フォルダにFILE_GENERIC_WRITEを指定するのは避けるべきといえます。



戻る