tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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