From dcef2560fbb947477964cc0a18f1f06dc4449eb5 Mon Sep 17 00:00:00 2001 From: Toshihito Kikuchi Date: Mon, 22 Jun 2020 18:37:49 +0000 Subject: [PATCH] Bug 1642577 - De-elevate the process with CreateProcessAsUser if the compat flag RUNASADMIN is set. r=aklotz If the process was elevated due to AppCompatFlags, we should not use LaunchUnelevated to launch the browser process because it starts an infinite loop of process launch. The fix is to make GetElevationState return a new elevation state if RUNASADMIN is set in AppCompatFlags. With that state, we use CreateProcessAsUser to launch the browser process. Differential Revision: https://phabricator.services.mozilla.com/D80114 --- browser/app/winlauncher/LaunchUnelevated.cpp | 128 +++++++++++++----- browser/app/winlauncher/LaunchUnelevated.h | 4 +- .../app/winlauncher/LauncherProcessWin.cpp | 2 +- 3 files changed, 101 insertions(+), 33 deletions(-) diff --git a/browser/app/winlauncher/LaunchUnelevated.cpp b/browser/app/winlauncher/LaunchUnelevated.cpp index 07d9cecb0a1c..4832cea0f4f6 100644 --- a/browser/app/winlauncher/LaunchUnelevated.cpp +++ b/browser/app/winlauncher/LaunchUnelevated.cpp @@ -71,6 +71,48 @@ static mozilla::LauncherResult GetMediumIntegrityToken( return result.disown(); } +static mozilla::LauncherResult IsAdminByAppCompat( + HKEY aRootKey, const wchar_t* aExecutablePath) { + static const wchar_t kPathToLayers[] = + L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\" + L"AppCompatFlags\\Layers"; + + DWORD dataLength = 0; + LSTATUS status = ::RegGetValueW(aRootKey, kPathToLayers, aExecutablePath, + RRF_RT_REG_SZ | RRF_SUBKEY_WOW6464KEY, + nullptr, nullptr, &dataLength); + if (status == ERROR_FILE_NOT_FOUND) { + return false; + } else if (status != ERROR_SUCCESS) { + return LAUNCHER_ERROR_FROM_WIN32(status); + } + + auto valueData = mozilla::MakeUnique(dataLength); + if (!valueData) { + return LAUNCHER_ERROR_FROM_WIN32(ERROR_OUTOFMEMORY); + } + + status = ::RegGetValueW(aRootKey, kPathToLayers, aExecutablePath, + RRF_RT_REG_SZ | RRF_SUBKEY_WOW6464KEY, nullptr, + valueData.get(), &dataLength); + if (status != ERROR_SUCCESS) { + return LAUNCHER_ERROR_FROM_WIN32(status); + } + + const wchar_t kRunAsAdmin[] = L"RUNASADMIN"; + const wchar_t kDelimiters[] = L" "; + wchar_t* tokenContext = nullptr; + const wchar_t* token = wcstok_s(valueData.get(), kDelimiters, &tokenContext); + while (token) { + if (!_wcsnicmp(token, kRunAsAdmin, mozilla::ArrayLength(kRunAsAdmin))) { + return true; + } + token = wcstok_s(nullptr, kDelimiters, &tokenContext); + } + + return false; +} + namespace mozilla { // If we're running at an elevated integrity level, re-run ourselves at the @@ -104,7 +146,8 @@ LauncherVoidResult LaunchUnelevated(int aArgc, wchar_t* aArgv[]) { } LauncherResult GetElevationState( - mozilla::LauncherFlags aFlags, nsAutoHandle& aOutMediumIlToken) { + const wchar_t* aExecutablePath, mozilla::LauncherFlags aFlags, + nsAutoHandle& aOutMediumIlToken) { aOutMediumIlToken.reset(); const DWORD tokenFlags = TOKEN_QUERY | TOKEN_DUPLICATE | @@ -121,55 +164,78 @@ LauncherResult GetElevationState( return LAUNCHER_ERROR_FROM_RESULT(elevationType); } + Maybe elevationState; switch (elevationType.unwrap()) { case TokenElevationTypeLimited: return ElevationState::eNormalUser; case TokenElevationTypeFull: - // If we want to start a non-elevated browser process and wait on it, - // we're going to need a medium IL token. - if ((aFlags & (mozilla::LauncherFlags::eWaitForBrowser | - mozilla::LauncherFlags::eNoDeelevate)) == - mozilla::LauncherFlags::eWaitForBrowser) { - LauncherResult tokenResult = GetMediumIntegrityToken(token); - if (tokenResult.isOk()) { - aOutMediumIlToken.own(tokenResult.unwrap()); - } else { - return LAUNCHER_ERROR_FROM_RESULT(tokenResult); - } + elevationState = Some(ElevationState::eElevated); + break; + case TokenElevationTypeDefault: { + // In this case, UAC is disabled. We do not yet know whether or not we + // are running at high integrity. If we are at high integrity, we can't + // relaunch ourselves in a non-elevated state via Explorer, as we would + // just end up in an infinite loop of launcher processes re-launching + // themselves. + LauncherResult isHighIntegrity = IsHighIntegrity(token); + if (isHighIntegrity.isErr()) { + return LAUNCHER_ERROR_FROM_RESULT(isHighIntegrity); } - return ElevationState::eElevated; - case TokenElevationTypeDefault: + if (!isHighIntegrity.unwrap()) { + return ElevationState::eNormalUser; + } + + elevationState = Some(ElevationState::eHighIntegrityNoUAC); break; + } default: MOZ_ASSERT_UNREACHABLE("Was a new value added to the enumeration?"); return LAUNCHER_ERROR_GENERIC(); } - // In this case, UAC is disabled. We do not yet know whether or not we are - // running at high integrity. If we are at high integrity, we can't relaunch - // ourselves in a non-elevated state via Explorer, as we would just end up in - // an infinite loop of launcher processes re-launching themselves. + MOZ_ASSERT(elevationState.isSome() && + elevationState.value() != ElevationState::eNormalUser, + "Should have returned earlier for the eNormalUser case."); - LauncherResult isHighIntegrity = IsHighIntegrity(token); - if (isHighIntegrity.isErr()) { - return LAUNCHER_ERROR_FROM_RESULT(isHighIntegrity); + LauncherResult isAdminByAppCompat = + IsAdminByAppCompat(HKEY_CURRENT_USER, aExecutablePath); + if (isAdminByAppCompat.isErr()) { + return LAUNCHER_ERROR_FROM_RESULT(isAdminByAppCompat); } - if (!isHighIntegrity.unwrap()) { - return ElevationState::eNormalUser; - } + if (isAdminByAppCompat.unwrap()) { + elevationState = Some(ElevationState::eHighIntegrityByAppCompat); + } else { + isAdminByAppCompat = + IsAdminByAppCompat(HKEY_LOCAL_MACHINE, aExecutablePath); + if (isAdminByAppCompat.isErr()) { + return LAUNCHER_ERROR_FROM_RESULT(isAdminByAppCompat); + } - if (!(aFlags & mozilla::LauncherFlags::eNoDeelevate)) { - LauncherResult tokenResult = GetMediumIntegrityToken(token); - if (tokenResult.isOk()) { - aOutMediumIlToken.own(tokenResult.unwrap()); - } else { - return LAUNCHER_ERROR_FROM_RESULT(tokenResult); + if (isAdminByAppCompat.unwrap()) { + elevationState = Some(ElevationState::eHighIntegrityByAppCompat); } } - return ElevationState::eHighIntegrityNoUAC; + // A medium IL token is not needed in the following cases. + // 1) We keep the process elevated (= LauncherFlags::eNoDeelevate) + // 2) The process was elevated by UAC (= ElevationState::eElevated) + // AND the launcher process doesn't wait for the browser process + if ((aFlags & mozilla::LauncherFlags::eNoDeelevate) || + (elevationState.value() == ElevationState::eElevated && + !(aFlags & mozilla::LauncherFlags::eWaitForBrowser))) { + return elevationState.value(); + } + + LauncherResult tokenResult = GetMediumIntegrityToken(token); + if (tokenResult.isOk()) { + aOutMediumIlToken.own(tokenResult.unwrap()); + } else { + return LAUNCHER_ERROR_FROM_RESULT(tokenResult); + } + + return elevationState.value(); } } // namespace mozilla diff --git a/browser/app/winlauncher/LaunchUnelevated.h b/browser/app/winlauncher/LaunchUnelevated.h index c5e0594d2fe9..8c6679edf3a8 100644 --- a/browser/app/winlauncher/LaunchUnelevated.h +++ b/browser/app/winlauncher/LaunchUnelevated.h @@ -18,10 +18,12 @@ enum class ElevationState { eNormalUser = 0, eElevated = (1 << 0), eHighIntegrityNoUAC = (1 << 1), + eHighIntegrityByAppCompat = (1 << 2), }; LauncherResult GetElevationState( - LauncherFlags aFlags, nsAutoHandle& aOutMediumIlToken); + const wchar_t* aExecutablePath, LauncherFlags aFlags, + nsAutoHandle& aOutMediumIlToken); LauncherVoidResult LaunchUnelevated(int aArgc, wchar_t* aArgv[]); diff --git a/browser/app/winlauncher/LauncherProcessWin.cpp b/browser/app/winlauncher/LauncherProcessWin.cpp index ee2c290bfc70..7a8b4a621dd4 100644 --- a/browser/app/winlauncher/LauncherProcessWin.cpp +++ b/browser/app/winlauncher/LauncherProcessWin.cpp @@ -270,7 +270,7 @@ Maybe LauncherMain(int& argc, wchar_t* argv[], nsAutoHandle mediumIlToken; LauncherResult elevationState = - GetElevationState(flags, mediumIlToken); + GetElevationState(argv[0], flags, mediumIlToken); if (elevationState.isErr()) { HandleLauncherError(elevationState); return Nothing();