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
This commit is contained in:
Toshihito Kikuchi 2020-06-22 18:37:49 +00:00
parent cf10ddd578
commit dcef2560fb
3 changed files with 101 additions and 33 deletions

View File

@ -71,6 +71,48 @@ static mozilla::LauncherResult<HANDLE> GetMediumIntegrityToken(
return result.disown();
}
static mozilla::LauncherResult<bool> 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<wchar_t[]>(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<ElevationState> 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<ElevationState> GetElevationState(
return LAUNCHER_ERROR_FROM_RESULT(elevationType);
}
Maybe<ElevationState> 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<HANDLE> 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<bool> 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<bool> isHighIntegrity = IsHighIntegrity(token);
if (isHighIntegrity.isErr()) {
return LAUNCHER_ERROR_FROM_RESULT(isHighIntegrity);
LauncherResult<bool> 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<HANDLE> 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<HANDLE> tokenResult = GetMediumIntegrityToken(token);
if (tokenResult.isOk()) {
aOutMediumIlToken.own(tokenResult.unwrap());
} else {
return LAUNCHER_ERROR_FROM_RESULT(tokenResult);
}
return elevationState.value();
}
} // namespace mozilla

View File

@ -18,10 +18,12 @@ enum class ElevationState {
eNormalUser = 0,
eElevated = (1 << 0),
eHighIntegrityNoUAC = (1 << 1),
eHighIntegrityByAppCompat = (1 << 2),
};
LauncherResult<ElevationState> GetElevationState(
LauncherFlags aFlags, nsAutoHandle& aOutMediumIlToken);
const wchar_t* aExecutablePath, LauncherFlags aFlags,
nsAutoHandle& aOutMediumIlToken);
LauncherVoidResult LaunchUnelevated(int aArgc, wchar_t* aArgv[]);

View File

@ -270,7 +270,7 @@ Maybe<int> LauncherMain(int& argc, wchar_t* argv[],
nsAutoHandle mediumIlToken;
LauncherResult<ElevationState> elevationState =
GetElevationState(flags, mediumIlToken);
GetElevationState(argv[0], flags, mediumIlToken);
if (elevationState.isErr()) {
HandleLauncherError(elevationState);
return Nothing();