// -------------------------------------------------------------------------- // Module Name: ThemeServer.cpp // // Copyright (c) 2000, Microsoft Corporation // // Functions that implement server functionality. Functions in this file // cannot execute per instance win32k functions that are done on the client's // behalf. That work must be done on the client side. // // History: 2000-11-11 vtan created // -------------------------------------------------------------------------- #include "stdafx.h" #include "ThemeServer.h" #include "uxthemeserver.h" #include #include "Loader.h" #include "Signing.h" #include "ThemeSection.h" #include "TmUtils.h" #include "sethook.h" #include "log.h" #include "services.h" #define TBOOL(x) ((BOOL)(x)) #define TW32(x) ((DWORD)(x)) #define THR(x) ((HRESULT)(x)) #define TSTATUS(x) ((NTSTATUS)(x)) #define goto !!DO NOT USE GOTO!! - DO NOT REMOVE THIS ON PAIN OF DEATH // -------------------------------------------------------------------------- // CThemeServer::CThemeServer // // Arguments: hProcessRegisterHook = Process used to install hooks. // dwServerChangeNumber = Server change number. // pfnRegister = Address of install hook function. // pfnUnregister = Address of remove hook function. // pfnClearStockObjects = Address of function to remove stock objects from section // // Returns: // // Purpose: Constructor for CThemeServer. Initializes member variables // with information relevant for the session. Keeps a handle to // the process that called this to use to inject threads in to // handle hook installation and removal. // // History: 2000-11-11 vtan created // -------------------------------------------------------------------------- CThemeServer::CThemeServer (HANDLE hProcessRegisterHook, DWORD dwServerChangeNumber, void *pfnRegister, void *pfnUnregister, void *pfnClearStockObjects, DWORD dwStackSizeReserve, DWORD dwStackSizeCommit) : _hProcessRegisterHook(NULL), _dwServerChangeNumber(dwServerChangeNumber), _pfnRegister(pfnRegister), _pfnUnregister(pfnUnregister), _pfnClearStockObjects(pfnClearStockObjects), _dwStackSizeReserve(dwStackSizeReserve), _dwStackSizeCommit(dwStackSizeCommit), _dwSessionID(NtCurrentPeb()->SessionId), _fHostHooksSet(false), _hSectionGlobalTheme(NULL), _dwClientChangeNumber(0) { ULONG ulReturnLength; PROCESS_SESSION_INFORMATION processSessionInformation; ZeroMemory(&_lock, sizeof(_lock)); if( !InitializeCriticalSectionAndSpinCount(&_lock, 0) ) { ASSERT(!VALID_CRITICALSECTION(&_lock)); } TBOOL(DuplicateHandle(GetCurrentProcess(), hProcessRegisterHook, GetCurrentProcess(), &_hProcessRegisterHook, 0, FALSE, DUPLICATE_SAME_ACCESS)); if (NT_SUCCESS(NtQueryInformationProcess(hProcessRegisterHook, ProcessSessionInformation, &processSessionInformation, sizeof(processSessionInformation), &ulReturnLength))) { _dwSessionID = processSessionInformation.SessionId; } } // -------------------------------------------------------------------------- // CThemeServer::~CThemeServer // // Arguments: // // Returns: // // Purpose: Destructor for CThemeServer. Releases resources used. // // History: 2000-11-11 vtan created // -------------------------------------------------------------------------- CThemeServer::~CThemeServer (void) { //---- important: turn off hooks so everyone gets unthemed ---- if (!GetSystemMetrics(SM_SHUTTINGDOWN)) // Don't do this on shutdown to keep winlogon themed { ThemeHooksOff(); } //---- mark global theme invalid & release it ---- if (_hSectionGlobalTheme != NULL) { SetGlobalTheme(NULL); } if (_hProcessRegisterHook != NULL) { TBOOL(CloseHandle(_hProcessRegisterHook)); _hProcessRegisterHook = NULL; } SAFE_DELETECRITICALSECTION(&_lock); } // -------------------------------------------------------------------------- // CThemeServer::ThemeHooksOn // // Arguments: // // Returns: HRESULT // // Purpose: Install theme hooks via the session controlling process. // // History: 2000-11-11 vtan created // -------------------------------------------------------------------------- HRESULT CThemeServer::ThemeHooksOn (void) { HRESULT hr; LockAcquire(); if (!_fHostHooksSet) { if (ClassicGetSystemMetrics(SM_CLEANBOOT) == 0) { if (_hProcessRegisterHook != NULL) { hr = InjectClientSessionThread(_hProcessRegisterHook, FunctionRegisterUserApiHook, NULL); _fHostHooksSet = SUCCEEDED(hr); } else { hr = MakeError32(ERROR_SERVICE_REQUEST_TIMEOUT); } } else { hr = MakeError32(ERROR_BAD_ENVIRONMENT); // themes not allowed in safe mode } } else { hr = S_OK; } LockRelease(); return(hr); } // -------------------------------------------------------------------------- // CThemeServer::ThemeHooksOff // // Arguments: // // Returns: HRESULT // // Purpose: Remove theme hooks via the session controlling process. // // History: 2000-11-11 vtan created // -------------------------------------------------------------------------- HRESULT CThemeServer::ThemeHooksOff (void) { LockAcquire(); if (_fHostHooksSet) { _fHostHooksSet = false; if (_hProcessRegisterHook != NULL) { THR(InjectClientSessionThread(_hProcessRegisterHook, FunctionUnregisterUserApiHook, NULL)); } } LockRelease(); return(S_OK); } // -------------------------------------------------------------------------- // CThemeServer::AreThemeHooksActive // // Arguments: // // Returns: bool // // Purpose: Returns whether theme hooks have been successfully installed. // // History: 2000-11-11 vtan created // -------------------------------------------------------------------------- bool CThemeServer::AreThemeHooksActive (void) { bool fResult; LockAcquire(); fResult = _fHostHooksSet; LockRelease(); return(fResult); } // -------------------------------------------------------------------------- // CThemeServer::GetCurrentChangeNumber // // Arguments: // // Returns: int // // Purpose: Returns the current change number. // // History: 2000-11-11 vtan created // -------------------------------------------------------------------------- int CThemeServer::GetCurrentChangeNumber (void) { int iCurrentChangeNumber; LockAcquire(); iCurrentChangeNumber = static_cast((_dwServerChangeNumber << 16) | _dwClientChangeNumber); LockRelease(); Log(LOG_TMCHANGE, L"GetCurrentChangeNumber: server: %d, client: %d, change: 0x%x", _dwServerChangeNumber, _dwClientChangeNumber, iCurrentChangeNumber); return(iCurrentChangeNumber); } // -------------------------------------------------------------------------- // CThemeServer::GetNewChangeNumber // // Arguments: // // Returns: int // // Purpose: Returns a new change number. // // History: 2000-11-11 vtan created // -------------------------------------------------------------------------- int CThemeServer::GetNewChangeNumber (void) { int iCurrentChangeNumber; LockAcquire(); _dwClientChangeNumber = static_cast(_dwClientChangeNumber + 1); iCurrentChangeNumber = static_cast((_dwServerChangeNumber << 16) | _dwClientChangeNumber); Log(LOG_TMLOAD, L"GetNewChangeNumber: server: %d, client: %d, change: 0x%x", _dwServerChangeNumber, _dwClientChangeNumber, iCurrentChangeNumber); LockRelease(); return(iCurrentChangeNumber); } // -------------------------------------------------------------------------- // CThemeServer::SetGlobalTheme // // Arguments: hSection = Handle to section of new theme. // // Returns: HRESULT // // Purpose: Invalidates the old section and closes the handle to it. // Validates the new section and if valid sets it as the global // theme. // // History: 2000-11-11 vtan created // -------------------------------------------------------------------------- HRESULT CThemeServer::SetGlobalTheme (HANDLE hSection) { HRESULT hr; LockAcquire(); if (_hSectionGlobalTheme != NULL) { void *pV; HANDLE hSemaphore = NULL; // Before closing the section invalidate it. pV = MapViewOfFile(_hSectionGlobalTheme, FILE_MAP_WRITE, 0, 0, 0); #ifdef DEBUG if (LogOptionOn(LO_TMLOAD)) { // Unexpected failure ASSERT(pV != NULL); } #endif if (pV != NULL) { // Create a semaphore so that no client will try to clean it before us, which would result in a double free // (as soon as we clear SECTION_GLOBAL, various CUxThemeFile destructors can call ClearStockObjects) WCHAR szName[64]; StringCchPrintfW(szName, ARRAYSIZE(szName), L"Global\\ClearStockGlobal%d-%d", reinterpret_cast(pV)->iLoadId, _dwSessionID); hSemaphore = CreateSemaphore(NULL, 0, 1, szName); Log(LOG_TMLOAD, L"SetGlobalTheme clearing section %d, semaphore=%s, hSemaphore=%X, gle=%d", reinterpret_cast(pV)->iLoadId, szName, hSemaphore, GetLastError()); reinterpret_cast(pV)->dwFlags &= ~(SECTION_READY | SECTION_GLOBAL); } HANDLE hSectionForInjection = NULL; //---- create a handle for CLIENT process to use to clear stock bitmaps ---- if (DuplicateHandle(GetCurrentProcess(), _hSectionGlobalTheme, _hProcessRegisterHook, &hSectionForInjection, FILE_MAP_READ, FALSE, 0) != FALSE) { // This will close the handle THR(InjectClientSessionThread(_hProcessRegisterHook, FunctionClearStockObjects, hSectionForInjection)); } if (pV != NULL) { reinterpret_cast(pV)->dwFlags &= ~SECTION_HASSTOCKOBJECTS; if (hSemaphore != NULL) { CloseHandle(hSemaphore); } TBOOL(UnmapViewOfFile(pV)); } TBOOL(CloseHandle(_hSectionGlobalTheme)); _hSectionGlobalTheme = NULL; } if (hSection != NULL) { CThemeSection themeSection; hr = themeSection.Open(hSection); if (SUCCEEDED(hr)) { hr = themeSection.ValidateData(true); } if (SUCCEEDED(hr)) { if (DuplicateHandle(GetCurrentProcess(), hSection, GetCurrentProcess(), &_hSectionGlobalTheme, FILE_MAP_ALL_ACCESS, FALSE, 0) != FALSE) { hr = S_OK; } else { hr = MakeErrorLast(); } } } else { hr = S_OK; } if (SUCCEEDED(hr)) { //---- bump the change number at the same time so everything is in sync. ---- int iChangeNum = GetNewChangeNumber(); if (_hSectionGlobalTheme) { //---- put changenum into theme hdr to help client keep things straight ---- VOID *pv = MapViewOfFile(_hSectionGlobalTheme, FILE_MAP_WRITE, 0, 0, 0); if (pv != NULL) { reinterpret_cast(pv)->dwFlags |= SECTION_GLOBAL; reinterpret_cast(pv)->iLoadId = iChangeNum; Log(LOG_TMLOAD, L"SetGlobalTheme: new section is %d", reinterpret_cast(pv)->iLoadId); TBOOL(UnmapViewOfFile(pv)); } } } LockRelease(); return(hr); } // -------------------------------------------------------------------------- // CThemeServer::GetGlobalTheme // // Arguments: phSection = Handle to the section received. // // Returns: HRESULT // // Purpose: Duplicates the section back to the caller. // // History: 2000-11-11 vtan created // -------------------------------------------------------------------------- HRESULT CThemeServer::GetGlobalTheme (HANDLE *phSection) { HRESULT hr; LockAcquire(); *phSection = NULL; if (_hSectionGlobalTheme != NULL) { if (DuplicateHandle(GetCurrentProcess(), _hSectionGlobalTheme, GetCurrentProcess(), phSection, 0, FALSE, DUPLICATE_SAME_ACCESS) != FALSE) { hr = S_OK; } else { hr = MakeErrorLast(); } } else { hr = S_OK; } LockRelease(); return(hr); } // -------------------------------------------------------------------------- // CThemeServer::LoadTheme // // Arguments: hSection = Section created by the client. // phSection = Section created by the server returned. // pszName = Theme name. // pszColor = Theme size. // pszSize = Theme color. // fOwnStockObjects = TRUE to clear the stock objects bit on the // incoming section, on success, thereby taking // full responsibility for SO cleanup. // // Returns: HRESULT // // Purpose: Creates a new section in the server context based on the // section from the client. The contents are transferred across. // The section contents are also strictly verified. // // History: 2000-11-11 vtan created // 2002-03-21 scotthan Update incoming section header to // indicate change of ownership. // -------------------------------------------------------------------------- HRESULT CThemeServer::LoadTheme ( HANDLE hSection, HANDLE *phSection, LPCWSTR pszName, LPCWSTR pszColor, LPCWSTR pszSize, OPTIONAL DWORD dwFlags) { HRESULT hr; hr = CheckThemeSignature(pszName); // Check this is signed if (SUCCEEDED(hr)) { CThemeSection themeSectionIn; if (SUCCEEDED(themeSectionIn.Open(hSection, FILE_MAP_READ|FILE_MAP_WRITE))) { if (ThemeMatch(themeSectionIn, pszName, pszColor, pszSize, 0) != FALSE) { hr = themeSectionIn.ValidateData(true); if (SUCCEEDED(hr)) { CThemeSection themeSectionOut; // Only privileged clients can create a section with GDI stock objects if( 0 == (((THEMEHDR*)themeSectionIn.GetData())->dwFlags & SECTION_HASSTOCKOBJECTS) || 0 != (dwFlags & LTF_GLOBALPRIVILEGEDCLIENT) ) { // Note: we come here impersonating the user, we need for ThemeMatch. // However the theme section must be created in the system context, so that only // the system context has write access to it. We revert to self here based on the // knowledge that nothing after this call needs to be done in the user context. RevertToSelf(); // make sure that we're TCB svchost. Nobody else should be able to do this in-proc. if( !TokenHasPrivilege( NULL, SE_TCB_PRIVILEGE ) ) { hr = E_ACCESSDENIED; } else { hr = themeSectionOut.CreateFromSection(hSection); if (SUCCEEDED(hr)) { *phSection = themeSectionOut.Get(); // We now own the handle if( (dwFlags & LTF_TRANSFERSTOCKOBJOWNERSHIP) != 0 ) { // We're transfering ownership of stock bitmaps, if any, to the output (read-only) // section; this simply means removing the SECTION_HASSTOCKOBJECTS bit from the // incoming section so that the client doesn't attempt to clear them. ((THEMEHDR*)themeSectionIn.GetData())->dwFlags &= ~SECTION_HASSTOCKOBJECTS; } } } } else { hr = E_ACCESSDENIED; } } } else { hr = E_ACCESSDENIED; } } } return(hr); } // -------------------------------------------------------------------------- // CThemeServer::IsSystemProcessContext // // Arguments: // // Returns: bool // // Purpose: Is the current process executing in the SYSTEM context? // // History: 2000-11-11 vtan created // -------------------------------------------------------------------------- bool CThemeServer::IsSystemProcessContext (void) { bool fResult; HANDLE hToken; fResult = false; if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken) != FALSE) { static const LUID sLUIDSystem = SYSTEM_LUID; ULONG ulReturnLength; TOKEN_STATISTICS tokenStatistics; fResult = ((GetTokenInformation(hToken, TokenStatistics, &tokenStatistics, sizeof(tokenStatistics), &ulReturnLength) != FALSE) && RtlEqualLuid(&tokenStatistics.AuthenticationId, &sLUIDSystem)); TBOOL(CloseHandle(hToken)); } return(fResult); } // -------------------------------------------------------------------------- // CThemeServer::ThemeHooksInstall // // Arguments: // // Returns: DWORD // // Purpose: Thread entry point for injected thread running in session // creating process' context to call user32!RegisterUserApiHook. // // History: 2000-11-11 vtan created // -------------------------------------------------------------------------- DWORD CThemeServer::ThemeHooksInstall (void) { DWORD dwErrorCode; if (IsSystemProcessContext()) { INITUSERAPIHOOK pfnInitUserApiHook = ThemeInitApiHook; if (RegisterUserApiHook(g_hInst, pfnInitUserApiHook) != FALSE) { dwErrorCode = ERROR_SUCCESS; } else { dwErrorCode = GetLastError(); } } else { dwErrorCode = ERROR_ACCESS_DENIED; } return(dwErrorCode); } // -------------------------------------------------------------------------- // CThemeServer::ThemeHooksRemove // // Arguments: // // Returns: DWORD // // Purpose: Thread entry point for injected thread running in session // creating process' context to call // user32!UnregisterUserApiHook. // // History: 2000-11-11 vtan created // -------------------------------------------------------------------------- DWORD CThemeServer::ThemeHooksRemove (void) { DWORD dwErrorCode; if (IsSystemProcessContext()) { if (UnregisterUserApiHook() != FALSE) { dwErrorCode = ERROR_SUCCESS; Log(LOG_TMLOAD, L"UnregisterUserApiHook() called"); } else { dwErrorCode = GetLastError(); } } else { dwErrorCode = ERROR_ACCESS_DENIED; } return(dwErrorCode); } // -------------------------------------------------------------------------- // CThemeServer::ClearStockObjects // // Arguments: HANDLE hSection // // Returns: DWORD // // Purpose: Thread entry point for injected thread running in session // creating process' context to clear stock objects in theme // section. // // // History: 2001-05-01 rfernand created // -------------------------------------------------------------------------- DWORD CThemeServer::ClearStockObjects (HANDLE hSection) { DWORD dwErrorCode = ERROR_SUCCESS; if (IsSystemProcessContext()) { if (hSection) { //---- Clearing the stock bitmaps in the section ---- //---- is OK here since we are running in the context ---- //---- of the current USER session ---- HRESULT hr = ClearTheme(hSection, TRUE); if (FAILED(hr)) { Log(LOG_ALWAYS, L"ClearTheme() failed, hr=0x%x", hr); hr = S_OK; // not a fatal error } } } else { dwErrorCode = ERROR_ACCESS_DENIED; } return(dwErrorCode); } // -------------------------------------------------------------------------- // CThemeServer::LockAcquire // // Arguments: // // Returns: // // Purpose: Acquires the object critical section. // // History: 2000-11-11 vtan created // -------------------------------------------------------------------------- void CThemeServer::LockAcquire (void) { SAFE_ENTERCRITICALSECTION(&_lock); } // -------------------------------------------------------------------------- // CThemeServer::LockRelease // // Arguments: // // Returns: // // Purpose: Releases the object critical section. // // History: 2000-11-11 vtan created // -------------------------------------------------------------------------- void CThemeServer::LockRelease (void) { SAFE_LEAVECRITICALSECTION(&_lock); } // -------------------------------------------------------------------------- // CThemeServer::InjectStockObjectCleanupThread // // Arguments: hSection = Handle of section to clean/clear // // Returns: HRESULT // // Purpose: Permits server to inject a thread into the process // in which stock object handles can be released. // // History: 2002-03-11 scotthan created // -------------------------------------------------------------------------- HRESULT CThemeServer::InjectStockObjectCleanupThread(HANDLE hSection) { HANDLE hSectionClean; //---- create a handle for target process to use to clear stock bitmaps ---- if (!DuplicateHandle(GetCurrentProcess(), hSection, _hProcessRegisterHook, &hSectionClean, FILE_MAP_READ, FALSE, 0)) { return MakeErrorLast(); } // This will close the duplicate we just created, but not the inbound handle BOOL fThreadCreated; HRESULT hr = InjectClientSessionThread(_hProcessRegisterHook, FunctionClearStockObjects, hSectionClean, &fThreadCreated); if( !fThreadCreated ) { CloseHandle(hSectionClean); } return hr; } // -------------------------------------------------------------------------- // CThemeServer::InjectClientSessionThread // // Arguments: hProcess = Handle to process to inject thread in. // iIndexFunction = Function to call on injected thread. // pvParam = Ptr to param for entry function // // Returns: HRESULT // // Purpose: Create a user mode thread in the remote process (possibly // across sessions) and execute the entry point specified at // object construction which is valid in the remote process // context. Wait for the thread to finish. It will signal its // success of failure in the exit code. // // History: 2000-11-11 vtan created // 2001-05-18 vtan generic function index // -------------------------------------------------------------------------- HRESULT CThemeServer::InjectClientSessionThread ( HANDLE hProcess, int iIndexFunction, void *pvParam, OUT OPTIONAL BOOL* pfThreadCreated ) { HRESULT hr; PUSER_THREAD_START_ROUTINE pfnThreadStart; if( pfThreadCreated ) *pfThreadCreated = FALSE; switch (iIndexFunction) { case FunctionRegisterUserApiHook: pfnThreadStart = reinterpret_cast(_pfnRegister); break; case FunctionUnregisterUserApiHook: pfnThreadStart = reinterpret_cast(_pfnUnregister); break; case FunctionClearStockObjects: pfnThreadStart = reinterpret_cast(_pfnClearStockObjects); break; default: pfnThreadStart = NULL; break; } if (pfnThreadStart != NULL) { NTSTATUS status; HANDLE hThread; status = RtlCreateUserThread(hProcess, NULL, FALSE, 0, _dwStackSizeReserve, _dwStackSizeCommit, pfnThreadStart, pvParam, &hThread, NULL); if (NT_SUCCESS(status)) { DWORD dwWaitResult, dwExitCode; if( pfThreadCreated ) *pfThreadCreated = TRUE; dwWaitResult = WaitForSingleObject(hThread, INFINITE); if (GetExitCodeThread(hThread, &dwExitCode) != FALSE) { hr = HRESULT_FROM_WIN32(dwExitCode); } else { hr = E_FAIL; } TBOOL(CloseHandle(hThread)); } else { hr = HRESULT_FROM_NT(status); } } else { hr = E_FAIL; } return(hr); }