/* Cache handling functions for use in kernel32.dll VadimB */ #include "sdbp.h" #define _APPHELP_CACHE_INIT_ #include "ahcache.h" #include NTSTATUS ApphelpCacheControlValidateParameters( IN PAHCACHESERVICEDATA pServiceData, OUT PUNICODE_STRING pFileName, OUT HANDLE* pFileHandle ); NTSYSCALLAPI NTSTATUS NtApphelpCacheControl( IN APPHELPCACHESERVICECLASS Service, IN OUT PVOID ServiceData ); #ifdef ALLOC_PRAGMA #pragma alloc_text(PAGE, ApphelpCacheInitialize) // INIT ? #pragma alloc_text(PAGE, ApphelpDuplicateUnicodeString) #pragma alloc_text(PAGE, ApphelpFreeUnicodeString) #pragma alloc_text(PAGE, ApphelpCacheQueryFileInformation) #pragma alloc_text(PAGE, ApphelpCacheCompareEntries) #pragma alloc_text(PAGE, ApphelpAVLTableAllocate) #pragma alloc_text(PAGE, ApphelpAVLTableFree) #pragma alloc_text(PAGE, _ApphelpCacheFreeEntry) #pragma alloc_text(PAGE, _ApphelpCacheDeleteEntry) #pragma alloc_text(PAGE, ApphelpCacheRemoveEntry) #pragma alloc_text(PAGE, ApphelpCacheInsertEntry) #pragma alloc_text(PAGE, ApphelpCacheLookupEntry) #pragma alloc_text(PAGE, ApphelpCacheParseBuffer) #pragma alloc_text(PAGE, ApphelpCacheCreateBuffer) #pragma alloc_text(PAGE, ApphelpCacheWrite) #pragma alloc_text(PAGE, ApphelpCacheRead) #pragma alloc_text(PAGE, ApphelpCacheVerifyContext) #pragma alloc_text(PAGE, ApphelpCacheReleaseLock) #pragma alloc_text(PAGE, ApphelpCacheLockExclusive) #pragma alloc_text(PAGE, ApphelpCacheLockExclusiveNoWait) #pragma alloc_text(PAGE, ApphelpCacheFlush) #pragma alloc_text(PAGE, ApphelpCacheControlValidateParameters) #pragma alloc_text(PAGE, ApphelpCacheShutdown) #pragma alloc_text(PAGE, ApphelpCacheDump) #pragma alloc_text(PAGE, NtApphelpCacheControl) #endif // ALLOC_PRAGMA #define APPCOMPAT_CACHE_KEY_NAME \ L"\\Registry\\MACHINE\\System\\CurrentControlSet\\Control\\Session Manager\\AppCompatCache" #define APPCOMPAT_CACHE_VALUE_NAME \ L"AppCompatCache" static UNICODE_STRING AppcompatKeyPathLayers = RTL_CONSTANT_STRING(L"\\Software\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\Layers"); static UNICODE_STRING AppcompatKeyPathCustom = RTL_CONSTANT_STRING(L"\\Registry\\Machine\\Software\\Microsoft\\Windows NT\\CurrentVersion\\AppCompatFlags\\Custom\\"); // // The default cache timeout. This timeout affects the maximum delay // that we can incur due to congestion for the shared mutex. // #define SHIM_CACHE_TIMEOUT 100 // // Cache entries as they are stored in the registry // typedef struct tagSTOREDCACHEENTRY { UNICODE_STRING FileName; // length, maximum length and buffer that is an offset LONGLONG FileTime; LONGLONG FileSize; } STOREDCACHEENTRY, *PSTOREDCACHEENTRY; typedef struct tagSTOREDCACHEHEADER { DWORD dwMagic; // cache identifier DWORD dwCount; // entry count } STOREDCACHEHEADER, *PSTOREDCACHEHEADER; // // Global cache data // typedef struct tagSHIMCACHEHEADER { RTL_AVL_TABLE Table; // cache LIST_ENTRY ListHead; // cache nodes, lru list } SHIMCACHEHEADER, *PSHIMCACHEHEADER; SHIMCACHEHEADER g_ShimCache; // global header ERESOURCE g_SharedLock; BOOL g_bCacheEnabled = FALSE; // // Magic DWORD that allows us to validate the cache quickly // we do not however limit validation to this dword check // and we are prepared for data being completely invalid // #define SHIM_CACHE_MAGIC_NEW 0xBADC0FFE // // Maximum number of cache entries // #define MAX_SHIM_CACHE_ENTRIES 0x200 /////////////////////////////////////////////////////////////////////////////////////// // // Utility functions // // /////////////////////////////////////////////////////////////////////////////////////// NTSTATUS ApphelpDuplicateUnicodeString( PUNICODE_STRING pStrDest, PUNICODE_STRING pStrSrc ) { USHORT Length = pStrSrc->Length; if (Length == 0) { pStrDest->Length = pStrDest->MaximumLength = 0; pStrDest->Buffer = NULL; return STATUS_SUCCESS; } pStrDest->MaximumLength = Length + sizeof(UNICODE_NULL); pStrDest->Buffer = (PWCHAR)SdbAlloc(pStrDest->MaximumLength); if (pStrDest->Buffer == NULL) { return STATUS_NO_MEMORY; } RtlCopyMemory(pStrDest->Buffer, pStrSrc->Buffer, Length); *(pStrDest->Buffer + Length/sizeof(WCHAR)) = L'\0'; pStrDest->Length = Length; return STATUS_SUCCESS; } VOID ApphelpFreeUnicodeString( PUNICODE_STRING pStr ) { if (pStr != NULL) { if (pStr->Buffer != NULL) { // #if DBG RtlFillMemory(pStr->Buffer, pStr->MaximumLength, 'B'); // #endif SdbFree(pStr->Buffer); } RtlZeroMemory(pStr, sizeof(*pStr)); } } NTSTATUS ApphelpCacheQueryFileInformation( HANDLE FileHandle, PLONGLONG pFileSize, PLONGLONG pFileTime ) /*++ Return: Status indicating success or failure Desc: Queries for file size and timestamp. --*/ { NTSTATUS Status; IO_STATUS_BLOCK IoStatusBlock; FILE_BASIC_INFORMATION BasicFileInfo; FILE_STANDARD_INFORMATION StdFileInfo; LONGLONG FileTime; Status = NtQueryInformationFile(FileHandle, &IoStatusBlock, &BasicFileInfo, sizeof(BasicFileInfo), FileBasicInformation); if (!NT_SUCCESS(Status)) { DBGPRINT((sdlError, "ShimQueryFileInformation", "NtQueryInformationFile/BasicInfo failed 0x%x\n", Status)); goto Cleanup; } FileTime = BasicFileInfo.LastWriteTime.QuadPart; Status = NtQueryInformationFile(FileHandle, &IoStatusBlock, &StdFileInfo, sizeof(StdFileInfo), FileStandardInformation); if (!NT_SUCCESS(Status)) { DBGPRINT((sdlError, "ShimQueryFileInformation", "NtQueryInformationFile/StdInfo failed 0x%x\n", Status)); goto Cleanup; } *pFileSize = StdFileInfo.EndOfFile.QuadPart; *pFileTime = FileTime; Cleanup: return Status; } /////////////////////////////////////////////////////////////////////////////////// // // Table handling routines // /////////////////////////////////////////////////////////////////////////////////// RTL_GENERIC_COMPARE_RESULTS NTAPI ApphelpCacheCompareEntries( IN PRTL_AVL_TABLE pTable, IN PVOID pFirstStruct, IN PVOID pSecondStruct ) { PSHIMCACHEENTRY pFirstEntry = (PSHIMCACHEENTRY)pFirstStruct; PSHIMCACHEENTRY pSecondEntry = (PSHIMCACHEENTRY)pSecondStruct; LONG lResult; UNREFERENCED_PARAMETER(pTable); lResult = RtlCompareUnicodeString(&pFirstEntry->FileName, &pSecondEntry->FileName, TRUE); if (lResult < 0) { return GenericLessThan; } if (lResult > 0) { return GenericGreaterThan; } // match return GenericEqual; } PVOID NTAPI ApphelpAVLTableAllocate( struct _RTL_AVL_TABLE *Table, CLONG ByteSize ) { UNREFERENCED_PARAMETER(Table); return SdbAlloc(ByteSize); } VOID NTAPI ApphelpAVLTableFree( struct _RTL_AVL_TABLE *Table, PVOID pBuffer ) { UNREFERENCED_PARAMETER(Table); if (pBuffer != NULL) { SdbFree(pBuffer); } } ////////////////////////////////////////////////////////////////////////////////////// // // Delete cache entry - by filename or using a pointer // ////////////////////////////////////////////////////////////////////////////////////// NTSTATUS _ApphelpCacheFreeEntry( IN PSHIMCACHEENTRY pEntry ) { PWCHAR pBuffer; BOOL bDeleted; NTSTATUS Status = STATUS_NOT_FOUND; ULONG BufferLength; ASSERT(ExIsResourceAcquiredExclusiveLite(&g_SharedLock) == TRUE); RemoveEntryList(&pEntry->ListEntry); pBuffer = pEntry->FileName.Buffer; BufferLength = pEntry->FileName.MaximumLength; bDeleted = RtlDeleteElementGenericTableAvl(&g_ShimCache.Table, pEntry); if (bDeleted) { // #if DBG RtlFillMemory(pBuffer, BufferLength, 'C'); // #endif SdbFree(pBuffer); Status = STATUS_SUCCESS; } return Status; } // // Delete Cache entry, no lock // // NTSTATUS _ApphelpCacheDeleteEntry( IN PUNICODE_STRING pFileName ) { SHIMCACHEENTRY ShimCacheEntry; PSHIMCACHEENTRY pEntryFound; NTSTATUS Status; ASSERT(ExIsResourceAcquiredExclusiveLite(&g_SharedLock) == TRUE); ShimCacheEntry.FileName = *pFileName; // // First find the element and unlink it. // pEntryFound = RtlLookupElementGenericTableAvl(&g_ShimCache.Table, &ShimCacheEntry); if (pEntryFound) { Status = _ApphelpCacheFreeEntry(pEntryFound); } else { Status = STATUS_NOT_FOUND; } return Status; } // // Delete cache entry under the lock // NTSTATUS ApphelpCacheRemoveEntry( IN PUNICODE_STRING FileName ) { NTSTATUS Status; Status = ApphelpCacheLockExclusive(); if (!NT_SUCCESS(Status)) { return Status; } Status = _ApphelpCacheDeleteEntry(FileName); ApphelpCacheReleaseLock(); return Status; } ////////////////////////////////////////////////////////////////////////////////////// // // Update the cache by inserting a new entry // ////////////////////////////////////////////////////////////////////////////////////// // // Uses Lock // NTSTATUS ApphelpCacheInsertEntry( IN PUNICODE_STRING pFileName, IN HANDLE FileHandle ) { PSHIMCACHEENTRY pEntryFound; SHIMCACHEENTRY ShimCacheEntry = { 0 }; PVOID pNodeOrParent; TABLE_SEARCH_RESULT SearchResult; NTSTATUS Status; ULONG nElements; LONGLONG FileTime = 0; LONGLONG FileSize = 0; Status = ApphelpCacheLockExclusive(); if (!NT_SUCCESS(Status)) { return Status; } if (FileHandle != INVALID_HANDLE_VALUE) { Status = ApphelpCacheQueryFileInformation(FileHandle, &FileSize, &FileTime); if (!NT_SUCCESS(Status)) { DBGPRINT((sdlError, "ApphelpCacheInsertEntry", "Failed to query file information for \"%ls\" status 0x%lx\n", pFileName->Buffer, Status)); goto Cleanup; } } ShimCacheEntry.FileName = *pFileName; // note that we take it as-is for lookup pEntryFound = (PSHIMCACHEENTRY)RtlLookupElementGenericTableFullAvl(&g_ShimCache.Table, &ShimCacheEntry, &pNodeOrParent, &SearchResult); if (SearchResult == TableFoundNode) { // // pEntryFound is valid and points to our node // // // update the data // pEntryFound->FileTime = FileTime; pEntryFound->FileSize = FileSize; // // update the lru index - exclude the element from it's place and insert // at the front of the list // RemoveEntryList(&pEntryFound->ListEntry); InsertHeadList(&g_ShimCache.ListHead, &pEntryFound->ListEntry); Status = STATUS_SUCCESS; goto Cleanup; } // // allocate the entry // Status = ApphelpDuplicateUnicodeString(&ShimCacheEntry.FileName, pFileName); if (!NT_SUCCESS(Status)) { goto Cleanup; } ShimCacheEntry.FileTime = FileTime; ShimCacheEntry.FileSize = FileSize; pEntryFound = RtlInsertElementGenericTableFullAvl(&g_ShimCache.Table, &ShimCacheEntry, sizeof(ShimCacheEntry), NULL, pNodeOrParent, SearchResult); InsertHeadList(&g_ShimCache.ListHead, &pEntryFound->ListEntry); nElements = RtlNumberGenericTableElementsAvl(&g_ShimCache.Table); if (nElements > MAX_SHIM_CACHE_ENTRIES) { // // remove an element -- check for list being empty, just in case // pEntryFound = (PSHIMCACHEENTRY)RemoveTailList(&g_ShimCache.ListHead); // // remove this entry // Status = _ApphelpCacheFreeEntry(pEntryFound); if (!NT_SUCCESS(Status)) { DBGPRINT((sdlError, "ApphelpCacheInsertEntry", "Failed to remove cache entry\n")); goto Cleanup; } } Status = STATUS_SUCCESS; Cleanup: ApphelpCacheReleaseLock(); return Status; } // // returns STATUS_SUCCESS if the entry was found in the cache // Procedure is using locking mechanism // // NTSTATUS ApphelpCacheLookupEntry( IN PUNICODE_STRING pFileName, IN HANDLE FileHandle ) { PSHIMCACHEENTRY pEntryFound; SHIMCACHEENTRY ShimCacheEntry; NTSTATUS Status = STATUS_NOT_FOUND; LONGLONG FileTime; LONGLONG FileSize; // // lock for exclusive access yet we do not wait // Status = ApphelpCacheLockExclusiveNoWait(); if (!NT_SUCCESS(Status)) { DBGPRINT((sdlInfo, "ApphelpCacheLookupEntry", "Failed to lock cache\n")); return STATUS_NOT_FOUND; } ShimCacheEntry.FileName = *pFileName; pEntryFound = RtlLookupElementGenericTableAvl(&g_ShimCache.Table, &ShimCacheEntry); if (pEntryFound == NULL) { Status = STATUS_NOT_FOUND; goto Cleanup; } if (FileHandle != INVALID_HANDLE_VALUE) { Status = ApphelpCacheQueryFileInformation(FileHandle, &FileSize, &FileTime); if (!NT_SUCCESS(Status) || pEntryFound->FileTime != FileTime || pEntryFound->FileSize != FileSize) { // // most likely the file is gone // Status = _ApphelpCacheDeleteEntry(pFileName); if (!NT_SUCCESS(Status)) { DBGPRINT((sdlWarning, "ApphelpCacheLookupEntry", "Entry \"%ls\" was found then disappeared 0x%lx\n", pFileName->Buffer, Status)); } Status = STATUS_NOT_FOUND; goto Cleanup; } } // // if we have not removed the entry -- move it to the head of the lru // RemoveEntryList(&pEntryFound->ListEntry); InsertHeadList(&g_ShimCache.ListHead, &pEntryFound->ListEntry); Status = STATUS_SUCCESS; Cleanup: ApphelpCacheReleaseLock(); return Status; } // // Verify apphelp cache caller's context // // NTSTATUS ApphelpCacheVerifyContext( VOID ) { // // verify that the cache is good to operate on // KPROCESSOR_MODE PreviousMode; NTSTATUS Status = STATUS_SUCCESS; PreviousMode = ExGetPreviousMode(); if (PreviousMode != KernelMode) { // // Does the caller have "trusted computer base" privilge? // if (!SeSinglePrivilegeCheck(SeTcbPrivilege, UserMode)) { DBGPRINT((sdlError, "ApphelpCacheVerifyContext", "Security check failed\n")); Status = STATUS_ACCESS_DENIED; } } return Status; } ////////////////////////////////////////////////////////////////////////////////////// // // Cache persistance routines // ////////////////////////////////////////////////////////////////////////////////////// NTSTATUS ApphelpCacheParseBuffer( PVOID pBuffer, ULONG lBufferSize ) { PSTOREDCACHEHEADER pStoredHeader; PSTOREDCACHEENTRY pStoredEntry; SHIMCACHEENTRY ShimCacheEntry; ULONG nElement; NTSTATUS Status; PSHIMCACHEENTRY pCacheEntry; PVOID pBufferEnd; STOREDCACHEENTRY StoredEntry; STOREDCACHEHEADER StoredHeader; if (lBufferSize < sizeof(STOREDCACHEHEADER)) { return STATUS_INVALID_PARAMETER; } pBufferEnd = (PVOID)((PBYTE)pBuffer + lBufferSize); RtlCopyMemory(&StoredHeader, pBuffer, sizeof(StoredHeader)); if (StoredHeader.dwMagic != SHIM_CACHE_MAGIC_NEW) { return STATUS_INVALID_PARAMETER; } pStoredHeader = (PSTOREDCACHEHEADER)pBuffer; pStoredEntry = (PSTOREDCACHEENTRY)(pStoredHeader + 1); for (nElement = 0; nElement < StoredHeader.dwCount; ++nElement, ++pStoredEntry) { if ((ULONG_PTR)(pStoredEntry + 1) > (ULONG_PTR)pBufferEnd) { // // invalid data // break; } // // once we determined that the entry has a valid size, copy it // RtlCopyMemory(&StoredEntry, pStoredEntry, sizeof(StoredEntry)); if ((ULONG_PTR)StoredEntry.FileName.Buffer >= (ULONG_PTR)lBufferSize) { // // also invalid entry -- offset is greater than the size of the buffer // break; } // // fixup the buffer please // StoredEntry.FileName.Buffer = (PWCHAR)((ULONG_PTR)pBuffer + (ULONG_PTR)StoredEntry.FileName.Buffer); if (StoredEntry.FileName.Length > StoredEntry.FileName.MaximumLength) { break; } if ((ULONG_PTR)(StoredEntry.FileName.Buffer + StoredEntry.FileName.MaximumLength/sizeof(WCHAR)) > (ULONG_PTR)pBufferEnd) { // // invalid data // break; } Status = ApphelpDuplicateUnicodeString(&ShimCacheEntry.FileName, &StoredEntry.FileName); if (!NT_SUCCESS(Status)) { return Status; } ShimCacheEntry.FileTime = StoredEntry.FileTime; ShimCacheEntry.FileSize = StoredEntry.FileSize; // // insert the entry // pCacheEntry = (PSHIMCACHEENTRY)RtlInsertElementGenericTableAvl(&g_ShimCache.Table, &ShimCacheEntry, sizeof(ShimCacheEntry), NULL); if (pCacheEntry == NULL) { // // the entry has not been inserted // clean it up now // ApphelpFreeUnicodeString(&ShimCacheEntry.FileName); return STATUS_NO_MEMORY; } InsertTailList(&g_ShimCache.ListHead, &pCacheEntry->ListEntry); } return STATUS_SUCCESS; } NTSTATUS ApphelpCacheCreateBuffer( PVOID* ppBuffer, PULONG pBufferSize ) { ULONG nElements = 0; ULONG lBufferSize; // size of the buffer we need PLIST_ENTRY pListEntry; PSHIMCACHEENTRY pEntry; PVOID pBuffer; PSTOREDCACHEENTRY pStoredEntry; PSTOREDCACHEHEADER pStoredHeader; PWCHAR pStringBuffer; // // Calculate total size // lBufferSize = sizeof(STOREDCACHEHEADER); pListEntry = g_ShimCache.ListHead.Flink; while (pListEntry != &g_ShimCache.ListHead) { pEntry = (PSHIMCACHEENTRY)pListEntry; lBufferSize += sizeof(STOREDCACHEENTRY) + pEntry->FileName.MaximumLength; nElements++; pListEntry = pListEntry->Flink; } lBufferSize = ROUND_UP_COUNT(lBufferSize, sizeof(ULONGLONG)); // // once we have all the entries, allocate the cache // pBuffer = SdbAlloc(lBufferSize); if (pBuffer == NULL) { return STATUS_NO_MEMORY; } pStoredHeader = (PSTOREDCACHEHEADER)pBuffer; pStoredHeader->dwMagic = SHIM_CACHE_MAGIC_NEW; pStoredHeader->dwCount = nElements; pStoredEntry = (PSTOREDCACHEENTRY)(pStoredHeader + 1); pStringBuffer = (PWCHAR)((PBYTE)pBuffer + lBufferSize); // // flatten the data // pListEntry = g_ShimCache.ListHead.Flink; while (pListEntry != &g_ShimCache.ListHead) { pEntry = (PSHIMCACHEENTRY)pListEntry; // // store the entry first // pStoredEntry->FileTime = pEntry->FileTime; pStoredEntry->FileSize = pEntry->FileSize; // // Filename is more interesting // pStoredEntry->FileName.Length = pEntry->FileName.Length; pStoredEntry->FileName.MaximumLength = pEntry->FileName.MaximumLength; // // now the buffer // pStringBuffer = (PWCHAR)((PBYTE)pStringBuffer - pEntry->FileName.MaximumLength); RtlCopyMemory(pStringBuffer, pEntry->FileName.Buffer, pEntry->FileName.MaximumLength); // // fixup the pointer // pStoredEntry->FileName.Buffer = (PWCHAR)((ULONG_PTR)pStringBuffer - (ULONG_PTR)pBuffer); pListEntry = pListEntry->Flink; pStoredEntry++; } // // done with the buffer // *ppBuffer = pBuffer; *pBufferSize = lBufferSize; return STATUS_SUCCESS; } NTSTATUS ApphelpCacheWrite( VOID ) { static UNICODE_STRING ustrAppcompatCacheKeyName = RTL_CONSTANT_STRING(APPCOMPAT_CACHE_KEY_NAME); static OBJECT_ATTRIBUTES objaAppcompatCacheKeyName = RTL_CONSTANT_OBJECT_ATTRIBUTES(&ustrAppcompatCacheKeyName, OBJ_CASE_INSENSITIVE); static UNICODE_STRING ustrAppcompatCacheValueName = RTL_CONSTANT_STRING(APPCOMPAT_CACHE_VALUE_NAME); HANDLE hKey = NULL; ULONG BufferSize = 0; NTSTATUS Status; ULONG CreateDisposition; PVOID pBuffer = NULL; Status = ApphelpCacheCreateBuffer(&pBuffer, &BufferSize); if (!NT_SUCCESS(Status)) { return Status; } Status = NtCreateKey(&hKey, STANDARD_RIGHTS_WRITE | KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS | KEY_SET_VALUE | KEY_CREATE_SUB_KEY, &objaAppcompatCacheKeyName, 0, NULL, REG_OPTION_NON_VOLATILE, &CreateDisposition); if (!NT_SUCCESS(Status)) { DBGPRINT((sdlError, "ApphelpCacheWrite", "Failed to create key 0x%lx\n", Status)); goto Cleanup; } Status = NtSetValueKey(hKey, &ustrAppcompatCacheValueName, 0, REG_BINARY, pBuffer, BufferSize); if (!NT_SUCCESS(Status)) { DBGPRINT((sdlError, "ApphelpCacheWrite", "Failed to create key 0x%lx\n", Status)); } NtClose(hKey); Cleanup: if (pBuffer != NULL) { SdbFree(pBuffer); } return Status; } NTSTATUS ApphelpCacheRead( VOID ) { // static UNICODE_STRING ustrAppcompatCacheKeyName = RTL_CONSTANT_STRING(APPCOMPAT_CACHE_KEY_NAME); static OBJECT_ATTRIBUTES objaAppcompatCacheKeyName = RTL_CONSTANT_OBJECT_ATTRIBUTES(&ustrAppcompatCacheKeyName, OBJ_CASE_INSENSITIVE); static UNICODE_STRING ustrAppcompatCacheValueName = RTL_CONSTANT_STRING(APPCOMPAT_CACHE_VALUE_NAME); HANDLE KeyHandle = NULL; KEY_VALUE_PARTIAL_INFORMATION KeyValuePartialInfo; PKEY_VALUE_PARTIAL_INFORMATION pKeyValueInfo = &KeyValuePartialInfo; ULONG KeyValueLength = 0; ULONG BufferSize = sizeof(KeyValuePartialInformation); NTSTATUS Status; Status = NtOpenKey(&KeyHandle, KEY_QUERY_VALUE, (POBJECT_ATTRIBUTES)&objaAppcompatCacheKeyName); if (!NT_SUCCESS(Status)) { return FALSE; } Status = NtQueryValueKey(KeyHandle, &ustrAppcompatCacheValueName, KeyValuePartialInformation, pKeyValueInfo, BufferSize, &KeyValueLength); if (!NT_SUCCESS(Status)) { if (Status != STATUS_BUFFER_TOO_SMALL) { DBGPRINT((sdlError, "ApphelpCacheRead", "NtQueryValueKey fails for \"%s\", status 0x%lx\n", ustrAppcompatCacheValueName.Buffer, Status)); goto Cleanup; } BufferSize = KeyValueLength; pKeyValueInfo = (PKEY_VALUE_PARTIAL_INFORMATION)SdbAlloc(BufferSize); if (pKeyValueInfo == NULL) { DBGPRINT((sdlError, "ApphelpCacheRead", "Failed to allocate cache buffer 0x%lx bytes\n", BufferSize)); Status = STATUS_NO_MEMORY; goto Cleanup; } Status = NtQueryValueKey(KeyHandle, &ustrAppcompatCacheValueName, KeyValuePartialInformation, pKeyValueInfo, BufferSize, &KeyValueLength); if (!NT_SUCCESS(Status)) { DBGPRINT((sdlError, "ApphelpCacheRead", "NtQueryValueKey fails(2) for \"%s\", status 0x%lx\n", ustrAppcompatCacheValueName.Buffer, Status)); goto Cleanup; } } if (pKeyValueInfo->Type != REG_BINARY) { DBGPRINT((sdlError, "ApphelpCacheRead", "Bad value type for apphelp cache 0x%lx\n", pKeyValueInfo->Type)); Status = STATUS_OBJECT_TYPE_MISMATCH; goto Cleanup; } // // Now go through the cache (flat part) and parse all the entries into the table. // Make sure that we are able to parse old entries (after system upgrade). // Status = ApphelpCacheParseBuffer((PVOID)pKeyValueInfo->Data, pKeyValueInfo->DataLength); Cleanup: if (pKeyValueInfo != &KeyValuePartialInfo) { SdbFree(pKeyValueInfo); } if (KeyHandle != NULL) { NtClose(KeyHandle); } return Status; } ////////////////////////////////////////////////////////////////////////////////////// // // Cache Initialization routine // ////////////////////////////////////////////////////////////////////////////////////// NTSTATUS ApphelpCacheInitialize( IN PLOADER_PARAMETER_BLOCK pLoaderBlock, IN ULONG BootPhase ) { NTSTATUS Status; // // Check for safe mode. // if (pLoaderBlock->LoadOptions != NULL && strstr(pLoaderBlock->LoadOptions, SAFEBOOT_LOAD_OPTION_A) != NULL) { g_bCacheEnabled = FALSE; return STATUS_SUCCESS; } // // Create the cache lock. // Status = ExInitializeResourceLite(&g_SharedLock); if (!NT_SUCCESS(Status)) { DBGPRINT((sdlError, "ApphelpCacheInitialize", "Failed to initialize the cache lock\n", Status)); g_bCacheEnabled = FALSE; return Status; } RtlInitializeGenericTableAvl(&g_ShimCache.Table, ApphelpCacheCompareEntries, ApphelpAVLTableAllocate, ApphelpAVLTableFree, NULL); InitializeListHead(&g_ShimCache.ListHead); // // Once we initialize - load the cache from the registry // Status = ApphelpCacheRead(); if (!NT_SUCCESS(Status)) { DBGPRINT((sdlError, "ApphelpCacheInitialize", "Failed to retrieve apphelp cache 0x%lx\n", Status)); } g_bCacheEnabled = TRUE; return STATUS_SUCCESS; UNREFERENCED_PARAMETER(pLoaderBlock); UNREFERENCED_PARAMETER(BootPhase); } NTSTATUS ApphelpCacheShutdown( IN ULONG ShutdownPhase ) { NTSTATUS Status = STATUS_SUCCESS; if (g_bCacheEnabled) { Status = ApphelpCacheWrite(); } return Status; UNREFERENCED_PARAMETER(ShutdownPhase); } ////////////////////////////////////////////////////////////////////////////////////// // // Locking // ////////////////////////////////////////////////////////////////////////////////////// NTSTATUS ApphelpCacheLockExclusiveNoWait( VOID ) { BOOLEAN bLock; NTSTATUS Status = STATUS_SUCCESS; KeEnterCriticalRegion(); bLock = ExTryToAcquireResourceExclusiveLite(&g_SharedLock); if (!bLock) { KeLeaveCriticalRegion(); DBGPRINT((sdlWarning, "ApphelpCacheLockExclusiveNoWait", "Failed to lock apphelp cache\n")); Status = STATUS_ACCESS_DENIED; } return Status; } NTSTATUS ApphelpCacheLockExclusive( VOID ) { BOOLEAN bLock; KeEnterCriticalRegion(); bLock = ExAcquireResourceExclusiveLite(&g_SharedLock, TRUE); ASSERT(bLock); return STATUS_SUCCESS; } NTSTATUS ApphelpCacheReleaseLock( VOID ) { ExReleaseResourceLite(&g_SharedLock); KeLeaveCriticalRegion(); return STATUS_SUCCESS; } ////////////////////////////////////////////////////////////////////////////////////// // // Routine to flush the cache // ////////////////////////////////////////////////////////////////////////////////////// NTSTATUS ApphelpCacheFlush( VOID ) { PWCHAR pBuffer; BOOL bDeleted; NTSTATUS Status; PSHIMCACHEENTRY pEntry; ULONG BufferLength; Status = ApphelpCacheLockExclusive(); if (!NT_SUCCESS(Status)) { return Status; } // // Enumerate the entries and remove them from the cache // for (pEntry = (PSHIMCACHEENTRY)RtlEnumerateGenericTableAvl(&g_ShimCache.Table, TRUE); pEntry != NULL; pEntry = (PSHIMCACHEENTRY)RtlEnumerateGenericTableAvl(&g_ShimCache.Table, TRUE)) { pBuffer = pEntry->FileName.Buffer; BufferLength = pEntry->FileName.MaximumLength; RemoveEntryList(&pEntry->ListEntry); bDeleted = RtlDeleteElementGenericTableAvl(&g_ShimCache.Table, pEntry); if (bDeleted) { // #if DBG RtlFillMemory(pBuffer, BufferLength, 'A'); // #endif SdbFree(pBuffer); } else { DBGPRINT((sdlError, "ApphelpCacheFlush", "Failed to remove the entry from cache %ls\n", pBuffer)); } } ASSERT(RtlIsGenericTableEmptyAvl(&g_ShimCache.Table)); ASSERT(IsListEmpty(&g_ShimCache.ListHead)); ApphelpCacheReleaseLock(); return STATUS_SUCCESS; } NTSTATUS ApphelpCacheDump( VOID ) { PLIST_ENTRY pListEntry; int nElement = 0; PSHIMCACHEENTRY pEntry; NTSTATUS Status; Status = ApphelpCacheLockExclusive(); if (!NT_SUCCESS(Status)) { return Status; } DBGPRINT((sdlInfo, "ApphelpCacheDump", "(LRU) (Name)\n")); pListEntry = g_ShimCache.ListHead.Flink; while (pListEntry != &g_ShimCache.ListHead) { pEntry = (PSHIMCACHEENTRY)pListEntry; ++nElement; DBGPRINT((sdlInfo, "ApphelpCacheDump", "%2d. %ls\n", nElement, pEntry->FileName.Buffer)); pListEntry = pListEntry->Flink; } DBGPRINT((sdlInfo, "ApphelpCacheDump", "----\n")); ApphelpCacheReleaseLock(); return STATUS_SUCCESS; } ////////////////////////////////////////////////////////////////////////////////////// // // Control function calls // ////////////////////////////////////////////////////////////////////////////////////// NTSTATUS ApphelpCacheControlValidateParameters( IN PAHCACHESERVICEDATA pServiceData, OUT PUNICODE_STRING pFileName, OUT HANDLE* pFileHandle ) { NTSTATUS Status; UNICODE_STRING FileName; if (pServiceData == NULL) { return STATUS_INVALID_PARAMETER; } RtlZeroMemory(pFileName, sizeof(*pFileName)); try { ProbeForRead(pServiceData, sizeof(AHCACHESERVICEDATA), sizeof(ULONG)); *pFileHandle = pServiceData->FileHandle; FileName = ProbeAndReadUnicodeString(&pServiceData->FileName); ProbeForRead(FileName.Buffer, FileName.Length, sizeof(UCHAR)); Status = ApphelpDuplicateUnicodeString(pFileName, &FileName); if (!NT_SUCCESS(Status)) { return Status; } } except(EXCEPTION_EXECUTE_HANDLER) { ApphelpFreeUnicodeString(pFileName); return GetExceptionCode(); } return STATUS_SUCCESS; } NTSYSCALLAPI NTSTATUS NtApphelpCacheControl( IN APPHELPCACHESERVICECLASS Service, IN OUT PVOID ServiceData ) { UNICODE_STRING FileName = { 0 }; HANDLE FileHandle = INVALID_HANDLE_VALUE; NTSTATUS Status; PAHCACHESERVICEDATA pServiceData = (PAHCACHESERVICEDATA)ServiceData; if (!g_bCacheEnabled) { return STATUS_INVALID_PARAMETER; } // // This is the parameter validation code // switch (Service) { case ApphelpCacheServiceLookup: // // Lookup the entry specified in ServiceData. // Potentially may remove an entry from the cache. // Status = ApphelpCacheControlValidateParameters(pServiceData, &FileName, &FileHandle); if (!NT_SUCCESS(Status)) { goto Done; } Status = ApphelpCacheLookupEntry(&FileName, FileHandle); break; case ApphelpCacheServiceUpdate: Status = ApphelpCacheVerifyContext(); if (!NT_SUCCESS(Status)) { goto Done; } Status = ApphelpCacheControlValidateParameters(pServiceData, &FileName, &FileHandle); if (!NT_SUCCESS(Status)) { goto Done; } Status = ApphelpCacheInsertEntry(&FileName, FileHandle); break; case ApphelpCacheServiceRemove: Status = ApphelpCacheControlValidateParameters(pServiceData, &FileName, &FileHandle); if (!NT_SUCCESS(Status)) { goto Done; } Status = ApphelpCacheRemoveEntry(&FileName); break; case ApphelpCacheServiceFlush: Status = ApphelpCacheFlush(); break; case ApphelpCacheServiceDump: #if DBG Status = ApphelpCacheDump(); #else Status = STATUS_SUCCESS; #endif break; default: Status = STATUS_INVALID_PARAMETER; break; } Done: ApphelpFreeUnicodeString(&FileName); return Status; }