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://devblogs.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 cmd;
144 
145   UniquePtr<wchar_t[]> packageFamilyName = mozilla::GetPackageFamilyName();
146   if (packageFamilyName) {
147     int cmdLen =
148         // 22 for the prefix + suffix + null terminator below
149         22 + wcslen(packageFamilyName.get());
150     wchar_t appCmd[cmdLen];
151     swprintf(appCmd, cmdLen, L"shell:appsFolder\\%s!App",
152              packageFamilyName.get());
153     cmd = appCmd;
154   } else {
155     cmd = aArgv[0];
156   }
157 
158   _variant_t args(cmdLine.get());
159   _variant_t operation(L"open");
160   _variant_t directory;
161   _variant_t showCmd(SW_SHOWNORMAL);
162   return ShellExecuteByExplorer(cmd, args, operation, directory, showCmd);
163 }
164 
GetElevationState(const wchar_t * aExecutablePath,mozilla::LauncherFlags aFlags,nsAutoHandle & aOutMediumIlToken)165 LauncherResult<ElevationState> GetElevationState(
166     const wchar_t* aExecutablePath, mozilla::LauncherFlags aFlags,
167     nsAutoHandle& aOutMediumIlToken) {
168   aOutMediumIlToken.reset();
169 
170   const DWORD tokenFlags = TOKEN_QUERY | TOKEN_DUPLICATE |
171                            TOKEN_ADJUST_DEFAULT | TOKEN_ASSIGN_PRIMARY;
172   HANDLE rawToken;
173   if (!::OpenProcessToken(::GetCurrentProcess(), tokenFlags, &rawToken)) {
174     return LAUNCHER_ERROR_FROM_LAST();
175   }
176 
177   nsAutoHandle token(rawToken);
178 
179   LauncherResult<TOKEN_ELEVATION_TYPE> elevationType = GetElevationType(token);
180   if (elevationType.isErr()) {
181     return elevationType.propagateErr();
182   }
183 
184   Maybe<ElevationState> elevationState;
185   switch (elevationType.unwrap()) {
186     case TokenElevationTypeLimited:
187       return ElevationState::eNormalUser;
188     case TokenElevationTypeFull:
189       elevationState = Some(ElevationState::eElevated);
190       break;
191     case TokenElevationTypeDefault: {
192       // In this case, UAC is disabled. We do not yet know whether or not we
193       // are running at high integrity. If we are at high integrity, we can't
194       // relaunch ourselves in a non-elevated state via Explorer, as we would
195       // just end up in an infinite loop of launcher processes re-launching
196       // themselves.
197       LauncherResult<bool> isHighIntegrity = IsHighIntegrity(token);
198       if (isHighIntegrity.isErr()) {
199         return isHighIntegrity.propagateErr();
200       }
201 
202       if (!isHighIntegrity.unwrap()) {
203         return ElevationState::eNormalUser;
204       }
205 
206       elevationState = Some(ElevationState::eHighIntegrityNoUAC);
207       break;
208     }
209     default:
210       MOZ_ASSERT_UNREACHABLE("Was a new value added to the enumeration?");
211       return LAUNCHER_ERROR_GENERIC();
212   }
213 
214   MOZ_ASSERT(elevationState.isSome() &&
215                  elevationState.value() != ElevationState::eNormalUser,
216              "Should have returned earlier for the eNormalUser case.");
217 
218   LauncherResult<bool> isAdminByAppCompat =
219       IsAdminByAppCompat(HKEY_CURRENT_USER, aExecutablePath);
220   if (isAdminByAppCompat.isErr()) {
221     return isAdminByAppCompat.propagateErr();
222   }
223 
224   if (isAdminByAppCompat.unwrap()) {
225     elevationState = Some(ElevationState::eHighIntegrityByAppCompat);
226   } else {
227     isAdminByAppCompat =
228         IsAdminByAppCompat(HKEY_LOCAL_MACHINE, aExecutablePath);
229     if (isAdminByAppCompat.isErr()) {
230       return isAdminByAppCompat.propagateErr();
231     }
232 
233     if (isAdminByAppCompat.unwrap()) {
234       elevationState = Some(ElevationState::eHighIntegrityByAppCompat);
235     }
236   }
237 
238   // A medium IL token is not needed in the following cases.
239   // 1) We keep the process elevated (= LauncherFlags::eNoDeelevate)
240   // 2) The process was elevated by UAC (= ElevationState::eElevated)
241   //    AND the launcher process doesn't wait for the browser process
242   if ((aFlags & mozilla::LauncherFlags::eNoDeelevate) ||
243       (elevationState.value() == ElevationState::eElevated &&
244        !(aFlags & mozilla::LauncherFlags::eWaitForBrowser))) {
245     return elevationState.value();
246   }
247 
248   LauncherResult<HANDLE> tokenResult = GetMediumIntegrityToken(token);
249   if (tokenResult.isOk()) {
250     aOutMediumIlToken.own(tokenResult.unwrap());
251   } else {
252     return tokenResult.propagateErr();
253   }
254 
255   return elevationState.value();
256 }
257 
258 }  // namespace mozilla
259