Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
#include "LaunchUnelevated.h"
#include "mozilla/Assertions.h"
#include "mozilla/CmdLineAndEnvUtils.h"
#include "mozilla/mscom/ProcessRuntime.h"
#include "mozilla/RefPtr.h"
#include "mozilla/ShellHeaderOnlyUtils.h"
#include "mozilla/WinHeaderOnlyUtils.h"
#include "../BrowserDefines.h"
#include "nsWindowsHelpers.h"
#include <windows.h>
#if !defined(RRF_SUBKEY_WOW6464KEY)
# define RRF_SUBKEY_WOW6464KEY 0x00010000
#endif // !defined(RRF_SUBKEY_WOW6464KEY)
static mozilla::LauncherResult<bool> IsHighIntegrity(
const nsAutoHandle& aToken) {
DWORD reqdLen;
if (!::GetTokenInformation(aToken.get(), TokenIntegrityLevel, nullptr, 0,
&reqdLen)) {
DWORD err = ::GetLastError();
if (err != ERROR_INSUFFICIENT_BUFFER) {
return LAUNCHER_ERROR_FROM_WIN32(err);
}
}
auto buf = mozilla::MakeUnique<char[]>(reqdLen);
if (!::GetTokenInformation(aToken.get(), TokenIntegrityLevel, buf.get(),
reqdLen, &reqdLen)) {
return LAUNCHER_ERROR_FROM_LAST();
}
auto tokenLabel = reinterpret_cast<PTOKEN_MANDATORY_LABEL>(buf.get());
DWORD subAuthCount = *::GetSidSubAuthorityCount(tokenLabel->Label.Sid);
DWORD integrityLevel =
*::GetSidSubAuthority(tokenLabel->Label.Sid, subAuthCount - 1);
return integrityLevel > SECURITY_MANDATORY_MEDIUM_RID;
}
static mozilla::LauncherResult<HANDLE> GetMediumIntegrityToken(
const nsAutoHandle& aProcessToken) {
HANDLE rawResult;
if (!::DuplicateTokenEx(aProcessToken.get(), 0, nullptr,
SecurityImpersonation, TokenPrimary, &rawResult)) {
return LAUNCHER_ERROR_FROM_LAST();
}
nsAutoHandle result(rawResult);
BYTE mediumIlSid[SECURITY_MAX_SID_SIZE];
DWORD mediumIlSidSize = sizeof(mediumIlSid);
if (!::CreateWellKnownSid(WinMediumLabelSid, nullptr, mediumIlSid,
&mediumIlSidSize)) {
return LAUNCHER_ERROR_FROM_LAST();
}
TOKEN_MANDATORY_LABEL integrityLevel = {};
integrityLevel.Label.Attributes = SE_GROUP_INTEGRITY;
integrityLevel.Label.Sid = reinterpret_cast<PSID>(mediumIlSid);
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.
LauncherVoidResult LaunchUnelevated(int aArgc, wchar_t* aArgv[]) {
// We need COM to talk to Explorer. Using ProcessRuntime so that
// process-global COM configuration is done correctly
mozilla::mscom::ProcessRuntime mscom(
mozilla::mscom::ProcessRuntime::ProcessCategory::Launcher);
if (!mscom) {
return LAUNCHER_ERROR_FROM_HRESULT(mscom.GetHResult());
}
// Omit the original argv[0] because ShellExecute doesn't need it. Insert
// ATTEMPTING_DEELEVATION_FLAG so that we know not to attempt to restart
// ourselves if deelevation fails.
UniquePtr<wchar_t[]> cmdLine = [&]() {
constexpr wchar_t const* kTagArg = L"--" ATTEMPTING_DEELEVATION_FLAG;
// This should have already been checked, but just in case...
EnsureBrowserCommandlineSafe(aArgc, aArgv);
if (mozilla::CheckArg(aArgc, aArgv, "osint", nullptr, CheckArgFlag::None)) {
// If the command line contains -osint, we have to arrange things in a
// particular order.
//
// (We can't just replace -osint with kTagArg, unfortunately: there is
// code in the browser which behaves differently in the presence of an
// `-osint` tag, but which will not have had a chance to react to this.
// See, _e.g._, bug 1243603.)
auto const aArgvCopy = MakeUnique<wchar_t const*[]>(aArgc + 1);
aArgvCopy[0] = aArgv[1];
aArgvCopy[1] = kTagArg;
for (int i = 2; i < aArgc; ++i) {
aArgvCopy[i] = aArgv[i];
}
aArgvCopy[aArgc] = nullptr; // because argv[argc] is NULL
return MakeCommandLine(aArgc, aArgvCopy.get(), 0, nullptr);
} else {
// Otherwise, just tack it on at the end.
constexpr wchar_t const* const kTagArgArray[] = {kTagArg};
return MakeCommandLine(aArgc - 1, aArgv + 1, 1, kTagArgArray);
}
}();
if (!cmdLine) {
return LAUNCHER_ERROR_GENERIC();
}
_bstr_t cmd;
UniquePtr<wchar_t[]> packageFamilyName = mozilla::GetPackageFamilyName();
if (packageFamilyName) {
int cmdLen =
// 22 for the prefix + suffix + null terminator below
22 + wcslen(packageFamilyName.get());
wchar_t appCmd[cmdLen];
swprintf(appCmd, cmdLen, L"shell:appsFolder\\%s!App",
packageFamilyName.get());
cmd = appCmd;
} else {
cmd = aArgv[0];
}
_variant_t args(cmdLine.get());
_variant_t operation(L"open");
_variant_t directory;
_variant_t showCmd(SW_SHOWNORMAL);
return ShellExecuteByExplorer(cmd, args, operation, directory, showCmd);
}
LauncherResult<ElevationState> GetElevationState(
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 elevationType.propagateErr();
}
Maybe<ElevationState> elevationState;
switch (elevationType.unwrap()) {
case TokenElevationTypeLimited:
return ElevationState::eNormalUser;
case TokenElevationTypeFull:
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 isHighIntegrity.propagateErr();
}
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();
}
MOZ_ASSERT(elevationState.isSome() &&
elevationState.value() != ElevationState::eNormalUser,
"Should have returned earlier for the eNormalUser case.");
LauncherResult<bool> isAdminByAppCompat =
IsAdminByAppCompat(HKEY_CURRENT_USER, aExecutablePath);
if (isAdminByAppCompat.isErr()) {
return isAdminByAppCompat.propagateErr();
}
if (isAdminByAppCompat.unwrap()) {
elevationState = Some(ElevationState::eHighIntegrityByAppCompat);
} else {
isAdminByAppCompat =
IsAdminByAppCompat(HKEY_LOCAL_MACHINE, aExecutablePath);
if (isAdminByAppCompat.isErr()) {
return isAdminByAppCompat.propagateErr();
}
if (isAdminByAppCompat.unwrap()) {
elevationState = Some(ElevationState::eHighIntegrityByAppCompat);
}
}
// 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 tokenResult.propagateErr();
}
return elevationState.value();
}
} // namespace mozilla