summaryrefslogtreecommitdiff
path: root/onlineupdate/source/update/common/uachelper.cxx
blob: 55053f3d79323b62b28a4d418f97be80c428ebab (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifdef WNT
#include <windows.h>
#include <wtsapi32.h>
#include "uachelper.h"
#include "updatelogging.h"

// See the MSDN documentation with title: Privilege Constants
// At the time of this writing, this documentation is located at:
// http://msdn.microsoft.com/en-us/library/windows/desktop/bb530716%28v=vs.85%29.aspx
LPCTSTR UACHelper::PrivsToDisable[] = {
  SE_ASSIGNPRIMARYTOKEN_NAME,
  SE_AUDIT_NAME,
  SE_BACKUP_NAME,
  // CreateProcess will succeed but the app will fail to launch on some WinXP
  // machines if SE_CHANGE_NOTIFY_NAME is disabled.  In particular this happens
  // for limited user accounts on those machines.  The define is kept here as a
  // reminder that it should never be re-added.
  // This permission is for directory watching but also from MSDN: "This
  // privilege also causes the system to skip all traversal access checks."
  // SE_CHANGE_NOTIFY_NAME,
  SE_CREATE_GLOBAL_NAME,
  SE_CREATE_PAGEFILE_NAME,
  SE_CREATE_PERMANENT_NAME,
  SE_CREATE_SYMBOLIC_LINK_NAME,
  SE_CREATE_TOKEN_NAME,
  SE_DEBUG_NAME,
  SE_ENABLE_DELEGATION_NAME,
  SE_IMPERSONATE_NAME,
  SE_INC_BASE_PRIORITY_NAME,
  SE_INCREASE_QUOTA_NAME,
  SE_INC_WORKING_SET_NAME,
  SE_LOAD_DRIVER_NAME,
  SE_LOCK_MEMORY_NAME,
  SE_MACHINE_ACCOUNT_NAME,
  SE_MANAGE_VOLUME_NAME,
  SE_PROF_SINGLE_PROCESS_NAME,
  SE_RELABEL_NAME,
  SE_REMOTE_SHUTDOWN_NAME,
  SE_RESTORE_NAME,
  SE_SECURITY_NAME,
  SE_SHUTDOWN_NAME,
  SE_SYNC_AGENT_NAME,
  SE_SYSTEM_ENVIRONMENT_NAME,
  SE_SYSTEM_PROFILE_NAME,
  SE_SYSTEMTIME_NAME,
  SE_TAKE_OWNERSHIP_NAME,
  SE_TCB_NAME,
  SE_TIME_ZONE_NAME,
  SE_TRUSTED_CREDMAN_ACCESS_NAME,
  SE_UNDOCK_NAME,
  SE_UNSOLICITED_INPUT_NAME
};

/**
 * Opens a user token for the given session ID
 *
 * @param  sessionID  The session ID for the token to obtain
 * @return A handle to the token to obtain which will be primary if enough
 *         permissions exist.  Caller should close the handle.
 */
HANDLE
UACHelper::OpenUserToken(DWORD sessionID)
{
  HMODULE module = LoadLibraryW(L"wtsapi32.dll");
  HANDLE token = nullptr;
  decltype(WTSQueryUserToken)* wtsQueryUserToken =
    (decltype(WTSQueryUserToken)*) GetProcAddress(module, "WTSQueryUserToken");
  if (wtsQueryUserToken) {
    wtsQueryUserToken(sessionID, &token);
  }
  FreeLibrary(module);
  return token;
}

/**
 * Opens a linked token for the specified token.
 *
 * @param  token The token to get the linked token from
 * @return A linked token or nullptr if one does not exist.
 *         Caller should close the handle.
 */
HANDLE
UACHelper::OpenLinkedToken(HANDLE token)
{
  // Magic below...
  // UAC creates 2 tokens.  One is the restricted token which we have.
  // the other is the UAC elevated one. Since we are running as a service
  // as the system account we have access to both.
  TOKEN_LINKED_TOKEN tlt;
  HANDLE hNewLinkedToken = nullptr;
  DWORD len;
  if (GetTokenInformation(token, (TOKEN_INFORMATION_CLASS)TokenLinkedToken,
                          &tlt, sizeof(TOKEN_LINKED_TOKEN), &len)) {
    token = tlt.LinkedToken;
    hNewLinkedToken = token;
  }
  return hNewLinkedToken;
}


/**
 * Enables or disables a privilege for the specified token.
 *
 * @param  token  The token to adjust the privilege on.
 * @param  priv   The privilege to adjust.
 * @param  enable Whether to enable or disable it
 * @return TRUE if the token was adjusted to the specified value.
 */
BOOL
UACHelper::SetPrivilege(HANDLE token, LPCTSTR priv, BOOL enable)
{
  LUID luidOfPriv;
  if (!LookupPrivilegeValue(nullptr, priv, &luidOfPriv)) {
    return FALSE;
  }

  TOKEN_PRIVILEGES tokenPriv;
  tokenPriv.PrivilegeCount = 1;
  tokenPriv.Privileges[0].Luid = luidOfPriv;
  tokenPriv.Privileges[0].Attributes = enable ? SE_PRIVILEGE_ENABLED : 0;

  SetLastError(ERROR_SUCCESS);
  if (!AdjustTokenPrivileges(token, false, &tokenPriv,
                             sizeof(tokenPriv), nullptr, nullptr)) {
    return FALSE;
  }

  return GetLastError() == ERROR_SUCCESS;
}

/**
 * For each privilege that is specified, an attempt will be made to
 * drop the privilege.
 *
 * @param  token         The token to adjust the privilege on.
 *         Pass nullptr for current token.
 * @param  unneededPrivs An array of unneeded privileges.
 * @param  count         The size of the array
 * @return TRUE if there were no errors
 */
BOOL
UACHelper::DisableUnneededPrivileges(HANDLE token,
                                     LPCTSTR *unneededPrivs,
                                     size_t count)
{
  HANDLE obtainedToken = nullptr;
  if (!token) {
    // Note: This handle is a pseudo-handle and need not be closed
    HANDLE process = GetCurrentProcess();
    if (!OpenProcessToken(process, TOKEN_ALL_ACCESS_P, &obtainedToken)) {
      LOG_WARN(("Could not obtain token for current process, no "
                "privileges changed. (%d)", GetLastError()));
      return FALSE;
    }
    token = obtainedToken;
  }

  BOOL result = TRUE;
  for (size_t i = 0; i < count; i++) {
    if (SetPrivilege(token, unneededPrivs[i], FALSE)) {
      LOG(("Disabled unneeded token privilege: %s.",
           unneededPrivs[i]));
    } else {
      LOG(("Could not disable token privilege value: %s. (%d)",
           unneededPrivs[i], GetLastError()));
      result = FALSE;
    }
  }

  if (obtainedToken) {
    CloseHandle(obtainedToken);
  }
  return result;
}

/**
 * Disables privileges for the specified token.
 * The privileges to disable are in PrivsToDisable.
 * In the future there could be new privs and we are not sure if we should
 * explicitly disable these or not.
 *
 * @param  token The token to drop the privilege on.
 *         Pass nullptr for current token.
 * @return TRUE if there were no errors
 */
BOOL
UACHelper::DisablePrivileges(HANDLE token)
{
  static const size_t PrivsToDisableSize =
    sizeof(UACHelper::PrivsToDisable) / sizeof(UACHelper::PrivsToDisable[0]);

  return DisableUnneededPrivileges(token, UACHelper::PrivsToDisable,
                                   PrivsToDisableSize);
}

/**
 * Check if the current user can elevate.
 *
 * @return true if the user can elevate.
 *         false otherwise.
 */
bool
UACHelper::CanUserElevate()
{
  HANDLE token;
  if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token)) {
    return false;
  }

  TOKEN_ELEVATION_TYPE elevationType;
  DWORD len;
  bool canElevate = GetTokenInformation(token, TokenElevationType,
                                        &elevationType,
                                        sizeof(elevationType), &len) &&
                    (elevationType == TokenElevationTypeLimited);
  CloseHandle(token);

  return canElevate;
}
#endif