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