Windows-Server-2003/ds/security/winsafer/safeinit.c

1695 lines
46 KiB
C

/*++
Copyright (c) 1997-2000 Microsoft Corporation
Module Name:
SafeInit.c (WinSAFER Initialization)
Abstract:
This module implements the WinSAFER APIs to initialize and
deinitialize all housekeeping and handle tracking structures.
Author:
Jeffrey Lawson (JLawson) - Nov 1999
Environment:
User mode only.
Exported Functions:
CodeAuthzInitialize (privately exported)
SaferiChangeRegistryScope (privately exported)
Revision History:
Created - Nov 1999
--*/
#include "pch.h"
#pragma hdrstop
#include <winsafer.h>
#include <winsaferp.h>
#include <winsafer.rh>
#include "saferp.h"
//#define SAFER_REGISTRY_NOTIFICATIONS
//
// Controls the maximum number of level handles that we will
// permit to be opened at any time.
//
#define MAXIMUM_LEVEL_HANDLES 64
//
// Various globals that are used for the cache of levels and
// identities so that we do not need to go to the registry each time.
//
BOOLEAN g_bInitializedFirstTime = FALSE;
CRITICAL_SECTION g_TableCritSec;
HANDLE g_hKeyCustomRoot;
DWORD g_dwKeyOptions;
DWORD g_dwLevelHandleSequence = 1; // monotonically increasing
DWORD g_dwNumHandlesAllocated = 0; // Number of outstanding handles
//
// All of the following global variables are cached settings that are
// read and parsed from the policy the first time it is needed.
// All of the variables within this block should be considered "stale"
// when the g_bNeedCacheReload flag is TRUE.
//
BOOLEAN g_bNeedCacheReload; // indicates the following vars are stale.
RTL_GENERIC_TABLE g_CodeLevelObjTable;
RTL_GENERIC_TABLE g_CodeIdentitiesTable;
RTL_HANDLE_TABLE g_LevelHandleTable;
BOOLEAN g_bHonorScopeUser;
PAUTHZLEVELTABLERECORD g_DefaultCodeLevel; // effective
PAUTHZLEVELTABLERECORD g_DefaultCodeLevelUser;
PAUTHZLEVELTABLERECORD g_DefaultCodeLevelMachine;
LARGE_INTEGER g_SaferPolicyTimeStamp;
//
// Handles used to receive registry change notifications against the
// currently loaded policy and invalidate the internal cache.
//
#ifdef SAFER_REGISTRY_NOTIFICATIONS
HANDLE g_hRegNotifyEvent; // from CreateEvent
HANDLE g_hWaitNotifyObject; // from RegisterWaitForSingleObject
HANDLE g_hKeyNotifyBase1, g_hKeyNotifyBase2;
#endif
NTSTATUS NTAPI
SaferpSetSingleIdentificationPath(
IN BOOLEAN bAllowCreation,
IN OUT PAUTHZIDENTSTABLERECORD pIdentRecord,
IN PSAFER_PATHNAME_IDENTIFICATION pIdentChanges,
IN BOOL UpdateCache
);
FORCEINLINE BOOLEAN
CodeAuthzpIsPowerOfTwo(
ULONG ulValue
)
/*++
Routine Description:
Determines if the specified value is a whole power of 2.
(ie, exactly one of the following: 1, 2, 4, 8, 16, 32, 64, ...)
Arguments:
ulValue - Integer value to test.
Return Value:
Returns TRUE on success, FALSE on failure.
--*/
{
while (ulValue != 0) {
if (ulValue & 1) {
ulValue >>= 1;
break;
}
ulValue >>= 1;
}
return (ulValue == 0);
}
FORCEINLINE ULONG
CodeAuthzpMakePowerOfTwo(
ULONG ulValue
)
/*++
Routine Description:
Rounds the specified number up to the next whole power of 2.
(ie, exactly one of the following: 1, 2, 4, 8, 16, 32, 64, ...)
Arguments:
ulValue - Integer value to operate on.
Return Value:
Returns the rounded result.
--*/
{
if (ulValue) {
ULONG ulOriginal = ulValue;
ULONG bitmask;
for (bitmask = 1; ulValue != 0 && bitmask != 0 &&
(ulValue & ~bitmask) != 0; bitmask <<= 1) {
ulValue = ulValue & ~bitmask;
}
ASSERTMSG("failed to make a power of two",
CodeAuthzpIsPowerOfTwo(ulValue));
if (ulOriginal > ulValue) {
// if we ended up rounding down, then round it up!
ulValue <<= 1;
}
ASSERT(ulValue >= ulOriginal);
}
return ulValue;
}
BOOLEAN
CodeAuthzInitialize (
IN HANDLE Handle,
IN DWORD Reason,
IN PVOID Reserved
)
/*++
Routine Description:
This is the callback procedure used by the Advapi initialization
and deinitialization.
Arguments:
Handle -
Reason -
Reserved -
Return Value:
Returns TRUE on success, FALSE on failure.
--*/
{
NTSTATUS Status;
UNREFERENCED_PARAMETER(Reserved);
UNREFERENCED_PARAMETER(Handle);
if (Reason == DLL_PROCESS_ATTACH) {
Status = CodeAuthzInitializeGlobals();
if (!NT_SUCCESS(Status)) return FALSE;
} else if (Reason == DLL_PROCESS_DETACH) {
CodeAuthzDeinitializeGlobals();
}
return TRUE;
}
#ifdef SAFER_REGISTRY_NOTIFICATIONS
static VOID NTAPI
SaferpRegistryNotificationRegister(VOID)
{
if (g_hRegNotifyEvent != NULL)
{
// Note that it is okay to call RNCKV again on the same
// registry handle even if there is still an outstanding
// change notification registered.
if (g_hKeyNotifyBase1 != NULL) {
RegNotifyChangeKeyValue(
g_hKeyNotifyBase1, TRUE,
REG_NOTIFY_CHANGE_NAME | REG_NOTIFY_CHANGE_LAST_SET,
g_hRegNotifyEvent,
TRUE);
}
if (g_hKeyNotifyBase2 != NULL) {
RegNotifyChangeKeyValue(
g_hKeyNotifyBase2, TRUE,
REG_NOTIFY_CHANGE_NAME | REG_NOTIFY_CHANGE_LAST_SET,
g_hRegNotifyEvent,
TRUE);
}
}
}
static VOID NTAPI
SaferpRegistryNotificationCallback (PVOID pvArg1, BOOLEAN bArg2)
{
UNREFERENCED_PARAMETER(pvArg1);
UNREFERENCED_PARAMETER(bArg2);
g_bNeedCacheReload = TRUE;
}
#endif
NTSTATUS NTAPI
CodeAuthzInitializeGlobals(VOID)
/*++
Routine Description:
Performs one-time startup operations that should be done before
any other handle or cache table operations are attempted.
Arguments:
nothing
Return Value:
Returns STATUS_SUCCESS on success.
--*/
{
NTSTATUS Status;
ULONG ulHandleEntrySize;
if (g_bInitializedFirstTime) {
// Already initialized.
return STATUS_SUCCESS;
}
//
// Initialize a bunch of tables for their first usage.
//
Status = RtlInitializeCriticalSection(&g_TableCritSec);
if (!NT_SUCCESS(Status)) {
return Status;
}
g_bInitializedFirstTime = g_bNeedCacheReload = TRUE;
CodeAuthzLevelObjpInitializeTable(&g_CodeLevelObjTable);
CodeAuthzGuidIdentsInitializeTable(&g_CodeIdentitiesTable);
g_hKeyCustomRoot = NULL;
g_dwKeyOptions = 0;
//
// Initialize the table that will be used to track opened
// WinSafer Level handles. Note that RtlInitializeHandleTable
// requires a structure size that is a whole power of 2.
//
ulHandleEntrySize = CodeAuthzpMakePowerOfTwo(sizeof(AUTHZLEVELHANDLESTRUCT));
RtlInitializeHandleTable(
MAXIMUM_LEVEL_HANDLES,
ulHandleEntrySize, // was sizeof(AUTHZLEVELHANDLESTRUCT)
&g_LevelHandleTable);
g_dwNumHandlesAllocated = 0;
#ifdef SAFER_REGISTRY_NOTIFICATIONS
//
// Create the event to catch modifications to the registry changes.
// This allows us to notice policy changes and reload as necessary.
//
g_hRegNotifyEvent = CreateEvent(
NULL, // Security Descriptor
TRUE, // reset type
FALSE, // initial state
NULL // object name
);
if (g_hRegNotifyEvent != INVALID_HANDLE_VALUE) {
if (!RegisterWaitForSingleObject(
&g_hWaitNotifyObject,
g_hRegNotifyEvent,
SaferpRegistryNotificationCallback,
NULL,
INFINITE,
WT_EXECUTEINWAITTHREAD))
{
CloseHandle(g_hRegNotifyEvent);
g_hRegNotifyEvent = g_hWaitNotifyObject = NULL;
}
} else {
g_hRegNotifyEvent = g_hWaitNotifyObject = NULL;
}
g_hKeyNotifyBase1 = g_hKeyNotifyBase2 = NULL;
#endif
return STATUS_SUCCESS;
}
VOID NTAPI
CodeAuthzDeinitializeGlobals(VOID)
/*++
Routine Description:
Performs one-time deinitialization operations.
Arguments:
nothing
Return Value:
Returns STATUS_SUCCESS on success.
--*/
{
if (g_bInitializedFirstTime) {
#ifdef SAFER_REGISTRY_NOTIFICATIONS
if (g_hWaitNotifyObject != NULL) {
UnregisterWait(g_hWaitNotifyObject);
CloseHandle(g_hWaitNotifyObject);
}
if (g_hRegNotifyEvent != NULL) {
CloseHandle(g_hRegNotifyEvent);
}
if (g_hKeyNotifyBase1 != NULL) {
NtClose(g_hKeyNotifyBase1);
}
if (g_hKeyNotifyBase2 != NULL) {
NtClose(g_hKeyNotifyBase2);
}
#endif
CodeAuthzLevelObjpEntireTableFree(&g_CodeLevelObjTable);
CodeAuthzGuidIdentsEntireTableFree(&g_CodeIdentitiesTable);
RtlDestroyHandleTable(&g_LevelHandleTable);
g_dwNumHandlesAllocated = 0;
if (g_hKeyCustomRoot != NULL) {
NtClose(g_hKeyCustomRoot);
g_hKeyCustomRoot = NULL;
g_dwKeyOptions = 0;
}
g_bInitializedFirstTime = FALSE;
RtlDeleteCriticalSection(&g_TableCritSec);
}
}
BOOL WINAPI
SaferiChangeRegistryScope(
IN HKEY hKeyCustomRoot OPTIONAL,
IN DWORD dwKeyOptions
)
/*++
Routine Description:
Closes and invalidates all currently open level handles and
reloads all cached levels and identities. The outstanding
handles are closed and freed during this operation.
If a hKeyCustomRoot is specified, all future level and identity
operations will be performed on the levels and identies defined
within that registry scope. Otherwise, such operations will be
done on the normal HKLM/HKCU policy store locations.
Arguments:
hKeyCustomRoot - If specified, this should be an opened
registry key handle to the base of the policy
storage that should be used for all future operations.
dwKeyOptions - Additional flags that should be passed in with
the dwOptions parameter to any RegCreateKey operations,
such as REG_OPTION_VOLATILE.
Return Value:
Returns TRUE on success, FALSE on failure. On error, GetLastError()
will return a more specific indicator of the nature of the failure.
--*/
{
NTSTATUS Status;
Status = CodeAuthzReloadCacheTables(
(HANDLE) hKeyCustomRoot,
dwKeyOptions,
FALSE
);
if (NT_SUCCESS(Status)) {
return TRUE;
} else {
BaseSetLastNTError(Status);
return FALSE;
}
}
NTSTATUS NTAPI
CodeAuthzReloadCacheTables(
IN HANDLE hKeyCustomRoot OPTIONAL,
IN DWORD dwKeyOptions,
IN BOOLEAN bImmediateLoad
)
/*++
Routine Description:
Closes and invalidates all currently open level handles and
reloads all cached levels and identities. The outstanding
handles are closed and freed during this operation.
If a hKeyCustomRoot is specified, all future level and identity
operations will be performed on the levels and identies defined
within that registry scope. Otherwise, such operations will be
done on the normal HKLM/HKCU policy store locations.
Arguments:
hKeyCustomRoot - If specified, this should be an opened
registry key handle to the base of the policy
storage that should be used for all future operations.
bPopulateDefaults - If TRUE, then the default set of Level definitions
will be inserted into the Registry if no existing Levels
were found at the specified scope.
Return Value:
Returns STATUS_SUCCESS on success.
--*/
{
NTSTATUS Status = STATUS_SUCCESS;
//
// Ensure that the general globals have been initialized.
//
if (!g_bInitializedFirstTime) {
Status = STATUS_UNSUCCESSFUL;
goto ExitHandler;
}
RtlEnterCriticalSection(&g_TableCritSec);
//
// Initialize and blank out the tables we will be using.
//
CodeAuthzLevelObjpEntireTableFree(&g_CodeLevelObjTable);
CodeAuthzGuidIdentsEntireTableFree(&g_CodeIdentitiesTable);
//
// Increment the sequence number. This has the effect of
// immediately invalidating all currently open handles, but
// allows the caller to still close them properly. Any
// attempt by the caller to actually use one of the old
// handles will result in a STATUS_INVALID_HANDLE error.
//
g_dwLevelHandleSequence++;
//
// Reset the rest of our variables.
//
if (g_hKeyCustomRoot != NULL) {
NtClose(g_hKeyCustomRoot);
g_hKeyCustomRoot = NULL;
}
#ifdef SAFER_REGISTRY_NOTIFICATIONS
if (g_hKeyNotifyBase1 != NULL) {
NtClose(g_hKeyNotifyBase1);
g_hKeyNotifyBase1 = NULL;
}
if (g_hKeyNotifyBase2 != NULL) {
NtClose(g_hKeyNotifyBase2);
g_hKeyNotifyBase2 = NULL;
}
#endif
g_DefaultCodeLevel = g_DefaultCodeLevelMachine =
g_DefaultCodeLevelUser = NULL;
g_bHonorScopeUser = FALSE;
g_bNeedCacheReload = FALSE;
g_dwKeyOptions = 0;
//
// Save a duplicated copy of the custom registry handle.
//
if (ARGUMENT_PRESENT(hKeyCustomRoot))
{
const static UNICODE_STRING SubKeyName = { 0, 0, NULL };
OBJECT_ATTRIBUTES ObjectAttributes;
InitializeObjectAttributes(&ObjectAttributes,
(PUNICODE_STRING) &SubKeyName,
OBJ_CASE_INSENSITIVE,
hKeyCustomRoot,
NULL
);
Status = NtOpenKey(&g_hKeyCustomRoot,
KEY_READ,
&ObjectAttributes);
if (!NT_SUCCESS(Status)) {
goto ExitHandler2;
}
g_dwKeyOptions = dwKeyOptions;
#ifdef SAFER_REGISTRY_NOTIFICATIONS
#error "open for KEY_NOTIFY"
#endif
}
else
{
#ifdef SAFER_REGISTRY_NOTIFICATIONS
#endif
}
//
// Perform the actual load now, if needed.
//
g_bNeedCacheReload = TRUE;
if (bImmediateLoad) {
Status = CodeAuthzpImmediateReloadCacheTables();
} else {
Status = STATUS_SUCCESS;
}
ExitHandler2:
RtlLeaveCriticalSection(&g_TableCritSec);
ExitHandler:
return Status;
}
NTSTATUS NTAPI
CodeAuthzpImmediateReloadCacheTables(
VOID
)
/*++
Routine Description:
Assumes that CodeAuthzReloadCacheTables() has already been called
with the specified scope already, and that this function has not
yet been called since the last reload.
Arguments:
none
Return Value:
returns STATUS_SUCCESS on successful completion.
--*/
{
NTSTATUS Status;
DWORD dwFlagValue;
ASSERT(g_TableCritSec.OwningThread == UlongToHandle(GetCurrentThreadId()));
ASSERT(RtlIsGenericTableEmpty(&g_CodeIdentitiesTable) &&
RtlIsGenericTableEmpty(&g_CodeLevelObjTable));
ASSERT(g_bNeedCacheReload != FALSE);
//
// Need to clear the cache reload flag, otherwise we
// might cause undesired infinite recursion later with
// some of the CodeAuthzPol_xxx functions.
//
g_bNeedCacheReload = FALSE;
//
// Begin loading the new policy settings from the specified location.
//
if (g_hKeyCustomRoot != NULL)
{
//
// Read in the definitions of all WinSafer Levels from
// the custom registry root specified.
//
CodeAuthzLevelObjpLoadTable(
&g_CodeLevelObjTable,
SAFER_SCOPEID_REGISTRY,
g_hKeyCustomRoot);
//
// The HonorScopeUser flag is not relevant when a custom
// registry scope is used, but we set it to false anyways.
//
g_bHonorScopeUser = FALSE;
//
// Load the Code Identities from the custom registry root.
//
CodeAuthzGuidIdentsLoadTableAll(
&g_CodeLevelObjTable,
&g_CodeIdentitiesTable,
SAFER_SCOPEID_REGISTRY,
g_hKeyCustomRoot);
//
// Load the Default Level specified by the custom registry root.
//
Status = CodeAuthzPol_GetInfoRegistry_DefaultLevel(
SAFER_SCOPEID_REGISTRY,
sizeof(DWORD), &dwFlagValue, NULL);
if (NT_SUCCESS(Status)) {
g_DefaultCodeLevelMachine =
CodeAuthzLevelObjpLookupByLevelId(
&g_CodeLevelObjTable, dwFlagValue);
} else {
g_DefaultCodeLevelMachine = NULL;
}
g_DefaultCodeLevelUser = NULL;
}
else // !ARGUMENT_PRESENT(hKeyCustomRoot)
{
g_hKeyCustomRoot = NULL;
//
// Read in the definitions of all WinSafer Levels from
// the HKEY_LOCAL_MACHINE registry scope.
//
CodeAuthzLevelObjpLoadTable(
&g_CodeLevelObjTable,
SAFER_SCOPEID_MACHINE,
NULL);
g_bHonorScopeUser = TRUE;
//
// Load in all Code Identities from the HKEY_LOCAL_MACHINE
// and possibly the HKEY_CURRENT_USER scope too.
//
CodeAuthzGuidIdentsLoadTableAll(
&g_CodeLevelObjTable,
&g_CodeIdentitiesTable,
SAFER_SCOPEID_MACHINE,
NULL);
if (g_bHonorScopeUser) {
CodeAuthzGuidIdentsLoadTableAll(
&g_CodeLevelObjTable,
&g_CodeIdentitiesTable,
SAFER_SCOPEID_USER,
NULL);
}
//
// Load the Default Level specified by the machine scope.
//
Status = CodeAuthzPol_GetInfoRegistry_DefaultLevel(
SAFER_SCOPEID_MACHINE,
sizeof(DWORD), &dwFlagValue, NULL);
if (NT_SUCCESS(Status)) {
g_DefaultCodeLevelMachine =
CodeAuthzLevelObjpLookupByLevelId(
&g_CodeLevelObjTable, dwFlagValue);
} else {
g_DefaultCodeLevelMachine = NULL;
}
//
// Load the Default Level specified by the user scope.
//
Status = CodeAuthzPol_GetInfoRegistry_DefaultLevel(
SAFER_SCOPEID_USER,
sizeof(DWORD), &dwFlagValue, NULL);
if (NT_SUCCESS(Status)) {
g_DefaultCodeLevelUser =
CodeAuthzLevelObjpLookupByLevelId(
&g_CodeLevelObjTable, dwFlagValue);
} else {
g_DefaultCodeLevelUser = NULL;
}
}
//
// Compute the effective Default Level (take the least privileged).
//
CodeAuthzpRecomputeEffectiveDefaultLevel();
GetSystemTimeAsFileTime((LPFILETIME) &g_SaferPolicyTimeStamp);
//
// Now that we have fully loaded the policy, set a change
// notification hook so that we can be alerted to updates.
//
#ifdef SAFER_REGISTRY_NOTIFICATIONS
g_bNeedCacheReload = FALSE;
SaferpRegistryNotificationRegister();
#endif
return STATUS_SUCCESS;
}
VOID NTAPI
CodeAuthzpRecomputeEffectiveDefaultLevel(
VOID
)
/*++
Routine Description:
Arguments:
nothing
Return Value:
nothing.
--*/
{
if (g_DefaultCodeLevelMachine != NULL &&
g_DefaultCodeLevelUser != NULL &&
g_bHonorScopeUser)
{
g_DefaultCodeLevel =
(g_DefaultCodeLevelMachine->dwLevelId <
g_DefaultCodeLevelUser->dwLevelId ?
g_DefaultCodeLevelMachine : g_DefaultCodeLevelUser);
} else if (g_DefaultCodeLevelMachine != NULL) {
g_DefaultCodeLevel = g_DefaultCodeLevelMachine;
} else if (g_bHonorScopeUser) {
g_DefaultCodeLevel = g_DefaultCodeLevelUser;
} else {
g_DefaultCodeLevel = NULL;
}
//
// If we still don't have a default Level, then try to pick
// the Fully Trusted level as default. It still might fail
// in the case where the Fully Trusted level doesn't exist,
// but that shouldn't ever happen.
//
if (!g_DefaultCodeLevel) {
g_DefaultCodeLevel = CodeAuthzLevelObjpLookupByLevelId(
&g_CodeLevelObjTable, SAFER_LEVELID_FULLYTRUSTED);
// ASSERT(g_DefaultCodeLevel != NULL);
}
}
NTSTATUS NTAPI
CodeAuthzpDeleteKeyRecursively(
IN HANDLE hBaseKey,
IN PUNICODE_STRING pSubKey OPTIONAL
)
/*++
Routine Description:
Recursively delete the key, including all child values and keys.
Arguments:
hkey - the base registry key handle to start from.
pszSubKey - subkey to delete from.
Return Value:
Returns ERROR_SUCCESS on success, otherwise error.
--*/
{
NTSTATUS Status;
BOOLEAN bCloseSubKey;
HANDLE hSubKey;
OBJECT_ATTRIBUTES ObjectAttributes;
UNICODE_STRING UnicodeString;
PKEY_BASIC_INFORMATION pKeyBasicInfo;
DWORD dwQueryBufferSize = 0, dwActualSize = 0;
//
// Open the subkey so we can enumerate any children
//
if (ARGUMENT_PRESENT(pSubKey) &&
pSubKey->Buffer != NULL)
{
InitializeObjectAttributes(&ObjectAttributes,
pSubKey,
OBJ_CASE_INSENSITIVE,
hBaseKey,
NULL
);
Status = NtOpenKey(&hSubKey, KEY_READ | DELETE, &ObjectAttributes);
if (!NT_SUCCESS(Status)) {
return Status;
}
bCloseSubKey = TRUE;
} else {
hSubKey = hBaseKey;
bCloseSubKey = FALSE;
}
//
// To delete a registry key, we must first ensure that all
// children subkeys are deleted (registry values do not need
// to be deleted in order to delete the key itself). To do
// this we loop enumerate
//
dwQueryBufferSize = 256;
pKeyBasicInfo = RtlAllocateHeap(RtlProcessHeap(), 0,
dwQueryBufferSize);
for (;;)
{
Status = NtEnumerateKey(
hSubKey, 0, KeyBasicInformation,
pKeyBasicInfo, dwQueryBufferSize, &dwActualSize);
if (Status == STATUS_BUFFER_TOO_SMALL ||
Status == STATUS_BUFFER_OVERFLOW)
{
if (dwActualSize <= dwQueryBufferSize) {
ASSERT(FALSE);
break; // should not happen, so stop now.
}
if (pKeyBasicInfo != NULL) {
RtlFreeHeap(RtlProcessHeap(), 0, pKeyBasicInfo);
}
dwQueryBufferSize = dwActualSize; // request a little more.
pKeyBasicInfo = RtlAllocateHeap(RtlProcessHeap(), 0,
dwQueryBufferSize);
if (!pKeyBasicInfo) {
break; // stop now, but we don't care about the error.
}
Status = NtEnumerateKey(
hSubKey, 0, KeyBasicInformation,
pKeyBasicInfo, dwQueryBufferSize, &dwActualSize);
}
if (Status == STATUS_NO_MORE_ENTRIES) {
// we've finished deleting all subkeys, stop now.
Status = STATUS_SUCCESS;
break;
}
if (!NT_SUCCESS(Status) || !pKeyBasicInfo) {
break;
}
UnicodeString.Buffer = pKeyBasicInfo->Name;
UnicodeString.MaximumLength = (USHORT) pKeyBasicInfo->NameLength;
UnicodeString.Length = (USHORT) (pKeyBasicInfo->NameLength - sizeof(WCHAR));
Status = CodeAuthzpDeleteKeyRecursively(hSubKey, &UnicodeString);
if (!NT_SUCCESS(Status)) {
break;
}
}
if (pKeyBasicInfo != NULL) {
RtlFreeHeap(RtlProcessHeap(), 0, pKeyBasicInfo);
}
Status = NtDeleteKey(hSubKey);
if (bCloseSubKey) {
NtClose(hSubKey);
}
return Status;
}
NTSTATUS NTAPI
CodeAuthzpFormatLevelKeyPath(
IN DWORD dwLevelId,
IN OUT PUNICODE_STRING UnicodeSuffix
)
/*++
Routine Description:
Internal function to generate the path to a given subkey within the
WinSafer policy store for the storage of a given Level. The
resulting path can then be supplied to CodeAuthzpOpenPolicyRootKey
Arguments:
dwLevelId - the LevelId to process.
UnicodeSuffix - Specifies the output buffer. The Buffer and
MaximumLength fields must be supplied, but the Length
field is ignored.
Return Value:
Returns STATUS_SUCCESS on success.
--*/
{
NTSTATUS Status;
UNICODE_STRING UnicodeTemp;
if (!ARGUMENT_PRESENT(UnicodeSuffix)) {
Status = STATUS_ACCESS_VIOLATION;
goto ExitHandler;
}
UnicodeSuffix->Length = 0;
Status = RtlAppendUnicodeToString(
UnicodeSuffix,
SAFER_OBJECTS_REGSUBKEY L"\\");
if (!NT_SUCCESS(Status)) {
goto ExitHandler;
}
UnicodeTemp.Buffer = &UnicodeSuffix->Buffer[
UnicodeSuffix->Length / sizeof(WCHAR) ];
UnicodeTemp.MaximumLength = (UnicodeSuffix->MaximumLength -
UnicodeSuffix->Length);
Status = RtlIntegerToUnicodeString(dwLevelId,
10,
&UnicodeTemp);
if (!NT_SUCCESS(Status)) {
goto ExitHandler;
}
UnicodeSuffix->Length += UnicodeTemp.Length;
ExitHandler:
return Status;
}
NTSTATUS NTAPI
CodeAuthzpFormatIdentityKeyPath(
IN DWORD dwLevelId,
IN LPCWSTR szIdentityType,
IN REFGUID refIdentGuid,
IN OUT PUNICODE_STRING UnicodeSuffix
)
/*++
Routine Description:
Internal function to generate the path to a given subkey within the
WinSafer policy store for the storage of a given Code Identity. The
resulting path can then be supplied to CodeAuthzpOpenPolicyRootKey
Arguments:
dwLevelId - the LevelId to process.
szIdentityType - should be one of the following string constants:
SAFER_PATHS_REGSUBKEY,
SAFER_HASHMD5_REGSUBKEY,
SAFER_SOURCEURL_REGSUBKEY
refIdentGuid - the GUID of the code identity.
UnicodeSuffix - Specifies the output buffer. The Buffer and
MaximumLength fields must be supplied, but the Length
field is ignored.
Return Value:
Returns STATUS_SUCCESS on success.
--*/
{
NTSTATUS Status;
UNICODE_STRING UnicodeTemp;
if (!ARGUMENT_PRESENT(refIdentGuid)) {
Status = STATUS_INVALID_PARAMETER;
goto ExitHandler;
}
if (!ARGUMENT_PRESENT(UnicodeSuffix)) {
Status = STATUS_ACCESS_VIOLATION;
goto ExitHandler;
}
UnicodeSuffix->Length = 0;
Status = RtlAppendUnicodeToString(
UnicodeSuffix,
SAFER_CODEIDS_REGSUBKEY L"\\");
if (!NT_SUCCESS(Status)) {
goto ExitHandler;
}
UnicodeTemp.Buffer = &UnicodeSuffix->Buffer[
UnicodeSuffix->Length / sizeof(WCHAR) ];
UnicodeTemp.MaximumLength = (UnicodeSuffix->MaximumLength -
UnicodeSuffix->Length);
Status = RtlIntegerToUnicodeString(dwLevelId,
10,
&UnicodeTemp);
if (!NT_SUCCESS(Status)) {
goto ExitHandler;
}
UnicodeSuffix->Length += UnicodeTemp.Length;
Status = RtlAppendUnicodeToString(
UnicodeSuffix,
L"\\");
if (!NT_SUCCESS(Status)) {
goto ExitHandler;
}
Status = RtlAppendUnicodeToString(
UnicodeSuffix,
szIdentityType);
if (!NT_SUCCESS(Status)) {
goto ExitHandler;
}
Status = RtlAppendUnicodeToString(
UnicodeSuffix,
L"\\");
if (!NT_SUCCESS(Status)) {
goto ExitHandler;
}
Status = RtlStringFromGUID(refIdentGuid, &UnicodeTemp);
if (!NT_SUCCESS(Status)) {
goto ExitHandler;
}
Status = RtlAppendUnicodeStringToString(
UnicodeSuffix, &UnicodeTemp);
RtlFreeUnicodeString(&UnicodeTemp);
ExitHandler:
return Status;
}
NTSTATUS NTAPI
CodeAuthzpOpenPolicyRootKey(
IN DWORD dwScopeId,
IN HANDLE hKeyCustomBase OPTIONAL,
IN LPCWSTR szRegistrySuffix OPTIONAL,
IN ACCESS_MASK DesiredAccess,
IN BOOLEAN bCreateKey,
OUT HANDLE *OpenedHandle
)
/*++
Routine Description:
Internal function to generate the path to a given subkey within the
WinSAFER policy store within the registry and then open that key.
The specified subkeys can optionally be automatically created if
they do not already exist.
Arguments:
dwScopeId - input scope identifier. This must be one of
SAFER_SCOPEID_MACHINE or SAFER_SCOPEID_USER or SAFER_SCOPEID_REGISTRY.
hKeyCustomBase - only used if dwScopeId is SAFER_SCOPEID_REGISTRY.
szRegistrySuffix - optionally specifies a subkey name to open under
the scope being referenced.
DesiredAccess - specifies the access that should be used to open
the registry key. For example, use KEY_READ for read access.
bCreateKey - if true, the key will be created if it does not exist.
OpenedHandle - pointer that recieves the opened handle. This handle
must be closed by the caller with NtClose()
Return Value:
Returns STATUS_SUCCESS on success.
--*/
{
NTSTATUS Status;
WCHAR KeyPathBuffer[MAX_PATH];
HANDLE hKeyPolicyBase;
UNICODE_STRING SubKeyName;
OBJECT_ATTRIBUTES ObjectAttributes;
USHORT KeyLength = 0;
//
// Verify that we were given a pointer to write the final handle to.
//
if (!ARGUMENT_PRESENT(OpenedHandle)) {
return STATUS_INVALID_PARAMETER_4;
}
if (ARGUMENT_PRESENT(szRegistrySuffix))
{
KeyLength = (wcslen(szRegistrySuffix) + 1 ) * sizeof(WCHAR);
}
//
// Evaluate the Scope and build the full registry path that we will
// use to open a handle to this key.
//
SubKeyName.Buffer = KeyPathBuffer;
SubKeyName.Length = 0;
SubKeyName.MaximumLength = sizeof(KeyPathBuffer);
if (dwScopeId == SAFER_SCOPEID_MACHINE)
{
KeyLength += sizeof(WCHAR) + sizeof(SAFER_HKCU_REGBASE) + sizeof(L"\\Registry\\Machine\\");
if (SubKeyName.MaximumLength < KeyLength)
{
SubKeyName.Buffer = RtlAllocateHeap(RtlProcessHeap(), 0,
KeyLength);
if (SubKeyName.Buffer == NULL)
{
return STATUS_NO_MEMORY;
}
SubKeyName.MaximumLength = KeyLength;
}
Status = RtlAppendUnicodeToString(&SubKeyName,
L"\\Registry\\Machine\\" SAFER_HKLM_REGBASE );
hKeyPolicyBase = NULL;
}
else if (dwScopeId == SAFER_SCOPEID_USER)
{
UNICODE_STRING CurrentUserKeyPath;
Status = RtlFormatCurrentUserKeyPath( &CurrentUserKeyPath );
if (NT_SUCCESS( Status ) )
{
KeyLength += CurrentUserKeyPath.Length + sizeof(WCHAR) +
sizeof(SAFER_HKCU_REGBASE);
if (SubKeyName.MaximumLength < KeyLength)
{
SubKeyName.Buffer = RtlAllocateHeap(RtlProcessHeap(), 0,
KeyLength);
if (SubKeyName.Buffer == NULL)
{
return STATUS_NO_MEMORY;
}
SubKeyName.MaximumLength = KeyLength;
}
Status = RtlAppendUnicodeStringToString(
&SubKeyName, &CurrentUserKeyPath );
RtlFreeUnicodeString( &CurrentUserKeyPath );
}
if (!NT_SUCCESS(Status)) {
goto Cleanup;
}
Status = RtlAppendUnicodeToString( &SubKeyName,
L"\\" SAFER_HKCU_REGBASE );
if (!NT_SUCCESS( Status )) {
goto Cleanup;
}
hKeyPolicyBase = NULL;
}
else if (dwScopeId == SAFER_SCOPEID_REGISTRY)
{
ASSERT(hKeyCustomBase != NULL);
hKeyPolicyBase = hKeyCustomBase;
if (SubKeyName.MaximumLength < KeyLength)
{
SubKeyName.Buffer = RtlAllocateHeap(RtlProcessHeap(), 0,
KeyLength);
if (SubKeyName.Buffer == NULL)
{
return STATUS_NO_MEMORY;
}
SubKeyName.MaximumLength = KeyLength;
}
}
else {
return STATUS_INVALID_PARAMETER_1;
}
//
// Append whatever suffix we're supposed to append if one was given.
//
if (ARGUMENT_PRESENT(szRegistrySuffix)) {
if (SubKeyName.Length > 0)
{
// We are appending a suffix to a partial path, so
// make sure there is at least a single backslash
// dividing the two strings (extra are fine).
if (*szRegistrySuffix != L'\\') {
Status = RtlAppendUnicodeToString(&SubKeyName, L"\\");
if (!NT_SUCCESS(Status)) {
goto Cleanup;
}
}
} else if (hKeyPolicyBase != NULL) {
// Otherwise we are opening a key relative to a custom
// specified key, and the supplied suffix happens to be
// the first part of the path, so ensure there are no
// leading backslashes.
while (*szRegistrySuffix != UNICODE_NULL &&
*szRegistrySuffix == L'\\') szRegistrySuffix++;
}
Status = RtlAppendUnicodeToString(&SubKeyName, szRegistrySuffix);
if (!NT_SUCCESS(Status)) {
goto Cleanup;
}
}
//
// Open a handle to the registry path that we are supposed to open.
//
InitializeObjectAttributes(&ObjectAttributes,
&SubKeyName,
OBJ_CASE_INSENSITIVE,
hKeyPolicyBase,
NULL
);
if (bCreateKey) {
Status = NtCreateKey(OpenedHandle, DesiredAccess,
&ObjectAttributes, 0, NULL,
g_dwKeyOptions, NULL);
if (Status == STATUS_OBJECT_NAME_NOT_FOUND)
{
BOOLEAN bAtLeastOnce;
USHORT uIndex, uFinalLength;
//
// If we fail on the first try to open the full path, then
// it is possible that one or more of the parent keys did
// not already exist, so we have to retry for each.
//
uFinalLength = (SubKeyName.Length / sizeof(WCHAR));
bAtLeastOnce = FALSE;
for (uIndex = 0; uIndex < uFinalLength; uIndex++) {
if (SubKeyName.Buffer[uIndex] == L'\\' ) {
HANDLE hTempKey;
SubKeyName.Length = uIndex * sizeof(WCHAR);
Status = NtCreateKey(&hTempKey, DesiredAccess,
&ObjectAttributes, 0, NULL,
g_dwKeyOptions, NULL);
if (NT_SUCCESS(Status)) {
NtClose(hTempKey);
} else if (Status == STATUS_OBJECT_NAME_NOT_FOUND) {
// one of the keys leading up here still failed.
break;
}
bAtLeastOnce = TRUE;
}
}
if (bAtLeastOnce) {
SubKeyName.Length = uFinalLength * sizeof(WCHAR);
Status = NtCreateKey(OpenedHandle, DesiredAccess,
&ObjectAttributes, 0, NULL,
g_dwKeyOptions, NULL);
}
}
} else {
Status = NtOpenKey(OpenedHandle, DesiredAccess,
&ObjectAttributes);
}
Cleanup:
if ((SubKeyName.Buffer != NULL) && (SubKeyName.Buffer != KeyPathBuffer))
{
RtlFreeHeap(RtlProcessHeap(), 0, SubKeyName.Buffer);
}
return Status;
}
BOOL WINAPI
SaferiPopulateDefaultsInRegistry(
IN HKEY hKeyBase,
OUT BOOL *pbSetDefaults
)
/*++
Routine Description:
Winsafer UI will use this API to populate default winsafer values
in the registry as follows:
DefaultLevel: SAFER_LEVELID_FULLYTRUSTED
ExecutableTypes: initialized to the latest list of attachment types
TransparentEnabled: 1
Policy Scope: 0 (enable policy for admins)
Level descriptions
Arguments:
hKeyBase - This should be an opened registry key handle to the
base of the policy storage that should be used for
to populate the defaults into. This handle should be
opened with a miniumum of KEY_SET_VALUE access.
pbSetDefaults - Pointer to a boolean that gets set when
default values are actually set (UI uses this).
Return Value:
returns STATUS_SUCCESS on successful completion.
--*/
{
#define SAFERP_WINDOWS L"%HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\SystemRoot%"
GUID WindowsGuid = SAFERP_WINDOWS_GUID;
#define SAFERP_WINDOWS_EXE L"%HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\SystemRoot%\\*.exe"
GUID WindowsExeGuid = SAFERP_WINDOWS_EXE_GUID;
#define SAFERP_SYSTEM_EXE L"%HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\SystemRoot%\\System32\\*.exe"
GUID SystemExeGuid = SAFERP_SYSTEM_EXE_GUID;
#define SAFERP_PROGRAMFILES L"%HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\ProgramFilesDir%"
GUID ProgramFilesGuid = SAFERP_PROGRAMFILES_GUID;
NTSTATUS Status;
DWORD dwValueValue;
PWSTR pmszFileTypes = NULL;
UNICODE_STRING ValueName;
ULONG uResultLength = 0;
KEY_NAME_INFORMATION pKeyInformation;
UNICODE_STRING ucSubKeyName;
WCHAR szSubKeyPath[] = L"Software\\Policies\\Microsoft\\Windows\\Safer\\CodeIdentifiers\0";
OBJECT_ATTRIBUTES ObjectAttributes;
HKEY hKeyFinal = NULL;
HANDLE hAdvApiInst;
AUTHZIDENTSTABLERECORD LocalRecord = {0};
SAFER_PATHNAME_IDENTIFICATION PathIdent = {0};
DWORD dwLevelIndex;
BYTE QueryBuffer[sizeof(KEY_VALUE_PARTIAL_INFORMATION) + 64];
PKEY_VALUE_PARTIAL_INFORMATION pKeyValueInfo = (PKEY_VALUE_PARTIAL_INFORMATION) QueryBuffer;
DWORD dwActualSize = 0;
if (!ARGUMENT_PRESENT(hKeyBase) || !ARGUMENT_PRESENT(pbSetDefaults)) {
Status = STATUS_INVALID_PARAMETER;
goto ExitHandler;
}
*pbSetDefaults = TRUE;
RtlInitUnicodeString(&ucSubKeyName, szSubKeyPath);
InitializeObjectAttributes(&ObjectAttributes,
(PUNICODE_STRING) &ucSubKeyName,
OBJ_CASE_INSENSITIVE,
hKeyBase,
NULL
);
Status = NtOpenKey(&hKeyFinal,
KEY_WRITE | KEY_READ,
&ObjectAttributes);
if (Status == STATUS_OBJECT_NAME_NOT_FOUND) {
Status = NtCreateKey(&hKeyFinal,
KEY_WRITE | KEY_READ,
&ObjectAttributes,
0,
NULL,
REG_OPTION_NON_VOLATILE,
NULL);
}
if (!NT_SUCCESS(Status)) {
goto ExitHandler;
}
//
// check if any default value is absent
// if so, populate all the defaults again
// if not, do not populate any values and return
//
RtlInitUnicodeString(&ValueName, SAFER_DEFAULTOBJ_REGVALUE);
Status = NtQueryValueKey(
hKeyFinal,
(PUNICODE_STRING) &ValueName,
KeyValuePartialInformation,
pKeyValueInfo,
sizeof(QueryBuffer),
&dwActualSize
);
if ( Status == STATUS_OBJECT_NAME_NOT_FOUND ) {
goto PopulateAllDefaults;
}
RtlInitUnicodeString(&ValueName, SAFER_TRANSPARENTENABLED_REGVALUE);
Status = NtQueryValueKey(
hKeyFinal,
(PUNICODE_STRING) &ValueName,
KeyValuePartialInformation,
pKeyValueInfo,
sizeof(QueryBuffer),
&dwActualSize
);
if ( Status == STATUS_OBJECT_NAME_NOT_FOUND ) {
goto PopulateAllDefaults;
}
RtlInitUnicodeString(&ValueName, SAFER_POLICY_SCOPE);
Status = NtQueryValueKey(
hKeyFinal,
(PUNICODE_STRING) &ValueName,
KeyValuePartialInformation,
pKeyValueInfo,
sizeof(QueryBuffer),
&dwActualSize
);
if ( Status == STATUS_OBJECT_NAME_NOT_FOUND ) {
goto PopulateAllDefaults;
}
RtlInitUnicodeString(&ValueName, SAFER_EXETYPES_REGVALUE);
Status = NtQueryValueKey(
hKeyFinal,
(PUNICODE_STRING) &ValueName,
KeyValuePartialInformation,
pKeyValueInfo,
sizeof(QueryBuffer),
&dwActualSize
);
if ( Status == STATUS_OBJECT_NAME_NOT_FOUND ) {
goto PopulateAllDefaults;
}
//
// all default values are present or there was an error
// querying one of the values no need to populate any
//
*pbSetDefaults = FALSE;
goto ExitHandler;
PopulateAllDefaults:
RtlInitUnicodeString(&ValueName, SAFER_DEFAULTOBJ_REGVALUE);
dwValueValue = SAFER_LEVELID_FULLYTRUSTED;
Status = NtSetValueKey(hKeyFinal,
&ValueName,
0,
REG_DWORD,
&dwValueValue,
sizeof(DWORD));
if (!NT_SUCCESS(Status))
goto ExitHandler;
dwValueValue = 1;
RtlInitUnicodeString(&ValueName, SAFER_TRANSPARENTENABLED_REGVALUE);
Status = NtSetValueKey(hKeyFinal,
&ValueName,
0,
REG_DWORD,
&dwValueValue,
sizeof(DWORD));
if (!NT_SUCCESS(Status))
goto ExitHandler;
dwValueValue = 0;
RtlInitUnicodeString(&ValueName, SAFER_POLICY_SCOPE);
Status = NtSetValueKey(hKeyFinal,
&ValueName,
0,
REG_DWORD,
&dwValueValue,
sizeof(DWORD));
if (!NT_SUCCESS(Status))
goto ExitHandler;
//
// prepare the MULTI_SZ value to write to the registry
//
RtlInitUnicodeString(&ValueName, SAFER_EXETYPES_REGVALUE);
pmszFileTypes = RtlAllocateHeap(RtlProcessHeap(),
0,
sizeof(SAFER_DEFAULT_EXECUTABLE_FILE_TYPES)
);
if (pmszFileTypes) {
RtlCopyMemory(pmszFileTypes,
SAFER_DEFAULT_EXECUTABLE_FILE_TYPES,
sizeof(SAFER_DEFAULT_EXECUTABLE_FILE_TYPES));
Status = NtSetValueKey(hKeyFinal,
&ValueName,
0,
REG_MULTI_SZ,
pmszFileTypes,
sizeof(SAFER_DEFAULT_EXECUTABLE_FILE_TYPES)
);
RtlFreeHeap(RtlProcessHeap(),
0,
pmszFileTypes
);
}
else {
Status = STATUS_NO_MEMORY;
goto ExitHandler;
}
//
// We now generate 4 rules so that the OS binaries are exempt.
// FULLY TRUSTED
// %windir%
// %windir%\*.exe
// %windir%\system32\*.exe
// %ProgramFiles%
//
LocalRecord.dwIdentityType = SaferIdentityTypeImageName;
LocalRecord.dwLevelId = SAFER_LEVELID_FULLYTRUSTED;
LocalRecord.dwScopeId = SAFER_SCOPEID_REGISTRY;
LocalRecord.ImageNameInfo.bExpandVars = TRUE;
LocalRecord.ImageNameInfo.dwSaferFlags = 0;
RtlInitUnicodeString(&LocalRecord.ImageNameInfo.ImagePath, SAFERP_WINDOWS);
LocalRecord.IdentGuid = WindowsGuid;
PathIdent.header.cbStructSize = sizeof(SAFER_IDENTIFICATION_HEADER);
PathIdent.header.dwIdentificationType = SaferIdentityTypeImageName;
PathIdent.header.IdentificationGuid = LocalRecord.IdentGuid;
PathIdent.dwSaferFlags = 0;
PathIdent.ImageName = SAFERP_WINDOWS;
PathIdent.Description[0] = L'\0';
Status = SaferpSetSingleIdentificationPath(TRUE,
&LocalRecord,
&PathIdent,
FALSE
);
if (!NT_SUCCESS(Status))
goto ExitHandler;
RtlInitUnicodeString(&LocalRecord.ImageNameInfo.ImagePath, SAFERP_WINDOWS_EXE);
LocalRecord.IdentGuid = WindowsExeGuid;
PathIdent.header.IdentificationGuid = LocalRecord.IdentGuid;
PathIdent.ImageName = SAFERP_WINDOWS_EXE;
Status = SaferpSetSingleIdentificationPath(TRUE,
&LocalRecord,
&PathIdent,
FALSE
);
if (!NT_SUCCESS(Status))
goto ExitHandler;
RtlInitUnicodeString(&LocalRecord.ImageNameInfo.ImagePath, SAFERP_SYSTEM_EXE);
LocalRecord.IdentGuid = SystemExeGuid;
PathIdent.header.IdentificationGuid = LocalRecord.IdentGuid;
PathIdent.ImageName = SAFERP_SYSTEM_EXE;
Status = SaferpSetSingleIdentificationPath(TRUE,
&LocalRecord,
&PathIdent,
FALSE
);
if (!NT_SUCCESS(Status))
goto ExitHandler;
RtlInitUnicodeString(&LocalRecord.ImageNameInfo.ImagePath, SAFERP_PROGRAMFILES);
LocalRecord.IdentGuid = ProgramFilesGuid;
PathIdent.header.IdentificationGuid = LocalRecord.IdentGuid;
PathIdent.ImageName = SAFERP_PROGRAMFILES;
Status = SaferpSetSingleIdentificationPath(TRUE,
&LocalRecord,
&PathIdent,
FALSE
);
if (!NT_SUCCESS(Status))
goto ExitHandler;
ExitHandler:
if (hKeyFinal) {
NtClose(hKeyFinal);
}
if (NT_SUCCESS(Status)) {
return TRUE;
} else {
BaseSetLastNTError(Status);
return FALSE;
}
}