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