1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
6
7 #define MOZ_USE_LAUNCHER_ERROR
8
9 #include "LaunchUnelevated.h"
10
11 #include "mozilla/Assertions.h"
12 #include "mozilla/CmdLineAndEnvUtils.h"
13 #include "mozilla/mscom/ProcessRuntime.h"
14 #include "mozilla/RefPtr.h"
15 #include "mozilla/ShellHeaderOnlyUtils.h"
16 #include "mozilla/WinHeaderOnlyUtils.h"
17 #include "nsWindowsHelpers.h"
18
19 #include <windows.h>
20
IsHighIntegrity(const nsAutoHandle & aToken)21 static mozilla::LauncherResult<bool> IsHighIntegrity(
22 const nsAutoHandle& aToken) {
23 DWORD reqdLen;
24 if (!::GetTokenInformation(aToken.get(), TokenIntegrityLevel, nullptr, 0,
25 &reqdLen)) {
26 DWORD err = ::GetLastError();
27 if (err != ERROR_INSUFFICIENT_BUFFER) {
28 return LAUNCHER_ERROR_FROM_WIN32(err);
29 }
30 }
31
32 auto buf = mozilla::MakeUnique<char[]>(reqdLen);
33
34 if (!::GetTokenInformation(aToken.get(), TokenIntegrityLevel, buf.get(),
35 reqdLen, &reqdLen)) {
36 return LAUNCHER_ERROR_FROM_LAST();
37 }
38
39 auto tokenLabel = reinterpret_cast<PTOKEN_MANDATORY_LABEL>(buf.get());
40
41 DWORD subAuthCount = *::GetSidSubAuthorityCount(tokenLabel->Label.Sid);
42 DWORD integrityLevel =
43 *::GetSidSubAuthority(tokenLabel->Label.Sid, subAuthCount - 1);
44 return integrityLevel > SECURITY_MANDATORY_MEDIUM_RID;
45 }
46
GetMediumIntegrityToken(const nsAutoHandle & aProcessToken)47 static mozilla::LauncherResult<HANDLE> GetMediumIntegrityToken(
48 const nsAutoHandle& aProcessToken) {
49 HANDLE rawResult;
50 if (!::DuplicateTokenEx(aProcessToken.get(), 0, nullptr,
51 SecurityImpersonation, TokenPrimary, &rawResult)) {
52 return LAUNCHER_ERROR_FROM_LAST();
53 }
54
55 nsAutoHandle result(rawResult);
56
57 BYTE mediumIlSid[SECURITY_MAX_SID_SIZE];
58 DWORD mediumIlSidSize = sizeof(mediumIlSid);
59 if (!::CreateWellKnownSid(WinMediumLabelSid, nullptr, mediumIlSid,
60 &mediumIlSidSize)) {
61 return LAUNCHER_ERROR_FROM_LAST();
62 }
63
64 TOKEN_MANDATORY_LABEL integrityLevel = {};
65 integrityLevel.Label.Attributes = SE_GROUP_INTEGRITY;
66 integrityLevel.Label.Sid = reinterpret_cast<PSID>(mediumIlSid);
67
68 if (!::SetTokenInformation(rawResult, TokenIntegrityLevel, &integrityLevel,
69 sizeof(integrityLevel))) {
70 return LAUNCHER_ERROR_FROM_LAST();
71 }
72
73 return result.disown();
74 }
75
IsAdminByAppCompat(HKEY aRootKey,const wchar_t * aExecutablePath)76 static mozilla::LauncherResult<bool> IsAdminByAppCompat(
77 HKEY aRootKey, const wchar_t* aExecutablePath) {
78 static const wchar_t kPathToLayers[] =
79 L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\"
80 L"AppCompatFlags\\Layers";
81
82 DWORD dataLength = 0;
83 LSTATUS status = ::RegGetValueW(aRootKey, kPathToLayers, aExecutablePath,
84 RRF_RT_REG_SZ | RRF_SUBKEY_WOW6464KEY,
85 nullptr, nullptr, &dataLength);
86 if (status == ERROR_FILE_NOT_FOUND) {
87 return false;
88 } else if (status != ERROR_SUCCESS) {
89 return LAUNCHER_ERROR_FROM_WIN32(status);
90 }
91
92 auto valueData = mozilla::MakeUnique<wchar_t[]>(dataLength);
93 if (!valueData) {
94 return LAUNCHER_ERROR_FROM_WIN32(ERROR_OUTOFMEMORY);
95 }
96
97 status = ::RegGetValueW(aRootKey, kPathToLayers, aExecutablePath,
98 RRF_RT_REG_SZ | RRF_SUBKEY_WOW6464KEY, nullptr,
99 valueData.get(), &dataLength);
100 if (status != ERROR_SUCCESS) {
101 return LAUNCHER_ERROR_FROM_WIN32(status);
102 }
103
104 const wchar_t kRunAsAdmin[] = L"RUNASADMIN";
105 const wchar_t kDelimiters[] = L" ";
106 wchar_t* tokenContext = nullptr;
107 const wchar_t* token = wcstok_s(valueData.get(), kDelimiters, &tokenContext);
108 while (token) {
109 if (!_wcsnicmp(token, kRunAsAdmin, mozilla::ArrayLength(kRunAsAdmin))) {
110 return true;
111 }
112 token = wcstok_s(nullptr, kDelimiters, &tokenContext);
113 }
114
115 return false;
116 }
117
118 namespace mozilla {
119
120 // If we're running at an elevated integrity level, re-run ourselves at the
121 // user's normal integrity level. We do this by locating the active explorer
122 // shell, and then asking it to do a ShellExecute on our behalf. We do it this
123 // way to ensure that the child process runs as the original user in the active
124 // session; an elevated process could be running with different credentials than
125 // those of the session.
126 // See https://blogs.msdn.microsoft.com/oldnewthing/20131118-00/?p=2643
127
LaunchUnelevated(int aArgc,wchar_t * aArgv[])128 LauncherVoidResult LaunchUnelevated(int aArgc, wchar_t* aArgv[]) {
129 // We need COM to talk to Explorer. Using ProcessRuntime so that
130 // process-global COM configuration is done correctly
131 mozilla::mscom::ProcessRuntime mscom(
132 mozilla::mscom::ProcessRuntime::ProcessCategory::Launcher);
133 if (!mscom) {
134 return LAUNCHER_ERROR_FROM_HRESULT(mscom.GetHResult());
135 }
136
137 // Omit argv[0] because ShellExecute doesn't need it in params
138 UniquePtr<wchar_t[]> cmdLine(MakeCommandLine(aArgc - 1, aArgv + 1));
139 if (!cmdLine) {
140 return LAUNCHER_ERROR_GENERIC();
141 }
142
143 _bstr_t exe(aArgv[0]);
144 _variant_t args(cmdLine.get());
145 _variant_t operation(L"open");
146 _variant_t directory;
147 _variant_t showCmd(SW_SHOWNORMAL);
148 return ShellExecuteByExplorer(exe, args, operation, directory, showCmd);
149 }
150
GetElevationState(const wchar_t * aExecutablePath,mozilla::LauncherFlags aFlags,nsAutoHandle & aOutMediumIlToken)151 LauncherResult<ElevationState> GetElevationState(
152 const wchar_t* aExecutablePath, mozilla::LauncherFlags aFlags,
153 nsAutoHandle& aOutMediumIlToken) {
154 aOutMediumIlToken.reset();
155
156 const DWORD tokenFlags = TOKEN_QUERY | TOKEN_DUPLICATE |
157 TOKEN_ADJUST_DEFAULT | TOKEN_ASSIGN_PRIMARY;
158 HANDLE rawToken;
159 if (!::OpenProcessToken(::GetCurrentProcess(), tokenFlags, &rawToken)) {
160 return LAUNCHER_ERROR_FROM_LAST();
161 }
162
163 nsAutoHandle token(rawToken);
164
165 LauncherResult<TOKEN_ELEVATION_TYPE> elevationType = GetElevationType(token);
166 if (elevationType.isErr()) {
167 return elevationType.propagateErr();
168 }
169
170 Maybe<ElevationState> elevationState;
171 switch (elevationType.unwrap()) {
172 case TokenElevationTypeLimited:
173 return ElevationState::eNormalUser;
174 case TokenElevationTypeFull:
175 elevationState = Some(ElevationState::eElevated);
176 break;
177 case TokenElevationTypeDefault: {
178 // In this case, UAC is disabled. We do not yet know whether or not we
179 // are running at high integrity. If we are at high integrity, we can't
180 // relaunch ourselves in a non-elevated state via Explorer, as we would
181 // just end up in an infinite loop of launcher processes re-launching
182 // themselves.
183 LauncherResult<bool> isHighIntegrity = IsHighIntegrity(token);
184 if (isHighIntegrity.isErr()) {
185 return isHighIntegrity.propagateErr();
186 }
187
188 if (!isHighIntegrity.unwrap()) {
189 return ElevationState::eNormalUser;
190 }
191
192 elevationState = Some(ElevationState::eHighIntegrityNoUAC);
193 break;
194 }
195 default:
196 MOZ_ASSERT_UNREACHABLE("Was a new value added to the enumeration?");
197 return LAUNCHER_ERROR_GENERIC();
198 }
199
200 MOZ_ASSERT(elevationState.isSome() &&
201 elevationState.value() != ElevationState::eNormalUser,
202 "Should have returned earlier for the eNormalUser case.");
203
204 LauncherResult<bool> isAdminByAppCompat =
205 IsAdminByAppCompat(HKEY_CURRENT_USER, aExecutablePath);
206 if (isAdminByAppCompat.isErr()) {
207 return isAdminByAppCompat.propagateErr();
208 }
209
210 if (isAdminByAppCompat.unwrap()) {
211 elevationState = Some(ElevationState::eHighIntegrityByAppCompat);
212 } else {
213 isAdminByAppCompat =
214 IsAdminByAppCompat(HKEY_LOCAL_MACHINE, aExecutablePath);
215 if (isAdminByAppCompat.isErr()) {
216 return isAdminByAppCompat.propagateErr();
217 }
218
219 if (isAdminByAppCompat.unwrap()) {
220 elevationState = Some(ElevationState::eHighIntegrityByAppCompat);
221 }
222 }
223
224 // A medium IL token is not needed in the following cases.
225 // 1) We keep the process elevated (= LauncherFlags::eNoDeelevate)
226 // 2) The process was elevated by UAC (= ElevationState::eElevated)
227 // AND the launcher process doesn't wait for the browser process
228 if ((aFlags & mozilla::LauncherFlags::eNoDeelevate) ||
229 (elevationState.value() == ElevationState::eElevated &&
230 !(aFlags & mozilla::LauncherFlags::eWaitForBrowser))) {
231 return elevationState.value();
232 }
233
234 LauncherResult<HANDLE> tokenResult = GetMediumIntegrityToken(token);
235 if (tokenResult.isOk()) {
236 aOutMediumIlToken.own(tokenResult.unwrap());
237 } else {
238 return tokenResult.propagateErr();
239 }
240
241 return elevationState.value();
242 }
243
244 } // namespace mozilla
245