Bug 1642577 - De-elevate the process with CreateProcessAsUser if the compat flag RUNASADMIN is set. r=aklotz
authorToshihito Kikuchi <tkikuchi@mozilla.com>
Mon, 22 Jun 2020 18:37:49 +0000
changeset 536639 f612d453e009554f7d9a375ebf01e8606178c38c
parent 536638 70a0f7fd1803d814072439bedd70160da691cc7a
child 536640 5065d048c9304bd0a9e0fa3753f30c00a686da84
push id37531
push usernbeleuzu@mozilla.com
push dateTue, 23 Jun 2020 03:44:39 +0000
treeherdermozilla-central@b1146cce5053 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaklotz
bugs1642577
milestone79.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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
browser/app/winlauncher/LaunchUnelevated.h
browser/app/winlauncher/LauncherProcessWin.cpp
--- a/browser/app/winlauncher/LaunchUnelevated.cpp
+++ b/browser/app/winlauncher/LaunchUnelevated.cpp
@@ -66,16 +66,58 @@ static mozilla::LauncherResult<HANDLE> G
   if (!::SetTokenInformation(rawResult, TokenIntegrityLevel, &integrityLevel,
                              sizeof(integrityLevel))) {
     return LAUNCHER_ERROR_FROM_LAST();
   }
 
   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
 // user's normal integrity level. We do this by locating the active explorer
 // shell, and then asking it to do a ShellExecute on our behalf. We do it this
 // way to ensure that the child process runs as the original user in the active
 // session; an elevated process could be running with different credentials than
 // those of the session.
@@ -99,77 +141,101 @@ LauncherVoidResult LaunchUnelevated(int 
   _variant_t args(cmdLine.get());
   _variant_t operation(L"open");
   _variant_t directory;
   _variant_t showCmd(SW_SHOWNORMAL);
   return ShellExecuteByExplorer(exe, args, operation, directory, showCmd);
 }
 
 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 |
                            TOKEN_ADJUST_DEFAULT | TOKEN_ASSIGN_PRIMARY;
   HANDLE rawToken;
   if (!::OpenProcessToken(::GetCurrentProcess(), tokenFlags, &rawToken)) {
     return LAUNCHER_ERROR_FROM_LAST();
   }
 
   nsAutoHandle token(rawToken);
 
   LauncherResult<TOKEN_ELEVATION_TYPE> elevationType = GetElevationType(token);
   if (elevationType.isErr()) {
     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
--- a/browser/app/winlauncher/LaunchUnelevated.h
+++ b/browser/app/winlauncher/LaunchUnelevated.h
@@ -13,18 +13,20 @@
 #include "nsWindowsHelpers.h"
 
 namespace mozilla {
 
 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[]);
 
 }  // namespace mozilla
 
 #endif  // mozilla_LaunchUnelevated_h
--- a/browser/app/winlauncher/LauncherProcessWin.cpp
+++ b/browser/app/winlauncher/LauncherProcessWin.cpp
@@ -265,17 +265,17 @@ Maybe<int> LauncherMain(int& argc, wchar
     HandleLauncherError(LAUNCHER_ERROR_GENERIC());
     return Nothing();
   }
 
   LauncherFlags flags = ProcessCmdLine(argc, argv);
 
   nsAutoHandle mediumIlToken;
   LauncherResult<ElevationState> elevationState =
-      GetElevationState(flags, mediumIlToken);
+      GetElevationState(argv[0], flags, mediumIlToken);
   if (elevationState.isErr()) {
     HandleLauncherError(elevationState);
     return Nothing();
   }
 
   // If we're elevated, we should relaunch ourselves as a normal user.
   // Note that we only call LaunchUnelevated when we don't need to wait for the
   // browser process.