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