LaunchUnelevated.cpp (10368B)
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 #include "LaunchUnelevated.h" 8 9 #include "mozilla/Assertions.h" 10 #include "mozilla/CmdLineAndEnvUtils.h" 11 #include "mozilla/mscom/ProcessRuntime.h" 12 #include "mozilla/ShellHeaderOnlyUtils.h" 13 #include "mozilla/WinHeaderOnlyUtils.h" 14 #include "../BrowserDefines.h" 15 #include "nsWindowsHelpers.h" 16 17 #include <windows.h> 18 19 #if !defined(RRF_SUBKEY_WOW6464KEY) 20 # define RRF_SUBKEY_WOW6464KEY 0x00010000 21 #endif // !defined(RRF_SUBKEY_WOW6464KEY) 22 23 static mozilla::LauncherResult<bool> IsHighIntegrity( 24 const nsAutoHandle& aToken) { 25 DWORD reqdLen; 26 if (!::GetTokenInformation(aToken.get(), TokenIntegrityLevel, nullptr, 0, 27 &reqdLen)) { 28 DWORD err = ::GetLastError(); 29 if (err != ERROR_INSUFFICIENT_BUFFER) { 30 return LAUNCHER_ERROR_FROM_WIN32(err); 31 } 32 } 33 34 auto buf = mozilla::MakeUnique<char[]>(reqdLen); 35 36 if (!::GetTokenInformation(aToken.get(), TokenIntegrityLevel, buf.get(), 37 reqdLen, &reqdLen)) { 38 return LAUNCHER_ERROR_FROM_LAST(); 39 } 40 41 auto tokenLabel = reinterpret_cast<PTOKEN_MANDATORY_LABEL>(buf.get()); 42 43 DWORD subAuthCount = *::GetSidSubAuthorityCount(tokenLabel->Label.Sid); 44 DWORD integrityLevel = 45 *::GetSidSubAuthority(tokenLabel->Label.Sid, subAuthCount - 1); 46 return integrityLevel > SECURITY_MANDATORY_MEDIUM_RID; 47 } 48 49 static mozilla::LauncherResult<HANDLE> GetMediumIntegrityToken( 50 const nsAutoHandle& aProcessToken) { 51 HANDLE rawResult; 52 if (!::DuplicateTokenEx(aProcessToken.get(), 0, nullptr, 53 SecurityImpersonation, TokenPrimary, &rawResult)) { 54 return LAUNCHER_ERROR_FROM_LAST(); 55 } 56 57 nsAutoHandle result(rawResult); 58 59 BYTE mediumIlSid[SECURITY_MAX_SID_SIZE]; 60 DWORD mediumIlSidSize = sizeof(mediumIlSid); 61 if (!::CreateWellKnownSid(WinMediumLabelSid, nullptr, mediumIlSid, 62 &mediumIlSidSize)) { 63 return LAUNCHER_ERROR_FROM_LAST(); 64 } 65 66 TOKEN_MANDATORY_LABEL integrityLevel = {}; 67 integrityLevel.Label.Attributes = SE_GROUP_INTEGRITY; 68 integrityLevel.Label.Sid = reinterpret_cast<PSID>(mediumIlSid); 69 70 if (!::SetTokenInformation(rawResult, TokenIntegrityLevel, &integrityLevel, 71 sizeof(integrityLevel))) { 72 return LAUNCHER_ERROR_FROM_LAST(); 73 } 74 75 return result.disown(); 76 } 77 78 static mozilla::LauncherResult<bool> IsAdminByAppCompat( 79 HKEY aRootKey, const wchar_t* aExecutablePath) { 80 static const wchar_t kPathToLayers[] = 81 L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\" 82 L"AppCompatFlags\\Layers"; 83 84 DWORD dataLength = 0; 85 LSTATUS status = ::RegGetValueW(aRootKey, kPathToLayers, aExecutablePath, 86 RRF_RT_REG_SZ | RRF_SUBKEY_WOW6464KEY, 87 nullptr, nullptr, &dataLength); 88 if (status == ERROR_FILE_NOT_FOUND) { 89 return false; 90 } else if (status != ERROR_SUCCESS) { 91 return LAUNCHER_ERROR_FROM_WIN32(status); 92 } 93 94 auto valueData = mozilla::MakeUnique<wchar_t[]>(dataLength); 95 if (!valueData) { 96 return LAUNCHER_ERROR_FROM_WIN32(ERROR_OUTOFMEMORY); 97 } 98 99 status = ::RegGetValueW(aRootKey, kPathToLayers, aExecutablePath, 100 RRF_RT_REG_SZ | RRF_SUBKEY_WOW6464KEY, nullptr, 101 valueData.get(), &dataLength); 102 if (status != ERROR_SUCCESS) { 103 return LAUNCHER_ERROR_FROM_WIN32(status); 104 } 105 106 const wchar_t kRunAsAdmin[] = L"RUNASADMIN"; 107 const wchar_t kDelimiters[] = L" "; 108 wchar_t* tokenContext = nullptr; 109 const wchar_t* token = wcstok_s(valueData.get(), kDelimiters, &tokenContext); 110 while (token) { 111 if (!_wcsnicmp(token, kRunAsAdmin, std::size(kRunAsAdmin))) { 112 return true; 113 } 114 token = wcstok_s(nullptr, kDelimiters, &tokenContext); 115 } 116 117 return false; 118 } 119 120 namespace mozilla { 121 122 // If we're running at an elevated integrity level, re-run ourselves at the 123 // user's normal integrity level. We do this by locating the active explorer 124 // shell, and then asking it to do a ShellExecute on our behalf. We do it this 125 // way to ensure that the child process runs as the original user in the active 126 // session; an elevated process could be running with different credentials than 127 // those of the session. 128 // See https://devblogs.microsoft.com/oldnewthing/20131118-00/?p=2643 129 130 LauncherVoidResult LaunchUnelevated(int aArgc, wchar_t* aArgv[]) { 131 // We need COM to talk to Explorer. Using ProcessRuntime so that 132 // process-global COM configuration is done correctly 133 mozilla::mscom::ProcessRuntime mscom( 134 mozilla::mscom::ProcessRuntime::ProcessCategory::Launcher); 135 if (!mscom) { 136 return LAUNCHER_ERROR_FROM_HRESULT(mscom.GetHResult()); 137 } 138 139 // Omit the original argv[0] because ShellExecute doesn't need it. Insert 140 // ATTEMPTING_DEELEVATION_FLAG so that we know not to attempt to restart 141 // ourselves if deelevation fails. 142 UniquePtr<wchar_t[]> cmdLine = [&]() { 143 constexpr wchar_t const* kTagArg = L"--" ATTEMPTING_DEELEVATION_FLAG; 144 145 // This should have already been checked, but just in case... 146 EnsureBrowserCommandlineSafe(aArgc, aArgv); 147 148 if (mozilla::CheckArg(aArgc, aArgv, "osint", nullptr, CheckArgFlag::None)) { 149 // If the command line contains -osint, we have to arrange things in a 150 // particular order. 151 // 152 // (We can't just replace -osint with kTagArg, unfortunately: there is 153 // code in the browser which behaves differently in the presence of an 154 // `-osint` tag, but which will not have had a chance to react to this. 155 // See, _e.g._, bug 1243603.) 156 auto const aArgvCopy = MakeUnique<wchar_t const*[]>(aArgc + 1); 157 aArgvCopy[0] = aArgv[1]; 158 aArgvCopy[1] = kTagArg; 159 for (int i = 2; i < aArgc; ++i) { 160 aArgvCopy[i] = aArgv[i]; 161 } 162 aArgvCopy[aArgc] = nullptr; // because argv[argc] is NULL 163 return MakeCommandLine(aArgc, aArgvCopy.get(), 0, nullptr); 164 } else { 165 // Otherwise, just tack it on at the end. 166 constexpr wchar_t const* const kTagArgArray[] = {kTagArg}; 167 return MakeCommandLine(aArgc - 1, aArgv + 1, 1, kTagArgArray); 168 } 169 }(); 170 if (!cmdLine) { 171 return LAUNCHER_ERROR_GENERIC(); 172 } 173 174 _bstr_t cmd; 175 176 UniquePtr<wchar_t[]> packageFamilyName = mozilla::GetPackageFamilyName(); 177 if (packageFamilyName) { 178 int cmdLen = 179 // 22 for the prefix + suffix + null terminator below 180 22 + wcslen(packageFamilyName.get()); 181 wchar_t appCmd[cmdLen]; 182 swprintf(appCmd, cmdLen, L"shell:appsFolder\\%s!App", 183 packageFamilyName.get()); 184 cmd = appCmd; 185 } else { 186 cmd = aArgv[0]; 187 } 188 189 _variant_t args(cmdLine.get()); 190 _variant_t operation(L"open"); 191 _variant_t directory; 192 _variant_t showCmd(SW_SHOWNORMAL); 193 return ShellExecuteByExplorer(cmd, args, operation, directory, showCmd); 194 } 195 196 LauncherResult<ElevationState> GetElevationState( 197 const wchar_t* aExecutablePath, mozilla::LauncherFlags aFlags, 198 nsAutoHandle& aOutMediumIlToken) { 199 aOutMediumIlToken.reset(); 200 201 const DWORD tokenFlags = TOKEN_QUERY | TOKEN_DUPLICATE | 202 TOKEN_ADJUST_DEFAULT | TOKEN_ASSIGN_PRIMARY; 203 HANDLE rawToken; 204 if (!::OpenProcessToken(::GetCurrentProcess(), tokenFlags, &rawToken)) { 205 return LAUNCHER_ERROR_FROM_LAST(); 206 } 207 208 nsAutoHandle token(rawToken); 209 210 LauncherResult<TOKEN_ELEVATION_TYPE> elevationType = GetElevationType(token); 211 if (elevationType.isErr()) { 212 return elevationType.propagateErr(); 213 } 214 215 Maybe<ElevationState> elevationState; 216 switch (elevationType.unwrap()) { 217 case TokenElevationTypeLimited: 218 return ElevationState::eNormalUser; 219 case TokenElevationTypeFull: 220 elevationState = Some(ElevationState::eElevated); 221 break; 222 case TokenElevationTypeDefault: { 223 // In this case, UAC is disabled. We do not yet know whether or not we 224 // are running at high integrity. If we are at high integrity, we can't 225 // relaunch ourselves in a non-elevated state via Explorer, as we would 226 // just end up in an infinite loop of launcher processes re-launching 227 // themselves. 228 LauncherResult<bool> isHighIntegrity = IsHighIntegrity(token); 229 if (isHighIntegrity.isErr()) { 230 return isHighIntegrity.propagateErr(); 231 } 232 233 if (!isHighIntegrity.unwrap()) { 234 return ElevationState::eNormalUser; 235 } 236 237 elevationState = Some(ElevationState::eHighIntegrityNoUAC); 238 break; 239 } 240 default: 241 MOZ_ASSERT_UNREACHABLE("Was a new value added to the enumeration?"); 242 return LAUNCHER_ERROR_GENERIC(); 243 } 244 245 MOZ_ASSERT(elevationState.isSome() && 246 elevationState.value() != ElevationState::eNormalUser, 247 "Should have returned earlier for the eNormalUser case."); 248 249 LauncherResult<bool> isAdminByAppCompat = 250 IsAdminByAppCompat(HKEY_CURRENT_USER, aExecutablePath); 251 if (isAdminByAppCompat.isErr()) { 252 return isAdminByAppCompat.propagateErr(); 253 } 254 255 if (isAdminByAppCompat.unwrap()) { 256 elevationState = Some(ElevationState::eHighIntegrityByAppCompat); 257 } else { 258 isAdminByAppCompat = 259 IsAdminByAppCompat(HKEY_LOCAL_MACHINE, aExecutablePath); 260 if (isAdminByAppCompat.isErr()) { 261 return isAdminByAppCompat.propagateErr(); 262 } 263 264 if (isAdminByAppCompat.unwrap()) { 265 elevationState = Some(ElevationState::eHighIntegrityByAppCompat); 266 } 267 } 268 269 // A medium IL token is not needed in the following cases. 270 // 1) We keep the process elevated (= LauncherFlags::eNoDeelevate) 271 // 2) The process was elevated by UAC (= ElevationState::eElevated) 272 // AND the launcher process doesn't wait for the browser process 273 if ((aFlags & mozilla::LauncherFlags::eNoDeelevate) || 274 (elevationState.value() == ElevationState::eElevated && 275 !(aFlags & mozilla::LauncherFlags::eWaitForBrowser))) { 276 return elevationState.value(); 277 } 278 279 LauncherResult<HANDLE> tokenResult = GetMediumIntegrityToken(token); 280 if (tokenResult.isOk()) { 281 aOutMediumIlToken.own(tokenResult.unwrap()); 282 } else { 283 return tokenResult.propagateErr(); 284 } 285 286 return elevationState.value(); 287 } 288 289 } // namespace mozilla