michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include michael@0: #include michael@0: #include "uachelper.h" michael@0: #include "updatelogging.h" michael@0: michael@0: // See the MSDN documentation with title: Privilege Constants michael@0: // At the time of this writing, this documentation is located at: michael@0: // http://msdn.microsoft.com/en-us/library/windows/desktop/bb530716%28v=vs.85%29.aspx michael@0: LPCTSTR UACHelper::PrivsToDisable[] = { michael@0: SE_ASSIGNPRIMARYTOKEN_NAME, michael@0: SE_AUDIT_NAME, michael@0: SE_BACKUP_NAME, michael@0: // CreateProcess will succeed but the app will fail to launch on some WinXP michael@0: // machines if SE_CHANGE_NOTIFY_NAME is disabled. In particular this happens michael@0: // for limited user accounts on those machines. The define is kept here as a michael@0: // reminder that it should never be re-added. michael@0: // This permission is for directory watching but also from MSDN: "This michael@0: // privilege also causes the system to skip all traversal access checks." michael@0: // SE_CHANGE_NOTIFY_NAME, michael@0: SE_CREATE_GLOBAL_NAME, michael@0: SE_CREATE_PAGEFILE_NAME, michael@0: SE_CREATE_PERMANENT_NAME, michael@0: SE_CREATE_SYMBOLIC_LINK_NAME, michael@0: SE_CREATE_TOKEN_NAME, michael@0: SE_DEBUG_NAME, michael@0: SE_ENABLE_DELEGATION_NAME, michael@0: SE_IMPERSONATE_NAME, michael@0: SE_INC_BASE_PRIORITY_NAME, michael@0: SE_INCREASE_QUOTA_NAME, michael@0: SE_INC_WORKING_SET_NAME, michael@0: SE_LOAD_DRIVER_NAME, michael@0: SE_LOCK_MEMORY_NAME, michael@0: SE_MACHINE_ACCOUNT_NAME, michael@0: SE_MANAGE_VOLUME_NAME, michael@0: SE_PROF_SINGLE_PROCESS_NAME, michael@0: SE_RELABEL_NAME, michael@0: SE_REMOTE_SHUTDOWN_NAME, michael@0: SE_RESTORE_NAME, michael@0: SE_SECURITY_NAME, michael@0: SE_SHUTDOWN_NAME, michael@0: SE_SYNC_AGENT_NAME, michael@0: SE_SYSTEM_ENVIRONMENT_NAME, michael@0: SE_SYSTEM_PROFILE_NAME, michael@0: SE_SYSTEMTIME_NAME, michael@0: SE_TAKE_OWNERSHIP_NAME, michael@0: SE_TCB_NAME, michael@0: SE_TIME_ZONE_NAME, michael@0: SE_TRUSTED_CREDMAN_ACCESS_NAME, michael@0: SE_UNDOCK_NAME, michael@0: SE_UNSOLICITED_INPUT_NAME michael@0: }; michael@0: michael@0: /** michael@0: * Opens a user token for the given session ID michael@0: * michael@0: * @param sessionID The session ID for the token to obtain michael@0: * @return A handle to the token to obtain which will be primary if enough michael@0: * permissions exist. Caller should close the handle. michael@0: */ michael@0: HANDLE michael@0: UACHelper::OpenUserToken(DWORD sessionID) michael@0: { michael@0: HMODULE module = LoadLibraryW(L"wtsapi32.dll"); michael@0: HANDLE token = nullptr; michael@0: decltype(WTSQueryUserToken)* wtsQueryUserToken = michael@0: (decltype(WTSQueryUserToken)*) GetProcAddress(module, "WTSQueryUserToken"); michael@0: if (wtsQueryUserToken) { michael@0: wtsQueryUserToken(sessionID, &token); michael@0: } michael@0: FreeLibrary(module); michael@0: return token; michael@0: } michael@0: michael@0: /** michael@0: * Opens a linked token for the specified token. michael@0: * michael@0: * @param token The token to get the linked token from michael@0: * @return A linked token or nullptr if one does not exist. michael@0: * Caller should close the handle. michael@0: */ michael@0: HANDLE michael@0: UACHelper::OpenLinkedToken(HANDLE token) michael@0: { michael@0: // Magic below... michael@0: // UAC creates 2 tokens. One is the restricted token which we have. michael@0: // the other is the UAC elevated one. Since we are running as a service michael@0: // as the system account we have access to both. michael@0: TOKEN_LINKED_TOKEN tlt; michael@0: HANDLE hNewLinkedToken = nullptr; michael@0: DWORD len; michael@0: if (GetTokenInformation(token, (TOKEN_INFORMATION_CLASS)TokenLinkedToken, michael@0: &tlt, sizeof(TOKEN_LINKED_TOKEN), &len)) { michael@0: token = tlt.LinkedToken; michael@0: hNewLinkedToken = token; michael@0: } michael@0: return hNewLinkedToken; michael@0: } michael@0: michael@0: michael@0: /** michael@0: * Enables or disables a privilege for the specified token. michael@0: * michael@0: * @param token The token to adjust the privilege on. michael@0: * @param priv The privilege to adjust. michael@0: * @param enable Whether to enable or disable it michael@0: * @return TRUE if the token was adjusted to the specified value. michael@0: */ michael@0: BOOL michael@0: UACHelper::SetPrivilege(HANDLE token, LPCTSTR priv, BOOL enable) michael@0: { michael@0: LUID luidOfPriv; michael@0: if (!LookupPrivilegeValue(nullptr, priv, &luidOfPriv)) { michael@0: return FALSE; michael@0: } michael@0: michael@0: TOKEN_PRIVILEGES tokenPriv; michael@0: tokenPriv.PrivilegeCount = 1; michael@0: tokenPriv.Privileges[0].Luid = luidOfPriv; michael@0: tokenPriv.Privileges[0].Attributes = enable ? SE_PRIVILEGE_ENABLED : 0; michael@0: michael@0: SetLastError(ERROR_SUCCESS); michael@0: if (!AdjustTokenPrivileges(token, false, &tokenPriv, michael@0: sizeof(tokenPriv), nullptr, nullptr)) { michael@0: return FALSE; michael@0: } michael@0: michael@0: return GetLastError() == ERROR_SUCCESS; michael@0: } michael@0: michael@0: /** michael@0: * For each privilege that is specified, an attempt will be made to michael@0: * drop the privilege. michael@0: * michael@0: * @param token The token to adjust the privilege on. michael@0: * Pass nullptr for current token. michael@0: * @param unneededPrivs An array of unneeded privileges. michael@0: * @param count The size of the array michael@0: * @return TRUE if there were no errors michael@0: */ michael@0: BOOL michael@0: UACHelper::DisableUnneededPrivileges(HANDLE token, michael@0: LPCTSTR *unneededPrivs, michael@0: size_t count) michael@0: { michael@0: HANDLE obtainedToken = nullptr; michael@0: if (!token) { michael@0: // Note: This handle is a pseudo-handle and need not be closed michael@0: HANDLE process = GetCurrentProcess(); michael@0: if (!OpenProcessToken(process, TOKEN_ALL_ACCESS_P, &obtainedToken)) { michael@0: LOG_WARN(("Could not obtain token for current process, no " michael@0: "privileges changed. (%d)", GetLastError())); michael@0: return FALSE; michael@0: } michael@0: token = obtainedToken; michael@0: } michael@0: michael@0: BOOL result = TRUE; michael@0: for (size_t i = 0; i < count; i++) { michael@0: if (SetPrivilege(token, unneededPrivs[i], FALSE)) { michael@0: LOG(("Disabled unneeded token privilege: %s.", michael@0: unneededPrivs[i])); michael@0: } else { michael@0: LOG(("Could not disable token privilege value: %s. (%d)", michael@0: unneededPrivs[i], GetLastError())); michael@0: result = FALSE; michael@0: } michael@0: } michael@0: michael@0: if (obtainedToken) { michael@0: CloseHandle(obtainedToken); michael@0: } michael@0: return result; michael@0: } michael@0: michael@0: /** michael@0: * Disables privileges for the specified token. michael@0: * The privileges to disable are in PrivsToDisable. michael@0: * In the future there could be new privs and we are not sure if we should michael@0: * explicitly disable these or not. michael@0: * michael@0: * @param token The token to drop the privilege on. michael@0: * Pass nullptr for current token. michael@0: * @return TRUE if there were no errors michael@0: */ michael@0: BOOL michael@0: UACHelper::DisablePrivileges(HANDLE token) michael@0: { michael@0: static const size_t PrivsToDisableSize = michael@0: sizeof(UACHelper::PrivsToDisable) / sizeof(UACHelper::PrivsToDisable[0]); michael@0: michael@0: return DisableUnneededPrivileges(token, UACHelper::PrivsToDisable, michael@0: PrivsToDisableSize); michael@0: } michael@0: michael@0: /** michael@0: * Check if the current user can elevate. michael@0: * michael@0: * @return true if the user can elevate. michael@0: * false otherwise. michael@0: */ michael@0: bool michael@0: UACHelper::CanUserElevate() michael@0: { michael@0: HANDLE token; michael@0: if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) { michael@0: return false; michael@0: } michael@0: michael@0: TOKEN_ELEVATION_TYPE elevationType; michael@0: DWORD len; michael@0: bool canElevate = GetTokenInformation(token, TokenElevationType, michael@0: &elevationType, michael@0: sizeof(elevationType), &len) && michael@0: (elevationType == TokenElevationTypeLimited); michael@0: CloseHandle(token); michael@0: michael@0: return canElevate; michael@0: }