Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

2954 lines
98 KiB

/*++
Copyright (c) 1999-2000 Microsoft Corporation
Module Name:
SafeIden.c (WinSAFER SaferIdentifyLevel)
Abstract:
This module implements the WinSAFER APIs that evaluate the system
policies to determine which Authorization Level has been configured
to apply restrictions for a specified application or code library.
Author:
Jeffrey Lawson (JLawson) - Nov 1999
Environment:
User mode only.
Exported Functions:
SaferiSearchMatchingHashRules (privately exported)
SaferIdentifyLevel
Revision History:
Created - Nov 1999
--*/
#include "pch.h"
#pragma hdrstop
#include <md5.h>
#include <wchar.h> //for swprintf
#pragma warning(push, 3)
#include <wintrust.h> // WinVerifyTrust
#include <softpub.h> // WINTRUST_ACTION_GENERIC_VERIFY_V2
#pragma warning(pop)
#include <winsafer.h>
#include <winsaferp.h>
#include <wintrust.h>
#include <crypto\wintrustp.h>
#include "saferp.h"
#define EXPAND_REGPATH
//#define VERBOSE_IDENTIFICATIONS
#ifdef VERBOSE_IDENTIFICATIONS
#define OUTPUTDEBUGSTRING(v) OutputDebugStringW(v)
#else
#define OUTPUTDEBUGSTRING(v)
#endif
const static GUID guidTrustedCert = SAFER_GUID_RESULT_TRUSTED_CERT;
const static GUID guidDefaultRule = SAFER_GUID_RESULT_DEFAULT_LEVEL;
NTSTATUS NTAPI
__CodeAuthzpEnsureMapped(
IN OUT PLOCALIDENTITYCONTEXT pIdentContext
)
/*++
Routine Description:
Evaluates the supplied identification context structure and
attempts to gain access to a mapped memory region of the entity
being identified. It does the following steps:
1) if the identification context already has a non-NULL memory
pointer then returns successfully.
2) if the identification context has a non-NULL file handle
then that handle is memory mapped into memory and
returns successfully.
3) if the identification context has a non-NULL image filename
then that filename is opened for read access and memory
mapped into memory.
Otherwise the function call is not successful.
The caller must be sure to call CodeAuthzpEnsureUnmapped later.
Arguments:
pIdentContext = pointer to the identification context structure.
After this function call succeeds, the caller can assume
that pIdentContext->pImageMemory and pIdentContext->ImageSize
are valid and can be used.
Return Value:
Returns STATUS_SUCCESS if a memory-mapped image pointer and size
are now available, otherwise a failure occurred trying to map them.
--*/
{
HANDLE hMapping;
ASSERT(ARGUMENT_PRESENT(pIdentContext) &&
pIdentContext->CodeProps != NULL);
if (pIdentContext->pImageMemory == NULL ||
pIdentContext->ImageSize.QuadPart == 0)
{
//
// If a memory pointer and imagesize were supplied to us
// in the CodeProperties, then just use them directly.
//
if (pIdentContext->CodeProps->ImageSize.QuadPart != 0 &&
pIdentContext->CodeProps->pByteBlock != NULL)
{
pIdentContext->pImageMemory =
pIdentContext->CodeProps->pByteBlock;
pIdentContext->ImageSize.QuadPart =
pIdentContext->CodeProps->ImageSize.QuadPart;
pIdentContext->bImageMemoryNeedUnmap = FALSE;
return STATUS_SUCCESS;
}
//
// Ensure that we have an open file handle, by using the
// handle supplied to us in the CodeProperties if possible,
// otherwise by opening the supplied ImagePath.
//
if (pIdentContext->hFileHandle == NULL) {
// no file handle supplied.
return STATUS_INVALID_PARAMETER; // failed.
}
//
// Get the size of the file. We assume that if we had to
// open the file ourself that the ImageSize cannot be used.
//
if (!GetFileSizeEx(pIdentContext->hFileHandle,
&pIdentContext->ImageSize)) {
return STATUS_ACCESS_DENIED; // failure
}
if (pIdentContext->ImageSize.HighPart != 0) {
//BLACKCOMB TODO: maybe later handle very large files.
return STATUS_NO_MEMORY; // failure--too large.
}
if (pIdentContext->ImageSize.QuadPart == 0) {
return STATUS_INVALID_PARAMETER; // failure--zero file size.
}
//
// Now that we have an open file handle, open it up
// as a memory-mapped file mapping.
//
hMapping = CreateFileMapping(
pIdentContext->hFileHandle,
NULL,
PAGE_READONLY,
(DWORD) 0, // highword zero
(DWORD) pIdentContext->ImageSize.LowPart,
NULL);
if (hMapping == NULL || hMapping == INVALID_HANDLE_VALUE) {
return STATUS_ACCESS_DENIED;
}
//
// View map the file into memory.
//
pIdentContext->pImageMemory =
MapViewOfFile(hMapping, FILE_MAP_READ, 0, 0,
pIdentContext->ImageSize.LowPart);
CloseHandle(hMapping);
if (pIdentContext->pImageMemory == NULL) {
return STATUS_ACCESS_DENIED;
}
pIdentContext->bImageMemoryNeedUnmap = TRUE;
}
return STATUS_SUCCESS;
}
NTSTATUS NTAPI
__CodeAuthzpEnsureUnmapped(
IN OUT PLOCALIDENTITYCONTEXT pIdentContext
)
/*++
Routine Description:
Reverses the effects of __CodeAuthzEnsureMapped and closes and
frees any handles that were opened to the specified file.
Arguments:
pIdentContext - pointer to the context structure.
Return Value:
Returns STATUS_SUCCESS if no errors occurred.
--*/
{
ASSERT(pIdentContext != NULL);
if (pIdentContext->bImageMemoryNeedUnmap &&
pIdentContext->pImageMemory != NULL)
{
UnmapViewOfFile((LPCVOID) pIdentContext->pImageMemory);
pIdentContext->pImageMemory = NULL;
pIdentContext->bImageMemoryNeedUnmap = FALSE;
}
return STATUS_SUCCESS;
}
NTSTATUS NTAPI
CodeAuthzpComputeImageHash(
IN PVOID pImageMemory,
IN DWORD dwImageSize,
OUT PBYTE pComputedHash OPTIONAL,
IN OUT PDWORD pdwHashSize OPTIONAL,
OUT ALG_ID *pHashAlgorithm OPTIONAL
)
/*++
Routine Description:
Computes an MD5 image hash of a specified region of memory.
Note, MD5 hashes are always 16 bytes in length.
Arguments:
pImageMemory - Pointer to a memory buffer to compute the hash of.
dwImageSize - Total size of the pImageMemory buffer in bytes.
pComputedHash - Pointer that receives the computed hash.
pdwHashSize - Pointer to a DWORD value. On input, this DWORD should
specify the maximum size of the pComputedHash buffer.
On successful execution of this function, the length of the
resulting hash is written to this pointer.
pHashAlgorithm - pointer to a variable that will receive the hash
algorithm that was used to compute the hash. This will
always be the constant CALG_MD5.
Return Value:
Returns STATUS_SUCCESS on successful execution.
--*/
{
MD5_CTX md5ctx;
//
// Check the validity of the arguments supplied to us.
//
if (!ARGUMENT_PRESENT(pImageMemory) ||
dwImageSize == 0) {
return STATUS_INVALID_PARAMETER;
}
if (!ARGUMENT_PRESENT(pComputedHash) ||
!ARGUMENT_PRESENT(pdwHashSize) ||
*pdwHashSize < 16) {
return STATUS_BUFFER_TOO_SMALL;
}
//
// Compute the MD5 hash of it.
// (this could also be done with CryptCreateHash+CryptHashData)
//
MD5Init(&md5ctx);
MD5Update(&md5ctx, (LPBYTE) pImageMemory, dwImageSize);
MD5Final(&md5ctx);
//
// Copy the hash to the user's buffer.
//
RtlCopyMemory(pComputedHash, &md5ctx.digest[0], 16);
*pdwHashSize = 16;
if (ARGUMENT_PRESENT(pHashAlgorithm)) {
*pHashAlgorithm = CALG_MD5;
}
return STATUS_SUCCESS;
}
NTSTATUS NTAPI
__CodeAuthzpCheckIdentityPathRules(
IN OUT PLOCALIDENTITYCONTEXT pIdentStruct,
OUT PAUTHZLEVELTABLERECORD *pFoundLevel,
OUT PBOOL pbExactMatch,
OUT PAUTHZIDENTSTABLERECORD *pFoundIdentity
)
/*++
Routine Description:
Evaluates a wildcard pattern against a specified pathname and
indicates if they match.
Arguments:
pIdentStruct -
pFoundLevel - receives a pointer to the authorization Level record
indicated by the best matching rule.
pbExactMatch - receives a boolean value indicating if the match
was against an exact fully qualified path rule.
pFoundIdentity - receives a pointer to the identifier entry rule
that best matched.
Return Value:
Returns STATUS_SUCCESS if a WinSafer Level has been found,
or STATUS_NOT_FOUND if not. Otherwise an error code.
--*/
{
NTSTATUS Status;
PVOID RestartKey;
UNICODE_STRING UnicodePath;
WCHAR ExpandedPath[MAX_PATH];
WCHAR szLongPath[MAX_PATH];
PAUTHZIDENTSTABLERECORD pAuthzIdentRecord, pBestIdentRecord;
PAUTHZLEVELTABLERECORD pAuthzLevelRecord;
LPWSTR lpKeyname = NULL;
LONG lBestLevelDepth;
DWORD dwBestLevelId;
BOOLEAN bFirstPass;
LONG bPathIdentIsBadType = -1; // represents uninit'd state
//
// Verify that our input arguments all make sense.
//
if (!ARGUMENT_PRESENT(pIdentStruct) ||
pIdentStruct->CodeProps == NULL ||
!ARGUMENT_PRESENT(pFoundLevel) ||
!ARGUMENT_PRESENT(pbExactMatch))
{
Status = STATUS_INVALID_PARAMETER;
goto ExitHandler;
}
if ((pIdentStruct->dwCheckFlags & SAFER_CRITERIA_IMAGEPATH) == 0 ||
pIdentStruct->UnicodeFullyQualfiedLongFileName.Buffer == NULL ||
RtlIsGenericTableEmpty(&g_CodeIdentitiesTable))
{
// We're not supposed to evaluate image paths.
Status = STATUS_NOT_FOUND;
goto ExitHandler;
}
ASSERT(g_TableCritSec.OwningThread == UlongToHandle(GetCurrentThreadId()));
//
// Enumerate through all path subkey GUIDs.
//
bFirstPass = TRUE;
RestartKey = NULL;
for (pAuthzIdentRecord = (PAUTHZIDENTSTABLERECORD)
RtlEnumerateGenericTableWithoutSplaying(
&g_CodeIdentitiesTable, &RestartKey);
pAuthzIdentRecord != NULL;
pAuthzIdentRecord = (PAUTHZIDENTSTABLERECORD)
RtlEnumerateGenericTableWithoutSplaying(
&g_CodeIdentitiesTable, &RestartKey)
)
{
if (pAuthzIdentRecord->dwIdentityType ==
SaferIdentityTypeImageName)
{//begin reg key lookup block
LONG lMatchDepth;
//
// Explicitly expand environmental variables.
//
if (pAuthzIdentRecord->ImageNameInfo.bExpandVars) {
#ifdef EXPAND_REGPATH
//This code attempts to expand "path" entries that are really reg keys.
//For example, some paths are install dependent. These paths are commonly written into
//the registry. You can specify a regkey that is a path.
//For example see the following regkeys:
//HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders
//HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders
//HKEY_CURRENT_USER\Software\Microsoft\Office\9.0\Outlook\Security\OutlookSecureTempFolder
{
LPWSTR lpzRegKey = pAuthzIdentRecord->ImageNameInfo.ImagePath.Buffer;
HKEY hKey=NULL, hKeyHive=NULL;
BOOL bIsCurrentUser = FALSE;
//leading percent does two things:
//1. The rules created will be the Expandable String Type (REG_EXPAND_SZ)
//2. Reduces the chance of a real path name conflict.
LPCWSTR LP_CU_HIVE = L"%HKEY_CURRENT_USER";
LPCWSTR LP_LM_HIVE = L"%HKEY_LOCAL_MACHINE";
BYTE buffer[MAX_PATH *2 + 80];
LPWSTR lpValue=NULL;
DWORD dwBufferSize = sizeof(buffer);
LPWSTR lpHivename;
LPWSTR lpLastPercentSign;
LONG retval;
BOOL bIsRegKey=TRUE;
DWORD dwKeyLength;
//We expect a string like the following:
//%HKEY_CURRENT_USER\Software\Microsoft\Office\9.0\Outlook\Security\OutlookSecureTempFolder%
//We need to break it into three parts for the registry query:
//1. The hive: HKEY_CURRENT_USER
//2. The key name: Software\Microsoft\Office\9.0\Outlook\Security
//3. The value name: OutlookSecureTempFolder
lpKeyname=NULL;
lpValue=NULL;
lpHivename=NULL;
lpLastPercentSign=NULL;
memset(buffer, 0, dwBufferSize);
lpHivename = wcsstr(lpzRegKey, LP_CU_HIVE);
OUTPUTDEBUGSTRING(L"\n");
OUTPUTDEBUGSTRING(L"$");
OUTPUTDEBUGSTRING(lpzRegKey);
OUTPUTDEBUGSTRING(L"\n");
lpLastPercentSign = wcsrchr(lpzRegKey, '%');
//if (lpLastPercentSign != &lpzRegKey[wcslen(lpzRegKey) - 1]) { //needs to end in a '%' as well
//
// we allow %key+valuename%OLK* type paths now
// but there still has to be a matching %
//
if (!lpLastPercentSign) {
bIsRegKey = FALSE;
}
if (bIsRegKey) {
if (lpHivename != NULL) {
hKeyHive = HKEY_CURRENT_USER;
bIsCurrentUser = TRUE;
dwKeyLength = (wcslen(&lpzRegKey[wcslen(LP_CU_HIVE)+1]) +1) * sizeof (WCHAR);
lpKeyname = HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY, dwKeyLength);
if ( lpKeyname == NULL ) {
Status = STATUS_NO_MEMORY;
goto ForLoopCleanup;
}
wcscpy(lpKeyname, &lpzRegKey[wcslen(LP_CU_HIVE)+1] );
OUTPUTDEBUGSTRING(L"HKEY_CURRENT_USER");
OUTPUTDEBUGSTRING(L"\n");
} else {
lpHivename = wcsstr(lpzRegKey, LP_LM_HIVE);
if (lpHivename != NULL) {
hKeyHive = HKEY_LOCAL_MACHINE;
dwKeyLength = (wcslen(&lpzRegKey[wcslen(LP_LM_HIVE)+1]) +1) * sizeof (WCHAR);
lpKeyname = HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY, dwKeyLength);
if ( lpKeyname == NULL ) {
Status = STATUS_NO_MEMORY;
goto ForLoopCleanup;
}
wcscpy(lpKeyname, &lpzRegKey[wcslen(LP_LM_HIVE)+1] );
OUTPUTDEBUGSTRING(L"HKEY_LOCAL_MACHINE");
OUTPUTDEBUGSTRING(L"\n");
} else {
//The string is either a path or bogus data
bIsRegKey = FALSE;
}
}
}
if (bIsRegKey) {
lpValue = wcsrchr(lpKeyname, '\\');
if (lpValue==NULL) {
Status = STATUS_NOT_FOUND;
goto ForLoopCleanup;
}
//Take the regkey and value and stick a null terminator in between them.
*lpValue = '\0';
lpValue++;
//lpValue[wcslen(lpValue)-1] = '\0';
//lpLastPercentSign[0] = L'\0'; //replace the final '%' char with a null terminator
lpLastPercentSign = wcsrchr(lpValue, '%');
if (lpLastPercentSign == NULL) {
Status = STATUS_NOT_FOUND;
goto ForLoopCleanup;
}
*lpLastPercentSign = '\0';
//
// Bug# 416461 - causes handle leak so use a different API
// for the user hive
//
if ( bIsCurrentUser ) {
if (retval = RegOpenCurrentUser( KEY_READ, &hKeyHive ) ) {
if ( retval == ERROR_FILE_NOT_FOUND ||
retval == ERROR_NOT_FOUND ||
retval == ERROR_PATH_NOT_FOUND) {
if (lpKeyname) {
HeapFree(GetProcessHeap(), 0,lpKeyname);
lpKeyname = NULL;
}
continue;
}
Status = STATUS_NOT_FOUND;
goto ForLoopCleanup;
}
}
retval = RegOpenKeyEx(hKeyHive,
lpKeyname,
0,
KEY_READ,
&hKey);
if ( bIsCurrentUser ) {
RegCloseKey(hKeyHive);
}
if (retval)
{
if ( retval == ERROR_FILE_NOT_FOUND ||
retval == ERROR_NOT_FOUND ||
retval == ERROR_PATH_NOT_FOUND) {
if (lpKeyname) {
HeapFree(GetProcessHeap(), 0,lpKeyname);
lpKeyname = NULL;
}
continue;
}
Status = STATUS_NOT_FOUND;
goto ForLoopCleanup;
} else {
OUTPUTDEBUGSTRING(lpKeyname);
OUTPUTDEBUGSTRING(L"\n");
OUTPUTDEBUGSTRING(lpValue);
OUTPUTDEBUGSTRING(L"\n");
if (retval = RegQueryValueEx(hKey,
lpValue,
NULL,
NULL,
buffer,
&dwBufferSize))
{
RegCloseKey(hKey);
Status = STATUS_NOT_FOUND;
goto ForLoopCleanup;
} else {
#ifdef VERBOSE_IDENTIFICATIONS
UNICODE_STRING UnicodeDebug;
WCHAR DebugBuffer[MAX_PATH*2 + 80];
#endif
UNICODE_STRING NewPath;
PUNICODE_STRING pPathFromRule;
//
// if it exists, concatenate the filename after
// i.e. the OLK in %HKEY\somekey\somevalue%OLK
//
if (lpLastPercentSign[1] != L'\0') {
//
// there is some stuff after %HKEY\somekey\somevalue%
//
if (sizeof(buffer) >
((wcslen((WCHAR*)buffer) + wcslen(lpLastPercentSign+1))* sizeof(WCHAR))) {
WCHAR *pwcBuffer = (WCHAR *)buffer;
if (pwcBuffer[0] != L'\0' &&
pwcBuffer[wcslen(pwcBuffer)-1] != L'\\') {
wcscat((WCHAR*)buffer, L"\\");
}
wcscat((WCHAR*)buffer, lpLastPercentSign+1);
}
}
pPathFromRule=&(pAuthzIdentRecord->ImageNameInfo.ImagePath);
NewPath.Length = (USHORT)wcslen((WCHAR*)buffer) * sizeof(WCHAR);
NewPath.MaximumLength = (USHORT)wcslen((WCHAR*)buffer) * sizeof(WCHAR);
NewPath.Buffer = (PWCHAR)buffer;
#ifdef VERBOSE_IDENTIFICATIONS
RtlInitEmptyUnicodeString(&UnicodeDebug, DebugBuffer, sizeof(DebugBuffer));
swprintf(UnicodeDebug.Buffer, L"pPathFromRule(L,ML,Buffer)=(%d,%d,%s)\n",
pPathFromRule->Length,
pPathFromRule->MaximumLength,
pPathFromRule->Buffer);
OUTPUTDEBUGSTRING(UnicodeDebug.Buffer);
memset(DebugBuffer, '0', sizeof(DebugBuffer));
swprintf(UnicodeDebug.Buffer, L"NewPath(L,ML,Buffer)=(%d,%d,%s)\n",
NewPath.Length,
NewPath.MaximumLength,
NewPath.Buffer);
OUTPUTDEBUGSTRING(UnicodeDebug.Buffer);
#endif
//The new path may be bigger than the current UNICODE_STRING can store. Reallocate if necessary.
if (pPathFromRule->MaximumLength >=
NewPath.Length + sizeof(UNICODE_NULL)) {
RtlCopyUnicodeString(
pPathFromRule,
&NewPath);
} else {
UNICODE_STRING UnicodeExpandedCopy;
Status = RtlDuplicateUnicodeString(
RTL_DUPLICATE_UNICODE_STRING_NULL_TERMINATE |
RTL_DUPLICATE_UNICODE_STRING_ALLOCATE_NULL_STRING,
&NewPath,
&UnicodeExpandedCopy);
if (NT_SUCCESS(Status)) {
RtlFreeUnicodeString(&pAuthzIdentRecord->ImageNameInfo.ImagePath);
pAuthzIdentRecord->ImageNameInfo.ImagePath = UnicodeExpandedCopy;
}
}
#ifdef VERBOSE_IDENTIFICATIONS
memset(DebugBuffer, '0', sizeof(DebugBuffer));
swprintf(UnicodeDebug.Buffer, L"pPathFromRule after copy(L,ML,Buffer)=(%d,%d,%s)\n",
pPathFromRule->Length,
pPathFromRule->MaximumLength,
pPathFromRule->Buffer);
OUTPUTDEBUGSTRING(UnicodeDebug.Buffer);
#endif
}
}
RegCloseKey(hKey);
}
if (lpKeyname) {
HeapFree(GetProcessHeap(), 0,lpKeyname);
lpKeyname = NULL;
}
} //end reg key lookup block
#endif
// Attempt to expand now.
RtlInitEmptyUnicodeString(
&UnicodePath,
&ExpandedPath[0],
sizeof(ExpandedPath) );
Status = RtlExpandEnvironmentStrings_U(
NULL, // environment
&pAuthzIdentRecord->ImageNameInfo.ImagePath, // unexpanded path
&UnicodePath, // resulting path
NULL); // needed buffer size.
if (!NT_SUCCESS(Status)) {
// Failed to expand environment strings.
continue;
}
// Perf optimization: If the expansion was successful,
// update the table to keep the expanded version, eliminating
// the need to expand the string for any future comparisons.
if (pAuthzIdentRecord->ImageNameInfo.ImagePath.MaximumLength >=
UnicodePath.Length + sizeof(UNICODE_NULL)) {
RtlCopyUnicodeString(
&pAuthzIdentRecord->ImageNameInfo.ImagePath,
&UnicodePath);
pAuthzIdentRecord->ImageNameInfo.bExpandVars = FALSE;
} else {
UNICODE_STRING UnicodeExpandedCopy;
Status = RtlDuplicateUnicodeString(
RTL_DUPLICATE_UNICODE_STRING_NULL_TERMINATE |
RTL_DUPLICATE_UNICODE_STRING_ALLOCATE_NULL_STRING,
&UnicodePath,
&UnicodeExpandedCopy);
if (NT_SUCCESS(Status)) {
RtlFreeUnicodeString(
&pAuthzIdentRecord->ImageNameInfo.ImagePath);
pAuthzIdentRecord->ImageNameInfo.ImagePath =
UnicodeExpandedCopy;
pAuthzIdentRecord->ImageNameInfo.bExpandVars = FALSE;
}
}
} else {
UnicodePath.Buffer = pAuthzIdentRecord->ImageNameInfo.ImagePath.Buffer;
UnicodePath.Length = pAuthzIdentRecord->ImageNameInfo.ImagePath.Length;
UnicodePath.MaximumLength = pAuthzIdentRecord->ImageNameInfo.ImagePath.MaximumLength;
}
//
// Attempt short -> long filename expansion (if there is a need)
//
szLongPath[0] = L'\0';
//
// unicode buffer is guaranteed to be < MAX_PATH
//
wcsncpy(szLongPath,
pAuthzIdentRecord->ImageNameInfo.ImagePath.Buffer,
pAuthzIdentRecord->ImageNameInfo.ImagePath.Length/sizeof(WCHAR));
szLongPath[pAuthzIdentRecord->ImageNameInfo.ImagePath.Length/sizeof(WCHAR)] = L'\0';
if ( wcschr(szLongPath, L'~') ) {
if (!GetLongPathNameW(szLongPath,
szLongPath,
sizeof(szLongPath) / sizeof(WCHAR))) {
Status = STATUS_VARIABLE_NOT_FOUND;
continue;
}
RtlInitUnicodeString(&UnicodePath, szLongPath);
if (pAuthzIdentRecord->ImageNameInfo.ImagePath.MaximumLength >=
UnicodePath.Length + sizeof(UNICODE_NULL)) {
RtlCopyUnicodeString(
&pAuthzIdentRecord->ImageNameInfo.ImagePath,
&UnicodePath);
} else {
UNICODE_STRING UnicodeExpandedCopy;
Status = RtlDuplicateUnicodeString(
RTL_DUPLICATE_UNICODE_STRING_NULL_TERMINATE |
RTL_DUPLICATE_UNICODE_STRING_ALLOCATE_NULL_STRING,
&UnicodePath,
&UnicodeExpandedCopy);
if (NT_SUCCESS(Status)) {
RtlFreeUnicodeString(
&pAuthzIdentRecord->ImageNameInfo.ImagePath);
pAuthzIdentRecord->ImageNameInfo.ImagePath =
UnicodeExpandedCopy;
}
}
}
//
// Compute the quality of which the wildcard path identity
// matches the ImagePath property we were asked to evaluate.
//
ASSERT(UnicodePath.Buffer[UnicodePath.Length / sizeof(WCHAR)] == UNICODE_NULL);
ASSERT(pIdentStruct->UnicodeFullyQualfiedLongFileName.Buffer[
pIdentStruct->UnicodeFullyQualfiedLongFileName.Length / sizeof(WCHAR)] == UNICODE_NULL);
lMatchDepth = CodeAuthzpCompareImagePath(UnicodePath.Buffer,
pIdentStruct->UnicodeFullyQualfiedLongFileName.Buffer);
if (!lMatchDepth) continue;
//
// If this path identity is configured to only apply to
// file extensions on the "bad list" then check to see if
// the ImagePath specifies one of the bad extensions.
//
#ifdef AUTHZPOL_SAFERFLAGS_ONLY_EXES
if (lMatchDepth > 0 &&
(pAuthzIdentRecord->ImageNameInfo.dwSaferFlags &
AUTHZPOL_SAFERFLAGS_ONLY_EXES) != 0)
{
if (bPathIdentIsBadType == -1) {
BOOLEAN bResult;
Status = CodeAuthzIsExecutableFileType(
&pIdentStruct->UnicodeFullyQualfiedLongFileName, FALSE,
&bResult );
if (!NT_SUCCESS(Status) || !bResult) {
bPathIdentIsBadType = FALSE;
} else {
bPathIdentIsBadType = TRUE;
}
}
if (!bPathIdentIsBadType) {
// This identity matches against only the "bad"
// extensions, so pretend that this didn't match.
continue;
}
}
#endif
//
// Emit some diagnostic debugging code to show the result
// of all of the path evaluations and their match depths.
//
#ifdef VERBOSE_IDENTIFICATIONS
{
UNICODE_STRING UnicodeDebug;
WCHAR DebugBuffer[MAX_PATH*2 + 80];
// sprintf is for wimps.
RtlInitEmptyUnicodeString(&UnicodeDebug, DebugBuffer, sizeof(DebugBuffer));
RtlAppendUnicodeToString(&UnicodeDebug, L"Safer pattern ");
RtlAppendUnicodeStringToString(&UnicodeDebug, &UnicodePath);
RtlAppendUnicodeToString(&UnicodeDebug, L" matched ");
RtlAppendUnicodeStringToString(&UnicodeDebug, &(pIdentStruct->UnicodeFullyQualfiedLongFileName));
RtlAppendUnicodeToString(&UnicodeDebug, L" with value ");
UnicodeDebug.Buffer += UnicodeDebug.Length / sizeof(WCHAR);
UnicodeDebug.MaximumLength -= UnicodeDebug.Length;
RtlIntegerToUnicodeString(lMatchDepth, 10, &UnicodeDebug);
RtlAppendUnicodeToString(&UnicodeDebug, L"\n");
OUTPUTDEBUGSTRING(DebugBuffer);
}
#endif
//
// Evaluate if this path identity matches better than whatever
// best path identity that we previously had, and keep it if so.
//
if (lMatchDepth < 0) // an exact fully-qualified path!
{
if (bFirstPass ||
lBestLevelDepth >= 0 ||
pAuthzIdentRecord->dwLevelId < dwBestLevelId)
{
pBestIdentRecord = pAuthzIdentRecord;
dwBestLevelId = pAuthzIdentRecord->dwLevelId;
lBestLevelDepth = lMatchDepth;
bFirstPass = FALSE;
}
}
else // an inexact leading prefix path match.
{
ASSERT(lMatchDepth > 0);
if (bFirstPass ||
(lBestLevelDepth >= 0 &&
(lMatchDepth > lBestLevelDepth ||
(lMatchDepth == lBestLevelDepth &&
pAuthzIdentRecord->dwLevelId < dwBestLevelId)
)
)
)
{
pBestIdentRecord = pAuthzIdentRecord;
dwBestLevelId = pAuthzIdentRecord->dwLevelId;
lBestLevelDepth = lMatchDepth;
bFirstPass = FALSE;
}
}
ForLoopCleanup:
if (lpKeyname)
{
HeapFree(GetProcessHeap(), 0,lpKeyname);
lpKeyname = NULL;
}
}
}
//
// If we have identified a matching WinSafer Level then
// look up the Level record for it and return success.
//
if (bFirstPass) {
Status = STATUS_NOT_FOUND;
goto ExitHandler;
}
pAuthzLevelRecord = CodeAuthzLevelObjpLookupByLevelId(
&g_CodeLevelObjTable, dwBestLevelId);
if (!pAuthzLevelRecord) {
Status = STATUS_NOT_FOUND;
goto ExitHandler;
}
*pFoundLevel = pAuthzLevelRecord;
*pbExactMatch = (lBestLevelDepth < 0 ? TRUE : FALSE);
*pFoundIdentity = pBestIdentRecord;
Status = STATUS_SUCCESS;
ExitHandler:
if (lpKeyname)
HeapFree(GetProcessHeap(), 0,lpKeyname);
return Status;
}
NTSTATUS NTAPI
__CodeAuthzpCheckIdentityCertificateRules(
IN OUT PLOCALIDENTITYCONTEXT pIdentStruct,
OUT DWORD *dwExtendedError,
OUT PAUTHZLEVELTABLERECORD *pFoundLevel,
IN DWORD dwUIChoice
)
/*++
Routine Description:
Calls WinVerifyTrust to determine the trust level of the code
signer that has signed a piece of code.
Arguments:
pIdentStruct - context state structure.
dwExtendedError - To return extended error returned by WinVerifyTrust.
pFoundLevel - receives a pointer to the authorization Level record
indicated by the best matching rule.
dwUIChoice - optionally specifies the amount of UI that WinVerifyTrust
is allowed to display. If this argument is 0, then it is treated
as if WTD_UI_ALL had been supplied.
Return Value:
Returns STATUS_SUCCESS if a WinSafer Level has been found,
or STATUS_RETRY if the publisher was unknown and UIflags blocked prompting,
or STATUS_NOT_FOUND if not. Otherwise an error code.
--*/
{
NTSTATUS Status;
PAUTHZLEVELTABLERECORD pLevelRecord;
GUID wvtFileActionID = WINTRUST_ACTION_GENERIC_VERIFY_V2;
WINTRUST_FILE_INFO wvtFileInfo;
WINTRUST_DATA wvtData;
LONG lStatus;
DWORD dwLastError;
DWORD LocalHandleSequenceNumber;
//
// Verify that our input arguments all make sense.
//
if (!ARGUMENT_PRESENT(pIdentStruct) ||
pIdentStruct->CodeProps == NULL ) {
Status = STATUS_INVALID_PARAMETER;
goto ExitHandler;
}
if ((pIdentStruct->dwCheckFlags & SAFER_CRITERIA_AUTHENTICODE) == 0 ||
!ARGUMENT_PRESENT(pIdentStruct->UnicodeFullyQualfiedLongFileName.Buffer)) {
// We're not supposed to evaluate certificates, or the
// filename was not supplied (WinVerifyTrust requires a
// filename, even if an opened handle to is also supplied).
Status = STATUS_NOT_FOUND;
goto ExitHandler;
}
if ( !ARGUMENT_PRESENT(pFoundLevel) ) {
Status = STATUS_ACCESS_VIOLATION;
goto ExitHandler;
}
ASSERT(g_TableCritSec.OwningThread == UlongToHandle(GetCurrentThreadId()));
//
// Prepare the input data structure that WinVerifyTrust expects.
//
RtlZeroMemory(&wvtData, sizeof(WINTRUST_DATA));
wvtData.cbStruct = sizeof(WINTRUST_DATA);
if ((wvtData.dwUIChoice = dwUIChoice) == 0) {
// If the UI choice element was left zero, then assume all UI.
wvtData.dwUIChoice = WTD_UI_ALL;
}
wvtData.dwProvFlags = WTD_SAFER_FLAG; // our magic flag.
wvtData.dwUnionChoice = WTD_CHOICE_FILE;
wvtData.pFile = &wvtFileInfo;
//
// Prepare the input file data structure used by WinVerifyTrust.
//
RtlZeroMemory(&wvtFileInfo, sizeof(WINTRUST_FILE_INFO));
wvtFileInfo.cbStruct = sizeof(WINTRUST_FILE_INFO);
wvtFileInfo.hFile = pIdentStruct->hFileHandle;
wvtFileInfo.pcwszFilePath = pIdentStruct->UnicodeFullyQualfiedLongFileName.Buffer;
//
// Save the global state number.
//
LocalHandleSequenceNumber = g_dwLevelHandleSequence;
//
// Leave the critical section to prevent deadlock with the LoaderLock.
//
RtlLeaveCriticalSection(&g_TableCritSec);
//
// Actually call WinVerifyTrust and save off the return code
// and last error code.
//
lStatus = WinVerifyTrust(
pIdentStruct->CodeProps->hWndParent, // hwnd
&wvtFileActionID,
&wvtData
);
dwLastError = GetLastError();
*dwExtendedError = dwLastError;
//
// Reacquire the lock and check global state.
//
RtlEnterCriticalSection(&g_TableCritSec);
//
// Check the global state and make sure that the tables were not reloaded
// when we were not looking.
//
if (LocalHandleSequenceNumber != g_dwLevelHandleSequence) {
ASSERT(FALSE);
Status = STATUS_INTERNAL_ERROR;
goto ExitHandler;
}
//
// Process the WinVerifyTrust errors per PhilH
//
pLevelRecord = NULL;
if (S_OK == lStatus && TRUST_E_SUBJECT_NOT_TRUSTED != dwLastError) {
//
// The file is signed. The publisher or hash is explicitly trusted
//
pLevelRecord = CodeAuthzLevelObjpLookupByLevelId(
&g_CodeLevelObjTable, SAFER_LEVELID_FULLYTRUSTED);
} else if (TRUST_E_EXPLICIT_DISTRUST == lStatus || TRUST_E_SUBJECT_NOT_TRUSTED == lStatus) {
//
// The publisher is revoked or explicitly untrusted. Alternatively, the hash is
// explicitly untrusted.
//
pLevelRecord = CodeAuthzLevelObjpLookupByLevelId(
&g_CodeLevelObjTable, SAFER_LEVELID_DISALLOWED);
} else {
//
// we won't be too conservative in any of the following cases.
// No explicit trust or untrust. Continue on to other SAFER checks.
//
// TRUST_E_NOSIGNATURE == lStatus
// The file isn't signed. Alternatively for TRUST_E_BAD_DIGEST == dwLastError,
// a signed file has been modified.
// CRYPT_E_SECURITY_SETTINGS == lStatus
// For authenticode downloads, the admin has disabled user UI and trust.
// any other combination of lStatus and dwLastError
// The file is signed. WVT has already called safer to check the hash rules.
Status = STATUS_NOT_FOUND;
goto ExitHandler;
}
if (!pLevelRecord) {
Status = STATUS_ACCESS_DENIED;
} else {
*pFoundLevel = pLevelRecord;
Status = STATUS_SUCCESS;
}
ExitHandler:
return Status;
}
BOOL WINAPI
SaferiSearchMatchingHashRules(
IN ALG_ID HashAlgorithm OPTIONAL,
IN PBYTE pHashBytes,
IN DWORD dwHashSize,
IN DWORD dwOriginalImageSize OPTIONAL,
OUT PDWORD pdwFoundLevel,
OUT PDWORD pdwSaferFlags
)
/*++
Routine Description:
This is a private function that is exported for WinVerifyTrust
to call to determine if a given hash has a WinSafer policy
associated with it.
Because this is a private function that is directly called by
outside code, there is extra work needed to enter the critical
section, reload the policy if needed, and set the value returned
by GetLastError.
Arguments:
HashAlgorithm - specifies the algorithm in which the hash
was computed (CALG_MD5, CALG_SHA, etc).
pHashBytes - pointer to a buffer containing the pre-computed
hash value of the file's contents.
dwHashSize - length indicating the size of the hash value that
is referenced by the pHashBytes argument. For example,
a 128-bit MD5 hash should have a dwHashSize length of 16.
dwOriginalImageSize - Specifies the size of the original file's
contents that are being hashed. This value is used as a
heuristic to minimize the number of comparisons that must
be done to identify a match. If this parameter is 0, then
this heuristic will not be used.
pdwFoundLevel - pointer that receives a DWORD indicating the
WinSafer LevelId that is found. This value is only written
when TRUE is returned.
pdwSaferFlags - pointer that receives a DWORD value containing flags
that control the supression of User-Interface dialogs.
This value is only written when TRUE is returned.
Return Value:
Returns TRUE if a WinSafer Level has been found, or FALSE if not.
If FALSE is returned, GetLastError() may be used to find out
specifics about why no match was found (possibly argument errors).
--*/
{
NTSTATUS Status;
PVOID RestartKey;
PAUTHZIDENTSTABLERECORD pAuthzIdentRecord;
DWORD dwBestLevelId;
DWORD dwBestSaferFlags;
BOOLEAN bFirstPass;
//
// Verify that our input arguments all make sense.
//
if (!ARGUMENT_PRESENT(pHashBytes) ||
dwHashSize < 1) {
Status = STATUS_INVALID_PARAMETER;
goto ExitHandler;
}
if (!ARGUMENT_PRESENT(pdwFoundLevel) ||
!ARGUMENT_PRESENT(pdwSaferFlags)) {
Status = STATUS_ACCESS_VIOLATION;
goto ExitHandler;
}
//
// Enter the critical section and reload the tables if needed.
// Notice that a potential reload is needed here because this
// function is externally called directly.
//
if (!g_bInitializedFirstTime) {
Status = STATUS_UNSUCCESSFUL;
goto ExitHandler;
}
RtlEnterCriticalSection(&g_TableCritSec);
if (g_bNeedCacheReload) {
Status = CodeAuthzpImmediateReloadCacheTables();
if (!NT_SUCCESS(Status)) {
goto ExitHandler2;
}
}
//
// Enumerate through all hash subkey GUIDs.
//
bFirstPass = TRUE;
RestartKey = NULL;
for (pAuthzIdentRecord = (PAUTHZIDENTSTABLERECORD)
RtlEnumerateGenericTableWithoutSplaying(
&g_CodeIdentitiesTable, &RestartKey);
pAuthzIdentRecord != NULL;
pAuthzIdentRecord = (PAUTHZIDENTSTABLERECORD)
RtlEnumerateGenericTableWithoutSplaying(
&g_CodeIdentitiesTable, &RestartKey)
)
{
if (pAuthzIdentRecord->dwIdentityType == SaferIdentityTypeImageHash)
{
//
// Ensure that the hash algorithm is the same type between
// what we were supplied and what we are matching against.
//
if (HashAlgorithm != 0 &&
pAuthzIdentRecord->ImageHashInfo.HashAlgorithm !=
HashAlgorithm) {
continue;
}
//
// If the actual filesize does not match the filesize stored
// with the hash identity then there is no need to perform
// any comparisons involving the hash.
//
if ( dwOriginalImageSize != 0 && dwOriginalImageSize !=
pAuthzIdentRecord->ImageHashInfo.ImageSize.QuadPart ) {
continue;
}
//
// If the hash doesn't match at all, then go onto the next one.
//
if ( dwHashSize != pAuthzIdentRecord->ImageHashInfo.HashSize ||
!RtlEqualMemory(
&pAuthzIdentRecord->ImageHashInfo.ImageHash[0],
&pHashBytes[0], dwHashSize))
{
continue;
}
//
// Evaluate if this identity matches better than whatever
// best path identity that we previously had, and keep it if so.
//
if ( bFirstPass ||
// we didn't have anything before.
pAuthzIdentRecord->dwLevelId < dwBestLevelId
// or specifies a less-privileged level.
)
{
dwBestLevelId = pAuthzIdentRecord->dwLevelId;
dwBestSaferFlags = pAuthzIdentRecord->ImageHashInfo.dwSaferFlags;
bFirstPass = FALSE;
}
}
}
//
// If we have identified a matching WinSafer Level then
// pass it back and return success.
//
if (bFirstPass) {
Status = STATUS_NOT_FOUND;
goto ExitHandler2;
}
*pdwFoundLevel = dwBestLevelId;
*pdwSaferFlags = dwBestSaferFlags;
Status = STATUS_SUCCESS;
ExitHandler2:
RtlLeaveCriticalSection(&g_TableCritSec);
ExitHandler:
if (NT_SUCCESS(Status)) {
return TRUE;
} else {
BaseSetLastNTError(Status);
return FALSE;
}
}
NTSTATUS NTAPI
__CodeAuthzpCheckIdentityHashRules(
IN OUT PLOCALIDENTITYCONTEXT pIdentStruct,
OUT PAUTHZLEVELTABLERECORD *pFoundLevel,
OUT PAUTHZIDENTSTABLERECORD *pFoundIdentity
)
/*++
Routine Description:
Assumes that the global table lock has already been acquired.
Arguments:
pIdentStruct -
pFoundLevel - receives a pointer to the authorization Level record
indicated by the best matching rule.
pFoundIdentity - receives a pointer to the identifier entry rule
that best matched.
Return Value:
Returns STATUS_SUCCESS if a WinSafer Level has been found,
or STATUS_NOT_FOUND if not. Otherwise an error code.
--*/
{
NTSTATUS Status;
PVOID RestartKey;
PAUTHZIDENTSTABLERECORD pAuthzIdentRecord, pBestIdentRecord;
PAUTHZLEVELTABLERECORD pAuthzLevelRecord;
DWORD dwBestLevelId;
BOOLEAN bFirstPass;
//
// Verify that our input arguments all make sense.
//
if (!ARGUMENT_PRESENT(pIdentStruct) ||
pIdentStruct->CodeProps == NULL ) {
Status = STATUS_INVALID_PARAMETER;
goto ExitHandler;
}
if ((pIdentStruct->dwCheckFlags & SAFER_CRITERIA_IMAGEHASH) == 0 ||
RtlIsGenericTableEmpty(&g_CodeIdentitiesTable)) {
// We're not supposed to evaluate hashes.
Status = STATUS_NOT_FOUND;
goto ExitHandler;
}
if ( !ARGUMENT_PRESENT(pFoundLevel) ||
!ARGUMENT_PRESENT(pFoundIdentity) ) {
Status = STATUS_ACCESS_VIOLATION;
goto ExitHandler;
}
ASSERT(g_TableCritSec.OwningThread == UlongToHandle(GetCurrentThreadId()));
//
// Enumerate through all hash subkey GUIDs.
//
bFirstPass = TRUE;
RestartKey = NULL;
for (pAuthzIdentRecord = (PAUTHZIDENTSTABLERECORD)
RtlEnumerateGenericTableWithoutSplaying(
&g_CodeIdentitiesTable, &RestartKey);
pAuthzIdentRecord != NULL;
pAuthzIdentRecord = (PAUTHZIDENTSTABLERECORD)
RtlEnumerateGenericTableWithoutSplaying(
&g_CodeIdentitiesTable, &RestartKey)
)
{
if (pAuthzIdentRecord->dwIdentityType == SaferIdentityTypeImageHash)
{
HRESULT hr = S_OK;
//
// If the user already supplied the pre-computed hash to us,
// but not the file size, then assume that we do not need
// to consider the file size when making a comparison.
//
if (pIdentStruct->bHaveHash) {
goto HashComputed;
}
//
// Get the signed hash for the file. If this fails then compute the
// MD5 hash.
//
if ((pIdentStruct->dwCheckFlags & SAFER_CRITERIA_NOSIGNEDHASH) == 0) {
//
// Leave the critical section to prevent deadlock with the LoaderLock.
//
RtlLeaveCriticalSection(&g_TableCritSec);
hr = WTHelperGetFileHash(
pIdentStruct->UnicodeFullyQualfiedLongFileName.Buffer,
0,
NULL,
&pIdentStruct->FinalHash[0],
&pIdentStruct->FinalHashSize,
&pIdentStruct->FinalHashAlgorithm);
//
// Reacquire the lock and check global state.
// Since this is the first thing that we do, it is ok to reacquire
// the lock safely.
//
RtlEnterCriticalSection(&g_TableCritSec);
if (SUCCEEDED(hr)) {
pIdentStruct->bHaveHash = TRUE;
goto HashComputed;
}
}
//
// If the actual filesize does not match the filesize stored
// with the hash identity then there is no need to perform
// any comparisons involving the hash.
//
if ( pIdentStruct->ImageSize.QuadPart == 0 )
{
// If we don't have the ImageSize yet, then try to
// open the file and memory map it to find the size.
Status = __CodeAuthzpEnsureMapped(pIdentStruct);
if (!NT_SUCCESS(Status)) {
// If we failed to compute the MD5 sum of this, then that is
// actually rather bad, but we'll proceed to evaluate any
// non-MD5 identity rules, since ignoring them could be worse.
pIdentStruct->dwCheckFlags &= ~SAFER_CRITERIA_IMAGEHASH;
goto ExitHandler;
}
ASSERTMSG("EnsureMapped failed but did not return error",
pIdentStruct->pImageMemory != NULL &&
pIdentStruct->ImageSize.QuadPart != 0);
}
if ( pAuthzIdentRecord->ImageHashInfo.ImageSize.QuadPart !=
pIdentStruct->ImageSize.QuadPart) {
continue;
}
//
// Dynamically compute the MD5 hash of the item if needed.
//
if (!pIdentStruct->bHaveHash)
{
// Otherwise hash was not supplied, so we must compute it now.
// Open the file and memory map it.
Status = __CodeAuthzpEnsureMapped(pIdentStruct);
if (!NT_SUCCESS(Status)) {
// If we failed to compute the MD5 sum of this, then
// that is actually rather bad, but we'll proceed to
// evaluate any non-MD5 identity rules, since ignoring
// them could be worse.
pIdentStruct->dwCheckFlags &= ~SAFER_CRITERIA_IMAGEHASH;
goto ExitHandler;
}
ASSERTMSG("EnsureMapped failed but did not return error",
pIdentStruct->pImageMemory != NULL &&
pIdentStruct->ImageSize.QuadPart != 0);
// We now have a MD5 hash to use.
pIdentStruct->FinalHashSize =
sizeof(pIdentStruct->FinalHash);
Status = CodeAuthzpComputeImageHash(
pIdentStruct->pImageMemory,
pIdentStruct->ImageSize.LowPart,
&pIdentStruct->FinalHash[0],
&pIdentStruct->FinalHashSize,
&pIdentStruct->FinalHashAlgorithm);
if (!NT_SUCCESS(Status)) {
goto ExitHandler;
}
pIdentStruct->bHaveHash = TRUE;
}
HashComputed:
//
// Ensure that the hash algorithm is the same type between
// what we were supplied and what we are matching against.
//
if ( pIdentStruct->FinalHashAlgorithm != 0 &&
pAuthzIdentRecord->ImageHashInfo.HashAlgorithm !=
pIdentStruct->FinalHashAlgorithm) {
continue;
}
//
// If the hash doesn't match at all, then go onto the next one.
//
if ( pIdentStruct->FinalHashSize !=
pAuthzIdentRecord->ImageHashInfo.HashSize ||
!RtlEqualMemory(
&pIdentStruct->FinalHash[0],
&pAuthzIdentRecord->ImageHashInfo.ImageHash[0],
pIdentStruct->FinalHashSize))
{
continue;
}
//
// Evaluate if this identity matches better than whatever
// best path identity that we previously had, and keep it if so.
//
if ( bFirstPass ||
// we didn't have anything before.
pAuthzIdentRecord->dwLevelId < dwBestLevelId
// same scope, but specifies a less-privileged level.
)
{
pBestIdentRecord = pAuthzIdentRecord;
dwBestLevelId = pAuthzIdentRecord->dwLevelId;
bFirstPass = FALSE;
}
}
}
//
// If we have identified a matching WinSafer Level then
// look up the Level record for it and return success.
//
if (bFirstPass) {
Status = STATUS_NOT_FOUND;
goto ExitHandler;
}
pAuthzLevelRecord = CodeAuthzLevelObjpLookupByLevelId(
&g_CodeLevelObjTable, dwBestLevelId);
if (!pAuthzLevelRecord) {
Status = STATUS_NOT_FOUND;
goto ExitHandler;
}
*pFoundLevel = pAuthzLevelRecord;
*pFoundIdentity = pBestIdentRecord;
Status = STATUS_SUCCESS;
ExitHandler:
return Status;
}
NTSTATUS NTAPI
__CodeAuthzpCheckIdentityUrlZoneRules(
IN OUT PLOCALIDENTITYCONTEXT pIdentStruct,
OUT PAUTHZLEVELTABLERECORD *pFoundLevel,
OUT PAUTHZIDENTSTABLERECORD *pFoundIdentity
)
/*++
Routine Description:
Arguments:
pIdentStruct -
pFoundLevel - receives a pointer to the authorization Level record
indicated by the best matching rule.
pFoundIdentity - receives a pointer to the identifier entry rule
that best matched.
Return Value:
Returns STATUS_SUCCESS if a WinSafer Level has been found,
or STATUS_NOT_FOUND if not. Otherwise an error code.
--*/
{
NTSTATUS Status;
PVOID RestartKey;
PAUTHZIDENTSTABLERECORD pAuthzIdentRecord, pBestIdentRecord;
PAUTHZLEVELTABLERECORD pAuthzLevelRecord;
DWORD dwBestLevelId;
BOOLEAN bFirstPass;
//
// Verify that our input arguments all make sense.
//
if (!ARGUMENT_PRESENT(pIdentStruct) ||
pIdentStruct->CodeProps == NULL) {
Status = STATUS_INVALID_PARAMETER;
goto ExitHandler;
}
if ((pIdentStruct->dwCheckFlags & SAFER_CRITERIA_URLZONE) == 0 ||
RtlIsGenericTableEmpty(&g_CodeIdentitiesTable)) {
// We're not supposed to evaluate zones.
Status = STATUS_NOT_FOUND;
goto ExitHandler;
}
if (!ARGUMENT_PRESENT(pFoundLevel) ||
!ARGUMENT_PRESENT(pFoundIdentity) ) {
Status = STATUS_ACCESS_VIOLATION;
goto ExitHandler;
}
ASSERT(g_TableCritSec.OwningThread == UlongToHandle(GetCurrentThreadId()));
//
// Enumerate through all UrlZone subkey GUIDs.
//
RestartKey = NULL;
bFirstPass = TRUE;
for (pAuthzIdentRecord = (PAUTHZIDENTSTABLERECORD)
RtlEnumerateGenericTableWithoutSplaying(
&g_CodeIdentitiesTable, &RestartKey);
pAuthzIdentRecord != NULL;
pAuthzIdentRecord = (PAUTHZIDENTSTABLERECORD)
RtlEnumerateGenericTableWithoutSplaying(
&g_CodeIdentitiesTable, &RestartKey)
)
{
if (pAuthzIdentRecord->dwIdentityType ==
SaferIdentityTypeUrlZone)
{
//
// Compare the identity with what was supplied to us.
//
if (pAuthzIdentRecord->ImageZone.UrlZoneId !=
pIdentStruct->CodeProps->UrlZoneId) {
// this zone does not match, so ignore it.
continue;
}
//
// Evaluate if this path identity matches better than whatever
// best path identity that we previously had, and keep it if so.
//
if (bFirstPass ||
// we didn't have anything better before.
pAuthzIdentRecord->dwLevelId < dwBestLevelId)
// this also matches, but specifies a less-privileged level.
{
pBestIdentRecord = pAuthzIdentRecord;
dwBestLevelId = pAuthzIdentRecord->dwLevelId;
bFirstPass = FALSE;
}
}
}
//
// If we have identified a matching WinSafer Level then
// look up the Level record for it and return success.
//
if (bFirstPass) {
Status = STATUS_NOT_FOUND;
goto ExitHandler;
}
pAuthzLevelRecord = CodeAuthzLevelObjpLookupByLevelId(
&g_CodeLevelObjTable, dwBestLevelId);
if (!pAuthzLevelRecord) {
Status = STATUS_NOT_FOUND;
goto ExitHandler;
}
*pFoundLevel = pAuthzLevelRecord;
*pFoundIdentity = pBestIdentRecord;
Status = STATUS_SUCCESS;
ExitHandler:
return Status;
}
NTSTATUS NTAPI
__CodeAuthzpIdentifyOneCodeAuthzLevel(
IN PSAFER_CODE_PROPERTIES pCodeProperties OPTIONAL,
OUT DWORD *dwExtendedError,
OUT PAUTHZLEVELTABLERECORD *pBestLevelRecord,
OUT GUID *pBestIdentGuid
)
/*++
Routine Description:
Performs the code identification process.
Assumes that the caller has already locked the global critsec.
Arguments:
pCodeProperties - pointer the single CODE_PROPERTIESW structure
that should be analyzed and evaluated. This parameter
may be specified as NULL to indicate that there are
no specific properties that should be evaluated and that
only the configured Default Level should be used.
dwExtendedError - In case of certificate rule match, return the extended
error from WinVerifyTrust.
pBestLevelRecord - returns the matching WinSafer Level record.
The value written to this parameter should only be
considered valid when STATUS_SUCCESS is also returned.
pBestIdentGuid - returns the matching Code Identity guid from
which the resulting WinSafer Level was determined.
The value written to this parameter should only be
considered valid when STATUS_SUCCESS is also returned.
This GUID may also be SAFER_GUID_RESULT_TRUSTED_CERT or
SAFER_GUID_RESULT_DEFAULT_LEVEL to indicate that the result
was from a publisher cert or a default rule match.
Note that a cert hash match will also
Return Value:
Returns STATUS_SUCCESS if a WinSafer Level has been found,
or STATUS_NOT_FOUND or another error code if not.
--*/
{
NTSTATUS Status;
LOCALIDENTITYCONTEXT identStruct = {0};
//
// Verify our input state and perform any explicit
// policy loading, if it hasn't been loaded yet.
//
if (!ARGUMENT_PRESENT(pBestLevelRecord) ||
!ARGUMENT_PRESENT(pBestIdentGuid)) {
Status = STATUS_ACCESS_VIOLATION;
goto ExitHandler;
}
ASSERT(g_TableCritSec.OwningThread == UlongToHandle(GetCurrentThreadId()));
//
// Star the identification process. If no code properties were
// supplied to us, then we can immediately skip to only
// considering the default WinSafer Level configurations.
//
if (ARGUMENT_PRESENT(pCodeProperties))
{
BOOLEAN bRetryCertRuleCheck = FALSE;
BOOLEAN bPathIsNtNamespace;
// Current best identity match.
PAUTHZLEVELTABLERECORD pAuthzLevelRecord = NULL;
PAUTHZIDENTSTABLERECORD pAuthzIdentRecord;
// Temporary evaluation identity match.
BOOL bExactPath;
PAUTHZLEVELTABLERECORD pTempLevelRecord;
PAUTHZIDENTSTABLERECORD pTempIdentRecord;
//
// Check that the CODE_PROPERTIES structure is the right size.
//
if (pCodeProperties->cbSize != sizeof(SAFER_CODE_PROPERTIES)) {
Status = STATUS_INFO_LENGTH_MISMATCH;
goto ExitHandler;
}
//
// Initialize the structure that we use to store our
// stateful information during policy evaluation.
// We don't copy everything from the CODE_PROPERTIES
// structure into the identStruct, since some of it
// is dynamically loaded/copied within "EnsureMapped".
//
RtlZeroMemory(&identStruct, sizeof(LOCALIDENTITYCONTEXT));
identStruct.CodeProps = pCodeProperties;
identStruct.dwCheckFlags = pCodeProperties->dwCheckFlags;
identStruct.ImageSize.QuadPart =
pCodeProperties->ImageSize.QuadPart;
if (identStruct.ImageSize.QuadPart != 0 &&
pCodeProperties->dwImageHashSize > 0 &&
pCodeProperties->dwImageHashSize <= SAFER_MAX_HASH_SIZE)
{
// The image hash and filesize were both supplied, therefore
// we have a valid hash and don't need to compute it ourself.
RtlCopyMemory(&identStruct.FinalHash[0],
&pCodeProperties->ImageHash[0],
pCodeProperties->dwImageHashSize);
identStruct.FinalHashSize = pCodeProperties->dwImageHashSize;
identStruct.bHaveHash = TRUE;
}
bPathIsNtNamespace = ((identStruct.dwCheckFlags &
SAFER_CRITERIA_IMAGEPATH_NT) != 0 ? TRUE : FALSE);
//
// Copy over the file handle into the context structure, if a
// handle was supplied, otherwise try to open the filepath.
//
if (pCodeProperties->hImageFileHandle != NULL &&
pCodeProperties->hImageFileHandle != INVALID_HANDLE_VALUE)
{
identStruct.hFileHandle = pCodeProperties->hImageFileHandle;
identStruct.bCloseFileHandle = FALSE;
}
else if (pCodeProperties->ImagePath != NULL)
{
HANDLE hFile;
if (bPathIsNtNamespace) {
UNICODE_STRING UnicodeFilename;
IO_STATUS_BLOCK IoStatusBlock;
OBJECT_ATTRIBUTES ObjectAttributes;
RtlInitUnicodeString(&UnicodeFilename, pCodeProperties->ImagePath);
InitializeObjectAttributes(
&ObjectAttributes, &UnicodeFilename,
OBJ_CASE_INSENSITIVE, NULL, NULL);
Status = NtOpenFile(&hFile, FILE_GENERIC_READ, &ObjectAttributes,
&IoStatusBlock, FILE_SHARE_READ, FILE_NON_DIRECTORY_FILE);
if (!NT_SUCCESS(Status)) {
hFile = NULL;
}
} else {
hFile = CreateFileW(
pCodeProperties->ImagePath,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
}
if (hFile != NULL && hFile != INVALID_HANDLE_VALUE) {
identStruct.hFileHandle = hFile;
identStruct.bCloseFileHandle = TRUE;
}
}
//
// Reconstruct the fully qualified pathname from the handle
// or from the supplied filename.
//
Status = CodeAuthzFullyQualifyFilename(
identStruct.hFileHandle,
bPathIsNtNamespace,
pCodeProperties->ImagePath,
&identStruct.UnicodeFullyQualfiedLongFileName);
if (!NT_SUCCESS(Status) &&
pCodeProperties->ImagePath != NULL &&
!bPathIsNtNamespace)
{
// Otherwise just live with what was passed in to us.
// If allocation fails, then path criteria will just be ignored.
Status = RtlCreateUnicodeString(
&identStruct.UnicodeFullyQualfiedLongFileName,
pCodeProperties->ImagePath);
}
//
// Perform the WinVerifyTrust sequence to see if the signing
// certificate matches any of the publishers that are in the
// trusted or distrusted publisher stores. This also has the
// additional effect of checking the "signed hashes".
//
Status = __CodeAuthzpCheckIdentityCertificateRules(
&identStruct,
dwExtendedError,
&pAuthzLevelRecord,
WTD_UI_NONE);
if (NT_SUCCESS(Status)) {
// An exact publisher was found, so return immediately.
ASSERT(pAuthzLevelRecord != NULL);
*pBestLevelRecord = pAuthzLevelRecord;
RtlCopyMemory(pBestIdentGuid,
&guidTrustedCert, sizeof(GUID));
goto ExitHandler2;
} else if (Status != STATUS_NOT_FOUND) {
goto ExitHandler2;
}
//
// Search hash rules defined for this level/scope.
// Note that hashes match exactly or not at all,
// so if we get a positive match, then that level
// is absolutely returned.
//
Status = __CodeAuthzpCheckIdentityHashRules(
&identStruct,
&pAuthzLevelRecord,
&pAuthzIdentRecord);
if (NT_SUCCESS(Status)) {
// An exact hash identity was found, so return immediately.
ASSERT(pAuthzLevelRecord != NULL);
*pBestLevelRecord = pAuthzLevelRecord;
RtlCopyMemory(pBestIdentGuid,
&pAuthzIdentRecord->IdentGuid, sizeof(GUID));
goto ExitHandler2;
} else if (Status != STATUS_NOT_FOUND) {
goto ExitHandler2;
}
ASSERT(pAuthzLevelRecord == NULL);
//
// Search file path rules defined for this level/scope.
// Note that file paths can either be an exact match
// or a partial match. If we find an exact match, then
// it should be absolutely returned. Otherwise the
// path was a "grouping match" and we must compare the
// Level with all of the remaining "grouping checks".
//
Status = __CodeAuthzpCheckIdentityPathRules(
&identStruct,
&pAuthzLevelRecord,
&bExactPath,
&pAuthzIdentRecord);
if (NT_SUCCESS(Status)) {
ASSERT(pAuthzLevelRecord != NULL);
pTempLevelRecord = pAuthzLevelRecord;
pTempIdentRecord = pAuthzIdentRecord;
if (bExactPath) {
*pBestLevelRecord = pTempLevelRecord;
RtlCopyMemory(pBestIdentGuid,
&pTempIdentRecord->IdentGuid, sizeof(GUID));
goto ExitHandler2;
}
} else if (Status != STATUS_NOT_FOUND) {
goto ExitHandler2;
}
//
// Search URL Zone identity rules.
// Note that zones are always "grouping matches",
// so they must be compared against all of the remaining
// "grouping checks".
//
Status = __CodeAuthzpCheckIdentityUrlZoneRules(
&identStruct,
&pTempLevelRecord,
&pTempIdentRecord);
if (NT_SUCCESS(Status)) {
ASSERT(pTempLevelRecord != NULL);
if (pAuthzLevelRecord == NULL ||
pTempLevelRecord->dwLevelId <
pAuthzLevelRecord->dwLevelId)
{
pAuthzLevelRecord = pTempLevelRecord;
pAuthzIdentRecord = pTempIdentRecord;
}
} else if (Status != STATUS_NOT_FOUND) {
goto ExitHandler2;
}
#ifdef SAFER_PROMPT_USER_FOR_DECISION_MAKING
#error "Prompting user in WinVerifyTrust"
//
// We were originally passed UI flag, but we supressed
// the UI display the first time. Call WinVerifyTrust
// again and see if user choice would allow code to run.
//
if (bRetryCertRuleCheck)
{
if (pAuthzLevelRecord != NULL) {
//If we have a rule match and the rule match is FULLYTRUSTED skip retry.
if (pAuthzLevelRecord->dwLevelId == SAFER_LEVELID_FULLYTRUSTED) {
bRetryCertRuleCheck = FALSE;
}
} else if (g_DefaultCodeLevel != NULL) {
//No rule match so far. Check default level.
//If default level is FULLY_TRUSTED skip retry
if (g_DefaultCodeLevel->dwLevelId == SAFER_LEVELID_FULLYTRUSTED) {
bRetryCertRuleCheck = FALSE;
}
}
//
// Perform the WinVerifyTrust sequence again to see if the signing
// certificate matches any of the publishers that are in the
// trusted or distrusted publisher stores.
//
if (bRetryCertRuleCheck) {
Status = __CodeAuthzpCheckIdentityCertificateRules(
&identStruct,
&pTempLevelRecord,
identStruct.CodeProps->dwWVTUIChoice);
if (NT_SUCCESS(Status)) {
// User clicked Yes or No. Run it as such.
ASSERT(pTempLevelRecord != NULL);
*pBestLevelRecord = pTempLevelRecord;
RtlCopyMemory(pBestIdentGuid,
&guidTrustedCert, sizeof(GUID));
goto ExitHandler2;
}
}
}
#endif
//
// If we found any Level matches at this point, then we
// should simply return that match. The identified Level
// will be the MIN() of all "grouping matches" found.
//
if (pAuthzLevelRecord != NULL) {
Status = STATUS_SUCCESS;
*pBestLevelRecord = pAuthzLevelRecord;
ASSERT(pAuthzIdentRecord != NULL);
RtlCopyMemory(pBestIdentGuid,
&pAuthzIdentRecord->IdentGuid, sizeof(GUID));
goto ExitHandler2;
}
}
//
// Now we need to consider the default WinSafer Level and
// return it if one was defined. If there was no default
// defined, then we should simply return STATUS_NOT_FOUND.
//
if (g_DefaultCodeLevel != NULL) {
*pBestLevelRecord = g_DefaultCodeLevel;
RtlCopyMemory(pBestIdentGuid, &guidDefaultRule, sizeof(GUID));
Status = STATUS_SUCCESS;
goto ExitHandler2;
}
Status = STATUS_NOT_FOUND;
ExitHandler2:
__CodeAuthzpEnsureUnmapped(&identStruct);
if (identStruct.UnicodeFullyQualfiedLongFileName.Buffer != NULL) {
RtlFreeUnicodeString(&identStruct.UnicodeFullyQualfiedLongFileName);
}
if (identStruct.bCloseFileHandle && identStruct.hFileHandle != NULL) {
NtClose(identStruct.hFileHandle);
}
ExitHandler:
return Status;
}
BOOL
SaferpSkipPolicyForAdmins(VOID)
/*++
Routine Description:
Decides whether or not Safer policy should be skipped.
Policy is skipped if
1. The caller is an Admin AND
2. The registry key specifies that the policy should be skipped
for Admins.
Arguments:
Return Value:
Returns TRUE if a policy should be skipped for admins.
Returns FALSE otherwise or in case of any intermediate errors.
--*/
{
static BOOL gSaferSkipPolicy = 2;
BOOL bIsAdmin = FALSE;
DWORD AdminSid[] = {0x201, 0x5000000, 0x20, 0x220};
NTSTATUS Status = STATUS_SUCCESS;
// If we have already evaluated policy once, return the cached value.
if (2 != gSaferSkipPolicy)
{
return gSaferSkipPolicy;
}
// Set the default to "will not skip policy"
gSaferSkipPolicy = 0;
// Check if the caller is an admin.
if (CheckTokenMembership(NULL, (PSID) AdminSid, &bIsAdmin))
{
// The caller is an Admin. Let's check whether the regkey says it's ok
// to skip the policy for admins.
if (bIsAdmin)
{
const static UNICODE_STRING SaferUnicodeKeyName = RTL_CONSTANT_STRING(L"\\Registry\\Machine\\Software\\Policies\\Microsoft\\Windows\\Safer\\CodeIdentifiers");
const static OBJECT_ATTRIBUTES SaferObjectAttributes = RTL_CONSTANT_OBJECT_ATTRIBUTES(&SaferUnicodeKeyName, OBJ_CASE_INSENSITIVE);
const static UNICODE_STRING SaferPolicyScope = RTL_CONSTANT_STRING(SAFER_POLICY_SCOPE);
HANDLE hKeyEnabled = NULL;
BYTE QueryBuffer[sizeof(KEY_VALUE_PARTIAL_INFORMATION) + 64];
PKEY_VALUE_PARTIAL_INFORMATION pKeyValueInfo = (PKEY_VALUE_PARTIAL_INFORMATION) QueryBuffer;
DWORD dwActualSize = 0;
// Open the CodeIdentifiers key.
Status = NtOpenKey(
&hKeyEnabled,
KEY_QUERY_VALUE,
(POBJECT_ATTRIBUTES) &SaferObjectAttributes
);
if (NT_SUCCESS(Status)) {
// Read the Policy Scope value.
Status = NtQueryValueKey(
hKeyEnabled,
(PUNICODE_STRING) &SaferPolicyScope,
KeyValuePartialInformation,
pKeyValueInfo,
sizeof(QueryBuffer),
&dwActualSize
);
NtClose(hKeyEnabled);
// Skip policy if the flag is set to 1.
if (NT_SUCCESS(Status)) {
if ((pKeyValueInfo->Type == REG_DWORD) &&
(pKeyValueInfo->DataLength == sizeof(DWORD)) &&
(*((PDWORD) pKeyValueInfo->Data) & 0x1)) {
gSaferSkipPolicy = 1;
}
}
}
}
}
return gSaferSkipPolicy;
}
VOID
SaferpLogResultsToFile(
LPWSTR InputImageName,
LPWSTR LevelName,
LPWSTR RuleTypeName,
GUID *Guid
)
/*++
Routine Description:
Logs a message to a file specified in
HKLM\Software\Policies\Microsoft\Windows\Safer\CodeIdentifiers LogFileName.
The format of the message is:
TLIST.EXE (PID = 1076) identified C:\SAFERTEST\TEST.VBS as FULLY TRUSTED
using CERTIFICATE rul, Guid = {abcdef00-abcd-abcd-abcdefabcdef00}
Arguments:
Return Value:
--*/
{
#define SAFER_LOG_NAME1 L" (PID = "
#define SAFER_LOG_NAME2 L") identified "
#define SAFER_LOG_NAME3 L" as "
#define SAFER_LOG_NAME4 L" using "
#define SAFER_LOG_NAME5 L" rule, Guid = "
#define SAFER_INTEGER_LENGTH 20
#define SAFER_MAX_RULE_DESCRIPTION_LENGTH 12
#define SAFER_GUID_LENGTH 38
NTSTATUS Status = STATUS_SUCCESS;
HANDLE hFile = NULL;
HANDLE hKey = NULL;
ULONG ProcessNameLength = 0;
PWCHAR Buffer = NULL;
ULONG BasicInfoLength = 0;
ULONG BytesWritten = 0;
UCHAR TmpBuf[] = {0xFF, 0xFE};
const static UNICODE_STRING SaferUnicodeKeyName = RTL_CONSTANT_STRING(L"\\Registry\\Machine\\Software\\Policies\\Microsoft\\Windows\\Safer\\CodeIdentifiers");
const static OBJECT_ATTRIBUTES SaferObjectAttributes = RTL_CONSTANT_OBJECT_ATTRIBUTES(&SaferUnicodeKeyName, OBJ_CASE_INSENSITIVE);
const static UNICODE_STRING SaferPolicyScope = RTL_CONSTANT_STRING(SAFER_LOGFILE_NAME);
PROCESS_BASIC_INFORMATION ProcInfo = {0};
ULONG TotalSize = sizeof(SAFER_LOG_NAME1) +
sizeof(SAFER_LOG_NAME2) +
sizeof(SAFER_LOG_NAME3) +
sizeof(SAFER_LOG_NAME4) +
sizeof(SAFER_LOG_NAME5) +
((SAFER_INTEGER_LENGTH +
SAFER_MAX_RULE_DESCRIPTION_LENGTH +
SAFER_GUID_LENGTH) * sizeof(WCHAR));
UCHAR QueryBuffer[sizeof(KEY_VALUE_PARTIAL_INFORMATION) + MAX_PATH * sizeof(WCHAR)];
PWCHAR ProcessImageName = NULL;
PKEY_VALUE_PARTIAL_INFORMATION pKeyValueInfo = (PKEY_VALUE_PARTIAL_INFORMATION) QueryBuffer;
DWORD dwActualSize = 0;
// Open the CodeIdentifiers key.
Status = NtOpenKey(
&hKey,
KEY_QUERY_VALUE,
(POBJECT_ATTRIBUTES) &SaferObjectAttributes
);
if (!NT_SUCCESS(Status)) {
return;
}
// Read the name of the file for logging.
Status = NtQueryValueKey(
hKey,
(PUNICODE_STRING) &SaferPolicyScope,
KeyValuePartialInformation,
pKeyValueInfo,
sizeof(QueryBuffer),
&dwActualSize
);
NtClose(hKey);
// We do not care if the buffer size was too small to retrieve the logfile
// name since this is for troubleshooting.
if (!NT_SUCCESS(Status)) {
return;
}
// This was not a string.
if (pKeyValueInfo->Type != REG_SZ) {
return;
}
hFile = CreateFileW((LPCWSTR) pKeyValueInfo->Data, GENERIC_WRITE, 0, NULL, OPEN_ALWAYS, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
return;
}
SetFilePointer (hFile, 0, NULL, FILE_BEGIN);
WriteFile (hFile, (LPCVOID)TmpBuf, 2, &BytesWritten, NULL);
SetFilePointer (hFile, 0, NULL, FILE_END);
Status = NtQueryInformationProcess(NtCurrentProcess(), ProcessImageFileName, QueryBuffer, sizeof(QueryBuffer), &ProcessNameLength);
if (!NT_SUCCESS(Status)) {
goto Cleanup;
}
ProcessImageName = (PWCHAR) (QueryBuffer + ProcessNameLength - sizeof(WCHAR));
ProcessNameLength = 1;
while (((PUCHAR) ProcessImageName >= QueryBuffer) && (*(ProcessImageName - 1) != L'\\')) {
ProcessImageName--;
ProcessNameLength++;
}
TotalSize += (ProcessNameLength + (wcslen(InputImageName) + wcslen(LevelName)) * sizeof(WCHAR));
Status = NtQueryInformationProcess(NtCurrentProcess(), ProcessBasicInformation, (PVOID) &ProcInfo, sizeof(PROCESS_BASIC_INFORMATION), &BasicInfoLength);
if (!NT_SUCCESS(Status)) {
goto Cleanup;
}
Buffer = (PWCHAR) RtlAllocateHeap(RtlProcessHeap(), 0, TotalSize);
if (Buffer == NULL) {
goto Cleanup;
}
swprintf(Buffer, L"%s%s%d%s%s%s%s%s%s%s{%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}\r\n",
ProcessImageName,
SAFER_LOG_NAME1,
ProcInfo.UniqueProcessId,
SAFER_LOG_NAME2,
InputImageName,
SAFER_LOG_NAME3,
LevelName,
SAFER_LOG_NAME4,
RuleTypeName,
SAFER_LOG_NAME5,
Guid->Data1, Guid->Data2, Guid->Data3, Guid->Data4[0], Guid->Data4[1], Guid->Data4[2], Guid->Data4[3], Guid->Data4[4], Guid->Data4[5], Guid->Data4[6], Guid->Data4[7]);
ASSERT((wcslen(Buffer) + 1) * sizeof(WCHAR) < TotalSize);
WriteFile (hFile, (LPCVOID)Buffer, (wcslen(Buffer) * sizeof(WCHAR)), &BytesWritten, NULL);
RtlFreeHeap(RtlProcessHeap(), 0, Buffer);
Cleanup:
CloseHandle(hFile);
}
#define USER_REGISTRY_EXT_MUTEX TEXT("userenv: User Registry policy mutex")
#define MACH_REGISTRY_EXT_MUTEX TEXT("Global\\userenv: Machine Registry policy mutex")
HANDLE
SaferpEnterCriticalPolicySection(
IN PWCHAR wszMutex,
IN DWORD dwTimeOut
)
/*++
Routine Description:
Acquires the machine/user mutex.
Arguments:
wszMutex - Name of the mutex to acquire.
dwTimeOut - Time for we will wait in case policy processing is going on.
Return Value:
Returns the handle to the mutex. In case of errors, the return value in ZERO.
--*/
{
HANDLE hSection;
DWORD dwRet;
//
// Open the mutex to wait on.
//
hSection = OpenMutex(SYNCHRONIZE, FALSE, wszMutex);
if (!hSection) {
return NULL;
}
//
// Claim the mutex
//
dwRet = WaitForSingleObject(hSection, dwTimeOut);
if ((dwRet == WAIT_FAILED) || (dwRet == WAIT_TIMEOUT)) {
CloseHandle(hSection);
return NULL;
}
return hSection;
}
BOOL
SaferpCheckKeyStamp(
IN HKEY Key
)
/*++
Routine Description:
Compare the last write stamp on the CodeIdentifiers key vs the last time
we loaded Safer policy.
Arguments:
Key - Handle to HKLM or HKCU.
Return Value:
Returns TRUE if policy needs to be reloaded.
--*/
{
#define SAFERP_KEY_NAME L"Software\\Policies\\Microsoft\\Windows\\Safer\\CodeIdentifiers"
NTSTATUS Status;
HKEY SubKey = NULL;
DWORD SizeIgnore = 0;
WCHAR Buffer[MAX_PATH];
PKEY_BASIC_INFORMATION KeyInfo = (PKEY_BASIC_INFORMATION) Buffer;
//
// Open the CodeIdentifiers key.
//
DWORD dwErr = RegOpenKeyW(Key, SAFERP_KEY_NAME, &SubKey);
if (dwErr != ERROR_SUCCESS) {
return FALSE;
}
//
// Query the key for basic information.
//
Status = NtQueryKey(SubKey, KeyBasicInformation, KeyInfo, sizeof(Buffer), &SizeIgnore);
CloseHandle(SubKey);
if (!NT_SUCCESS(Status)) {
return FALSE;
}
//
// Compare the timestmaps.
//
if (-1 == CompareFileTime((FILETIME *) &g_SaferPolicyTimeStamp, (FILETIME *) &KeyInfo->LastWriteTime)) {
return TRUE;
}
return FALSE;
}
NTSTATUS
SaferpGrabGroupPolicyLocks(
OUT PHANDLE phMachine,
OUT PHANDLE phUser
)
/*++
Routine Description:
Acquire both user and machine locks.
Arguments:
phMachine - To return the handle to machine mutex.
phUser - To return the handle to user mutex.
Return Value:
Returns STATUS_SUCCESS if both mutexes have been acquired.
--*/
{
//
// Observe the locking order - machine first, then user.
//
*phMachine = SaferpEnterCriticalPolicySection(MACH_REGISTRY_EXT_MUTEX, 0);
if (*phMachine != NULL) {
//
// We got the machine lock. Now let's get the user lock.
//
*phUser = SaferpEnterCriticalPolicySection(USER_REGISTRY_EXT_MUTEX, 0);
if (*phUser != NULL) {
return STATUS_SUCCESS;
}
//
// If we could not get the User lock, release the machine lock.
//
ReleaseMutex (*phMachine);
CloseHandle (*phMachine);
*phMachine = NULL;
}
return STATUS_ACCESS_DENIED;
}
VOID
SaferpReleaseGroupPolicyLocks(
IN HANDLE hMachine,
IN HANDLE hUser
)
/*++
Routine Description:
Release both user and machine locks.
Arguments:
hMachine - Machine handle to release.
hUser - User handle to release.
Return Value:
None.
--*/
{
if (hUser)
{
ReleaseMutex(hUser);
CloseHandle(hUser);
}
if (hMachine)
{
ReleaseMutex(hMachine);
CloseHandle(hMachine);
}
}
NTSTATUS
SaferpReloadPolicyIfNeeded()
/*++
Routine Description:
Reload Safer policy in case the time stamp on Safer keys is more recent than
the last time we loaded policy.
Arguments:
None.
Return Value:
Returns STATUS_SUCCESS in case we fail to load the policy after we have decided
to do so. No failure is returned if we encounter an error before that since
we already have a cached one.
--*/
{
NTSTATUS Status;
HANDLE hUser = NULL;
HANDLE hMachine = NULL;
//
// Check if Machine policy has changed.
//
if (!SaferpCheckKeyStamp(HKEY_LOCAL_MACHINE))
{
//
// Machine policy has not changed.
// Check if User policy has changed.
//
if (!SaferpCheckKeyStamp(HKEY_CURRENT_USER))
{
//
// Neither policy has changed. Just return.
//
return STATUS_SUCCESS;
}
}
//
// Check if it is ok to load policy.
//
Status = SaferpGrabGroupPolicyLocks(&hMachine, &hUser);
if (!NT_SUCCESS(Status)) {
//
// Failure to grab the locks is ok here. We will just return.
//
Status = STATUS_SUCCESS;
goto Cleanup;
}
//
// Release the current set of rules.
//
CodeAuthzGuidIdentsEntireTableFree(&g_CodeIdentitiesTable);
CodeAuthzLevelObjpEntireTableFree(&g_CodeLevelObjTable);
//
// And reload the policy.
//
g_bNeedCacheReload = TRUE;
Status = CodeAuthzpImmediateReloadCacheTables();
if (!NT_SUCCESS(Status)) {
ASSERT(FALSE);
}
Cleanup:
//
// Leave the group policy locks.
//
SaferpReleaseGroupPolicyLocks(hMachine, hUser);
return Status;
}
BOOL WINAPI
SaferIdentifyLevel(
IN DWORD dwNumProperties,
IN PSAFER_CODE_PROPERTIES pCodeProperties,
OUT SAFER_LEVEL_HANDLE *pLevelHandle,
IN LPVOID lpReserved
)
/*++
Routine Description:
Performs the code identification process. Accepts an array of
CODE_PROPERTIES structures that supply all of the identification
criteria. The final result is the least privileged match resulting
from each element of the array.
Arguments:
dwNumProperties - indicates the number of CODE_PROPERTIES structures
pointed to by the CodeProperties argument.
pCodeProperties - pointer to one or more structures that specify
all of the input criteria that will be used to identify level.
pLevelHandle - pointer that will receive the opened Level object
handle when the identification operation is successful.
lpReserved - unused, must be zero.
Return Value:
Returns TRUE if a Level was identified and an opened handle
to it stored in the 'LevelHandle' argument. Otherwise this
function returns FALSE on error and GetLastError() can be used
to obtain additional information about the error.
--*/
{
DWORD Index;
NTSTATUS Status;
BOOL ReturnValue = FALSE;
PAUTHZLEVELTABLERECORD pBestLevelRecord;
GUID BestIdentGuid;
PWCHAR LocalLevelName = L"\"default\"";
PWCHAR LocalRuleName = L"default";
PWCHAR LocalImageName = L"Default";
DWORD dwExtendedError = ERROR_SUCCESS;
BOOL bCheckPolicyPropagation = TRUE;
//
// Validate the input parameters.
//
UNREFERENCED_PARAMETER(lpReserved);
if (!ARGUMENT_PRESENT(pLevelHandle)) {
Status = STATUS_ACCESS_VIOLATION;
goto ExitHandler;
}
if (!g_bInitializedFirstTime) {
Status = STATUS_UNSUCCESSFUL;
goto ExitHandler;
}
RtlEnterCriticalSection(&g_TableCritSec);
if (g_bNeedCacheReload) {
Status = CodeAuthzpImmediateReloadCacheTables();
if (!NT_SUCCESS(Status)) {
goto ExitHandler2;
}
bCheckPolicyPropagation = FALSE;
}
if (RtlIsGenericTableEmpty(&g_CodeLevelObjTable)) {
// There are no levels defined! Should not happen.
Status = STATUS_NOT_FOUND;
goto ExitHandler2;
}
//
// Do not allow filehandles unless filename is specified.
//
for (Index = 0; Index < dwNumProperties; Index++)
{
if (pCodeProperties[Index].hImageFileHandle != NULL &&
pCodeProperties[Index].hImageFileHandle != INVALID_HANDLE_VALUE &&
pCodeProperties[Index].ImagePath == NULL)
{
Status = STATUS_INVALID_PARAMETER;
goto ExitHandler2;
}
}
if (SaferpSkipPolicyForAdmins())
{
pBestLevelRecord = CodeAuthzLevelObjpLookupByLevelId(
&g_CodeLevelObjTable, SAFER_LEVELID_FULLYTRUSTED);
RtlCopyMemory(&BestIdentGuid, &guidDefaultRule, sizeof(GUID));
goto GotMatchingRule;
}
if (!ARGUMENT_PRESENT(pCodeProperties) || dwNumProperties == 0) {
// We were given no criteria to evaluate, so just return
// the default level. If there was no default defined,
// then we should simply return STATUS_NOT_FOUND.
if (g_DefaultCodeLevel != NULL) {
pBestLevelRecord = g_DefaultCodeLevel;
RtlCopyMemory(&BestIdentGuid, &guidDefaultRule, sizeof(GUID));
goto GotMatchingRule;
} else {
Status = STATUS_NOT_FOUND;
goto ExitHandler2;
}
}
//
// If we did not reload policy in this call, check if we need to.
//
if (bCheckPolicyPropagation)
{
//
// Make sure that we do not have any outstanding handles.
//
if (0 == g_dwNumHandlesAllocated)
{
//
// Get the last time policy was propagated.
//
Status = SaferpReloadPolicyIfNeeded();
if (!NT_SUCCESS(Status))
{
goto ExitHandler2;
}
}
}
//
// Iterate through the list of CODE_PROPERTIES supplied
// and determine the final code Level that should be used.
//
pBestLevelRecord = NULL;
for (Index = 0; Index < dwNumProperties; Index++)
{
PAUTHZLEVELTABLERECORD pOneLevelRecord;
GUID OneIdentGuid;
Status = __CodeAuthzpIdentifyOneCodeAuthzLevel(
&pCodeProperties[Index],
&dwExtendedError,
&pOneLevelRecord,
&OneIdentGuid);
if (NT_SUCCESS(Status)) {
ASSERT(pOneLevelRecord != NULL);
if (!pBestLevelRecord ||
pOneLevelRecord->dwLevelId <
pBestLevelRecord->dwLevelId )
{
pBestLevelRecord = pOneLevelRecord;
RtlCopyMemory(&BestIdentGuid, &OneIdentGuid, sizeof(GUID));
}
} else if (Status != STATUS_NOT_FOUND) {
// An unexpected error occurred, so return that.
goto ExitHandler2;
}
}
if (pBestLevelRecord == NULL) {
Status = STATUS_NOT_FOUND;
goto ExitHandler2;
}
//
// Now we have the result so pass back a handle to the
// identified WinSafer Level.
// Allocate a handle to represent this level.
//
GotMatchingRule:
ASSERT(pBestLevelRecord != NULL);
if (IsEqualGUID(&guidDefaultRule, &BestIdentGuid))
{
// The resulting level match came from the default rule.
// Now we have to try to guess whether the default actually
// came from the Machine or User scope.
DWORD dwScopeId;
if (g_hKeyCustomRoot != NULL) {
dwScopeId = SAFER_SCOPEID_REGISTRY;
} else if (g_DefaultCodeLevelUser != NULL &&
g_DefaultCodeLevelUser->dwLevelId ==
pBestLevelRecord->dwLevelId) {
dwScopeId = SAFER_SCOPEID_USER;
} else {
dwScopeId = SAFER_SCOPEID_MACHINE;
}
Status = CodeAuthzpCreateLevelHandleFromRecord(
pBestLevelRecord, // pLevelRecord
dwScopeId, // dwScopeId
0, // dwSaferFlags
dwExtendedError,
SaferIdentityDefault,
&BestIdentGuid, // pIdentRecord
pLevelHandle // pLevelHandle
);
}
else if (IsEqualGUID(&guidTrustedCert, &BestIdentGuid))
{
// Note that when the result is from a certificate, we have
// no way of actually knowing whether the certificate was
// defined within the Machine or User scope, so we'll just
// arbitrarily pick the Machine scope for the handle to be
// based out of. Additionally, there are no SaferFlags
// persisted for certificates so we just assume 0.
Status = CodeAuthzpCreateLevelHandleFromRecord(
pBestLevelRecord, // pLevelRecord
SAFER_SCOPEID_MACHINE, // dwScopeId
0, // dwSaferFlags
dwExtendedError,
SaferIdentityTypeCertificate,
&BestIdentGuid, // pIdentRecord
pLevelHandle // pLevelHandle
);
LocalRuleName = L"certificate";
}
else
{
// Otherwise the result must have come from a path, hash,
// or zone rule, so we must look up the resulting GUID in our
// identity table and retrieve the SaferFlags that were stored
// along with that Identity record. But we won't panic if we
// can't actually find the GUID anymore (even though that should
// not ever be the case while we have the critical section).
PAUTHZIDENTSTABLERECORD pBestIdentRecord;
DWORD dwSaferFlags = 0;
SAFER_IDENTIFICATION_TYPES LocalIdentificationType = SaferIdentityDefault;
pBestIdentRecord = CodeAuthzIdentsLookupByGuid(
&g_CodeIdentitiesTable, &BestIdentGuid);
if (pBestIdentRecord != NULL) {
// we identified a level, and the match came from a Identity.
switch (pBestIdentRecord->dwIdentityType) {
case SaferIdentityTypeImageName:
dwSaferFlags = pBestIdentRecord->ImageNameInfo.dwSaferFlags;
LocalRuleName = L"path";
LocalIdentificationType = SaferIdentityTypeImageName;
break;
case SaferIdentityTypeImageHash:
dwSaferFlags = pBestIdentRecord->ImageHashInfo.dwSaferFlags;
LocalRuleName = L"hash";
LocalIdentificationType = SaferIdentityTypeImageHash;
break;
case SaferIdentityTypeUrlZone:
dwSaferFlags = pBestIdentRecord->ImageZone.dwSaferFlags;
LocalRuleName = L"zone";
LocalIdentificationType = SaferIdentityTypeUrlZone;
break;
default: break;
}
Status = CodeAuthzpCreateLevelHandleFromRecord(
pBestLevelRecord, // pLevelRecord
pBestIdentRecord->dwScopeId,
dwSaferFlags, // dwSaferFlags
dwExtendedError,
LocalIdentificationType,
&BestIdentGuid, // pIdentRecord
pLevelHandle // pLevelHandle
);
}
else
{
Status = STATUS_ACCESS_DENIED;
}
}
if (NT_SUCCESS(Status)) {
ReturnValue = TRUE; // success.
}
switch(pBestLevelRecord->dwLevelId)
{
case SAFER_LEVELID_FULLYTRUSTED:
LocalLevelName = L"Unrestricted";
break;
case SAFER_LEVELID_NORMALUSER:
LocalLevelName = L"Basic User";
break;
case SAFER_LEVELID_CONSTRAINED:
LocalLevelName = L"Restricted";
break;
case SAFER_LEVELID_UNTRUSTED:
LocalLevelName = L"Untrusted";
break;
case SAFER_LEVELID_DISALLOWED:
LocalLevelName = L"Disallowed";
break;
default:
ASSERT(FALSE);
break;
}
if (pCodeProperties->ImagePath != NULL) {
LocalImageName = (PWSTR) pCodeProperties->ImagePath;
}
SaferpLogResultsToFile(
LocalImageName,
LocalLevelName,
LocalRuleName,
&BestIdentGuid);
ExitHandler2:
RtlLeaveCriticalSection(&g_TableCritSec);
ExitHandler:
if (!ReturnValue) {
BaseSetLastNTError(Status);
}
return ReturnValue;
}