Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 3 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | #include <windows.h> |
michael@0 | 6 | #include <wtsapi32.h> |
michael@0 | 7 | #include "uachelper.h" |
michael@0 | 8 | #include "updatelogging.h" |
michael@0 | 9 | |
michael@0 | 10 | // See the MSDN documentation with title: Privilege Constants |
michael@0 | 11 | // At the time of this writing, this documentation is located at: |
michael@0 | 12 | // http://msdn.microsoft.com/en-us/library/windows/desktop/bb530716%28v=vs.85%29.aspx |
michael@0 | 13 | LPCTSTR UACHelper::PrivsToDisable[] = { |
michael@0 | 14 | SE_ASSIGNPRIMARYTOKEN_NAME, |
michael@0 | 15 | SE_AUDIT_NAME, |
michael@0 | 16 | SE_BACKUP_NAME, |
michael@0 | 17 | // CreateProcess will succeed but the app will fail to launch on some WinXP |
michael@0 | 18 | // machines if SE_CHANGE_NOTIFY_NAME is disabled. In particular this happens |
michael@0 | 19 | // for limited user accounts on those machines. The define is kept here as a |
michael@0 | 20 | // reminder that it should never be re-added. |
michael@0 | 21 | // This permission is for directory watching but also from MSDN: "This |
michael@0 | 22 | // privilege also causes the system to skip all traversal access checks." |
michael@0 | 23 | // SE_CHANGE_NOTIFY_NAME, |
michael@0 | 24 | SE_CREATE_GLOBAL_NAME, |
michael@0 | 25 | SE_CREATE_PAGEFILE_NAME, |
michael@0 | 26 | SE_CREATE_PERMANENT_NAME, |
michael@0 | 27 | SE_CREATE_SYMBOLIC_LINK_NAME, |
michael@0 | 28 | SE_CREATE_TOKEN_NAME, |
michael@0 | 29 | SE_DEBUG_NAME, |
michael@0 | 30 | SE_ENABLE_DELEGATION_NAME, |
michael@0 | 31 | SE_IMPERSONATE_NAME, |
michael@0 | 32 | SE_INC_BASE_PRIORITY_NAME, |
michael@0 | 33 | SE_INCREASE_QUOTA_NAME, |
michael@0 | 34 | SE_INC_WORKING_SET_NAME, |
michael@0 | 35 | SE_LOAD_DRIVER_NAME, |
michael@0 | 36 | SE_LOCK_MEMORY_NAME, |
michael@0 | 37 | SE_MACHINE_ACCOUNT_NAME, |
michael@0 | 38 | SE_MANAGE_VOLUME_NAME, |
michael@0 | 39 | SE_PROF_SINGLE_PROCESS_NAME, |
michael@0 | 40 | SE_RELABEL_NAME, |
michael@0 | 41 | SE_REMOTE_SHUTDOWN_NAME, |
michael@0 | 42 | SE_RESTORE_NAME, |
michael@0 | 43 | SE_SECURITY_NAME, |
michael@0 | 44 | SE_SHUTDOWN_NAME, |
michael@0 | 45 | SE_SYNC_AGENT_NAME, |
michael@0 | 46 | SE_SYSTEM_ENVIRONMENT_NAME, |
michael@0 | 47 | SE_SYSTEM_PROFILE_NAME, |
michael@0 | 48 | SE_SYSTEMTIME_NAME, |
michael@0 | 49 | SE_TAKE_OWNERSHIP_NAME, |
michael@0 | 50 | SE_TCB_NAME, |
michael@0 | 51 | SE_TIME_ZONE_NAME, |
michael@0 | 52 | SE_TRUSTED_CREDMAN_ACCESS_NAME, |
michael@0 | 53 | SE_UNDOCK_NAME, |
michael@0 | 54 | SE_UNSOLICITED_INPUT_NAME |
michael@0 | 55 | }; |
michael@0 | 56 | |
michael@0 | 57 | /** |
michael@0 | 58 | * Opens a user token for the given session ID |
michael@0 | 59 | * |
michael@0 | 60 | * @param sessionID The session ID for the token to obtain |
michael@0 | 61 | * @return A handle to the token to obtain which will be primary if enough |
michael@0 | 62 | * permissions exist. Caller should close the handle. |
michael@0 | 63 | */ |
michael@0 | 64 | HANDLE |
michael@0 | 65 | UACHelper::OpenUserToken(DWORD sessionID) |
michael@0 | 66 | { |
michael@0 | 67 | HMODULE module = LoadLibraryW(L"wtsapi32.dll"); |
michael@0 | 68 | HANDLE token = nullptr; |
michael@0 | 69 | decltype(WTSQueryUserToken)* wtsQueryUserToken = |
michael@0 | 70 | (decltype(WTSQueryUserToken)*) GetProcAddress(module, "WTSQueryUserToken"); |
michael@0 | 71 | if (wtsQueryUserToken) { |
michael@0 | 72 | wtsQueryUserToken(sessionID, &token); |
michael@0 | 73 | } |
michael@0 | 74 | FreeLibrary(module); |
michael@0 | 75 | return token; |
michael@0 | 76 | } |
michael@0 | 77 | |
michael@0 | 78 | /** |
michael@0 | 79 | * Opens a linked token for the specified token. |
michael@0 | 80 | * |
michael@0 | 81 | * @param token The token to get the linked token from |
michael@0 | 82 | * @return A linked token or nullptr if one does not exist. |
michael@0 | 83 | * Caller should close the handle. |
michael@0 | 84 | */ |
michael@0 | 85 | HANDLE |
michael@0 | 86 | UACHelper::OpenLinkedToken(HANDLE token) |
michael@0 | 87 | { |
michael@0 | 88 | // Magic below... |
michael@0 | 89 | // UAC creates 2 tokens. One is the restricted token which we have. |
michael@0 | 90 | // the other is the UAC elevated one. Since we are running as a service |
michael@0 | 91 | // as the system account we have access to both. |
michael@0 | 92 | TOKEN_LINKED_TOKEN tlt; |
michael@0 | 93 | HANDLE hNewLinkedToken = nullptr; |
michael@0 | 94 | DWORD len; |
michael@0 | 95 | if (GetTokenInformation(token, (TOKEN_INFORMATION_CLASS)TokenLinkedToken, |
michael@0 | 96 | &tlt, sizeof(TOKEN_LINKED_TOKEN), &len)) { |
michael@0 | 97 | token = tlt.LinkedToken; |
michael@0 | 98 | hNewLinkedToken = token; |
michael@0 | 99 | } |
michael@0 | 100 | return hNewLinkedToken; |
michael@0 | 101 | } |
michael@0 | 102 | |
michael@0 | 103 | |
michael@0 | 104 | /** |
michael@0 | 105 | * Enables or disables a privilege for the specified token. |
michael@0 | 106 | * |
michael@0 | 107 | * @param token The token to adjust the privilege on. |
michael@0 | 108 | * @param priv The privilege to adjust. |
michael@0 | 109 | * @param enable Whether to enable or disable it |
michael@0 | 110 | * @return TRUE if the token was adjusted to the specified value. |
michael@0 | 111 | */ |
michael@0 | 112 | BOOL |
michael@0 | 113 | UACHelper::SetPrivilege(HANDLE token, LPCTSTR priv, BOOL enable) |
michael@0 | 114 | { |
michael@0 | 115 | LUID luidOfPriv; |
michael@0 | 116 | if (!LookupPrivilegeValue(nullptr, priv, &luidOfPriv)) { |
michael@0 | 117 | return FALSE; |
michael@0 | 118 | } |
michael@0 | 119 | |
michael@0 | 120 | TOKEN_PRIVILEGES tokenPriv; |
michael@0 | 121 | tokenPriv.PrivilegeCount = 1; |
michael@0 | 122 | tokenPriv.Privileges[0].Luid = luidOfPriv; |
michael@0 | 123 | tokenPriv.Privileges[0].Attributes = enable ? SE_PRIVILEGE_ENABLED : 0; |
michael@0 | 124 | |
michael@0 | 125 | SetLastError(ERROR_SUCCESS); |
michael@0 | 126 | if (!AdjustTokenPrivileges(token, false, &tokenPriv, |
michael@0 | 127 | sizeof(tokenPriv), nullptr, nullptr)) { |
michael@0 | 128 | return FALSE; |
michael@0 | 129 | } |
michael@0 | 130 | |
michael@0 | 131 | return GetLastError() == ERROR_SUCCESS; |
michael@0 | 132 | } |
michael@0 | 133 | |
michael@0 | 134 | /** |
michael@0 | 135 | * For each privilege that is specified, an attempt will be made to |
michael@0 | 136 | * drop the privilege. |
michael@0 | 137 | * |
michael@0 | 138 | * @param token The token to adjust the privilege on. |
michael@0 | 139 | * Pass nullptr for current token. |
michael@0 | 140 | * @param unneededPrivs An array of unneeded privileges. |
michael@0 | 141 | * @param count The size of the array |
michael@0 | 142 | * @return TRUE if there were no errors |
michael@0 | 143 | */ |
michael@0 | 144 | BOOL |
michael@0 | 145 | UACHelper::DisableUnneededPrivileges(HANDLE token, |
michael@0 | 146 | LPCTSTR *unneededPrivs, |
michael@0 | 147 | size_t count) |
michael@0 | 148 | { |
michael@0 | 149 | HANDLE obtainedToken = nullptr; |
michael@0 | 150 | if (!token) { |
michael@0 | 151 | // Note: This handle is a pseudo-handle and need not be closed |
michael@0 | 152 | HANDLE process = GetCurrentProcess(); |
michael@0 | 153 | if (!OpenProcessToken(process, TOKEN_ALL_ACCESS_P, &obtainedToken)) { |
michael@0 | 154 | LOG_WARN(("Could not obtain token for current process, no " |
michael@0 | 155 | "privileges changed. (%d)", GetLastError())); |
michael@0 | 156 | return FALSE; |
michael@0 | 157 | } |
michael@0 | 158 | token = obtainedToken; |
michael@0 | 159 | } |
michael@0 | 160 | |
michael@0 | 161 | BOOL result = TRUE; |
michael@0 | 162 | for (size_t i = 0; i < count; i++) { |
michael@0 | 163 | if (SetPrivilege(token, unneededPrivs[i], FALSE)) { |
michael@0 | 164 | LOG(("Disabled unneeded token privilege: %s.", |
michael@0 | 165 | unneededPrivs[i])); |
michael@0 | 166 | } else { |
michael@0 | 167 | LOG(("Could not disable token privilege value: %s. (%d)", |
michael@0 | 168 | unneededPrivs[i], GetLastError())); |
michael@0 | 169 | result = FALSE; |
michael@0 | 170 | } |
michael@0 | 171 | } |
michael@0 | 172 | |
michael@0 | 173 | if (obtainedToken) { |
michael@0 | 174 | CloseHandle(obtainedToken); |
michael@0 | 175 | } |
michael@0 | 176 | return result; |
michael@0 | 177 | } |
michael@0 | 178 | |
michael@0 | 179 | /** |
michael@0 | 180 | * Disables privileges for the specified token. |
michael@0 | 181 | * The privileges to disable are in PrivsToDisable. |
michael@0 | 182 | * In the future there could be new privs and we are not sure if we should |
michael@0 | 183 | * explicitly disable these or not. |
michael@0 | 184 | * |
michael@0 | 185 | * @param token The token to drop the privilege on. |
michael@0 | 186 | * Pass nullptr for current token. |
michael@0 | 187 | * @return TRUE if there were no errors |
michael@0 | 188 | */ |
michael@0 | 189 | BOOL |
michael@0 | 190 | UACHelper::DisablePrivileges(HANDLE token) |
michael@0 | 191 | { |
michael@0 | 192 | static const size_t PrivsToDisableSize = |
michael@0 | 193 | sizeof(UACHelper::PrivsToDisable) / sizeof(UACHelper::PrivsToDisable[0]); |
michael@0 | 194 | |
michael@0 | 195 | return DisableUnneededPrivileges(token, UACHelper::PrivsToDisable, |
michael@0 | 196 | PrivsToDisableSize); |
michael@0 | 197 | } |
michael@0 | 198 | |
michael@0 | 199 | /** |
michael@0 | 200 | * Check if the current user can elevate. |
michael@0 | 201 | * |
michael@0 | 202 | * @return true if the user can elevate. |
michael@0 | 203 | * false otherwise. |
michael@0 | 204 | */ |
michael@0 | 205 | bool |
michael@0 | 206 | UACHelper::CanUserElevate() |
michael@0 | 207 | { |
michael@0 | 208 | HANDLE token; |
michael@0 | 209 | if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) { |
michael@0 | 210 | return false; |
michael@0 | 211 | } |
michael@0 | 212 | |
michael@0 | 213 | TOKEN_ELEVATION_TYPE elevationType; |
michael@0 | 214 | DWORD len; |
michael@0 | 215 | bool canElevate = GetTokenInformation(token, TokenElevationType, |
michael@0 | 216 | &elevationType, |
michael@0 | 217 | sizeof(elevationType), &len) && |
michael@0 | 218 | (elevationType == TokenElevationTypeLimited); |
michael@0 | 219 | CloseHandle(token); |
michael@0 | 220 | |
michael@0 | 221 | return canElevate; |
michael@0 | 222 | } |