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 "LauncherRegistryInfo.h"
#include "commonupdatedir.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/Assertions.h"
#include "mozilla/NativeNt.h"
#include "mozilla/UniquePtr.h"
#include <cwctype>
#include <shlobj.h>
#include <string>
#include <type_traits>
#define EXPAND_STRING_MACRO2(t) t
#define EXPAND_STRING_MACRO(t) EXPAND_STRING_MACRO2(t)
// This function is copied from Chromium base/time/time_win.cc
// Returns the current value of the performance counter.
static uint64_t QPCNowRaw() {
LARGE_INTEGER perf_counter_now = {};
// According to the MSDN documentation for QueryPerformanceCounter(), this
// will never fail on systems that run XP or later.
::QueryPerformanceCounter(&perf_counter_now);
return perf_counter_now.QuadPart;
}
static mozilla::LauncherResult<DWORD> GetCurrentImageTimestamp() {
mozilla::nt::PEHeaders headers(::GetModuleHandleW(nullptr));
if (!headers) {
return LAUNCHER_ERROR_FROM_WIN32(ERROR_BAD_EXE_FORMAT);
}
DWORD timestamp;
if (!headers.GetTimeStamp(timestamp)) {
return LAUNCHER_ERROR_FROM_WIN32(ERROR_INVALID_DATA);
}
return timestamp;
}
template <typename T>
static mozilla::LauncherResult<mozilla::Maybe<T>> ReadRegistryValueData(
const nsAutoRegKey& key, const std::wstring& name, DWORD expectedType) {
static_assert(std::is_trivial_v<T> && std::is_standard_layout_v<T>,
"Registry value type must be primitive.");
T data;
DWORD dataLen = sizeof(data);
DWORD type;
LSTATUS status = ::RegQueryValueExW(key.get(), name.c_str(), nullptr, &type,
reinterpret_cast<PBYTE>(&data), &dataLen);
if (status == ERROR_FILE_NOT_FOUND) {
return mozilla::Maybe<T>();
}
if (status != ERROR_SUCCESS) {
return LAUNCHER_ERROR_FROM_WIN32(status);
}
if (type != expectedType) {
return LAUNCHER_ERROR_FROM_WIN32(ERROR_DATATYPE_MISMATCH);
}
return mozilla::Some(data);
}
static mozilla::LauncherResult<mozilla::UniquePtr<wchar_t[]>>
ReadRegistryValueString(const nsAutoRegKey& aKey, const std::wstring& aName) {
mozilla::UniquePtr<wchar_t[]> buf;
DWORD dataLen;
LSTATUS status = ::RegGetValueW(aKey.get(), nullptr, aName.c_str(),
RRF_RT_REG_SZ, nullptr, nullptr, &dataLen);
if (status == ERROR_FILE_NOT_FOUND) {
return buf;
}
if (status != ERROR_SUCCESS) {
return LAUNCHER_ERROR_FROM_WIN32(status);
}
buf = mozilla::MakeUnique<wchar_t[]>(dataLen / sizeof(wchar_t));
status = ::RegGetValueW(aKey.get(), nullptr, aName.c_str(), RRF_RT_REG_SZ,
nullptr, buf.get(), &dataLen);
if (status != ERROR_SUCCESS) {
return LAUNCHER_ERROR_FROM_WIN32(status);
}
return buf;
}
static mozilla::LauncherVoidResult WriteRegistryValueString(
const nsAutoRegKey& aKey, const std::wstring& aName,
const std::wstring& aValue) {
DWORD dataBytes = (aValue.size() + 1) * sizeof(wchar_t);
LSTATUS status = ::RegSetValueExW(
aKey.get(), aName.c_str(), /*Reserved*/ 0, REG_SZ,
reinterpret_cast<const BYTE*>(aValue.c_str()), dataBytes);
if (status != ERROR_SUCCESS) {
return LAUNCHER_ERROR_FROM_WIN32(status);
}
return mozilla::Ok();
}
template <typename T>
static mozilla::LauncherVoidResult WriteRegistryValueData(
const nsAutoRegKey& key, const std::wstring& name, DWORD type, T data) {
static_assert(std::is_trivial_v<T> && std::is_standard_layout_v<T>,
"Registry value type must be primitive.");
LSTATUS status =
::RegSetValueExW(key.get(), name.c_str(), 0, type,
reinterpret_cast<PBYTE>(&data), sizeof(data));
if (status != ERROR_SUCCESS) {
return LAUNCHER_ERROR_FROM_WIN32(status);
}
return mozilla::Ok();
}
static mozilla::LauncherResult<bool> DeleteRegistryValueData(
const nsAutoRegKey& key, const std::wstring& name) {
LSTATUS status = ::RegDeleteValueW(key, name.c_str());
if (status == ERROR_FILE_NOT_FOUND) {
return false;
}
if (status != ERROR_SUCCESS) {
return LAUNCHER_ERROR_FROM_WIN32(status);
}
return true;
}
namespace mozilla {
const wchar_t LauncherRegistryInfo::kLauncherSubKeyPath[] =
L"SOFTWARE\\" EXPAND_STRING_MACRO(MOZ_APP_VENDOR) L"\\" EXPAND_STRING_MACRO(
MOZ_APP_BASENAME) L"\\Launcher";
const wchar_t LauncherRegistryInfo::kLauncherSuffix[] = L"|Launcher";
const wchar_t LauncherRegistryInfo::kBrowserSuffix[] = L"|Browser";
const wchar_t LauncherRegistryInfo::kImageTimestampSuffix[] = L"|Image";
const wchar_t LauncherRegistryInfo::kTelemetrySuffix[] = L"|Telemetry";
const wchar_t LauncherRegistryInfo::kBlocklistSuffix[] = L"|Blocklist";
bool LauncherRegistryInfo::sAllowCommit = true;
LauncherResult<LauncherRegistryInfo::Disposition> LauncherRegistryInfo::Open() {
if (!!mRegKey) {
return Disposition::OpenedExisting;
}
DWORD disposition;
HKEY rawKey;
LSTATUS result = ::RegCreateKeyExW(
HKEY_CURRENT_USER, kLauncherSubKeyPath, 0, nullptr,
REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, nullptr, &rawKey, &disposition);
if (result != ERROR_SUCCESS) {
return LAUNCHER_ERROR_FROM_WIN32(result);
}
mRegKey.own(rawKey);
switch (disposition) {
case REG_CREATED_NEW_KEY:
return Disposition::CreatedNew;
case REG_OPENED_EXISTING_KEY:
return Disposition::OpenedExisting;
default:
break;
}
MOZ_ASSERT_UNREACHABLE("Invalid disposition from RegCreateKeyExW");
return LAUNCHER_ERROR_GENERIC();
}
LauncherVoidResult LauncherRegistryInfo::ReflectPrefToRegistry(
const bool aEnable) {
LauncherResult<EnabledState> curEnabledState = IsEnabled();
if (curEnabledState.isErr()) {
return curEnabledState.propagateErr();
}
bool isCurrentlyEnabled =
curEnabledState.inspect() != EnabledState::ForceDisabled;
if (isCurrentlyEnabled == aEnable) {
// Don't reflect to the registry unless the new enabled state is actually
// changing with respect to the current enabled state.
return Ok();
}
// Always delete the launcher timestamp
LauncherResult<bool> clearedLauncherTimestamp = ClearLauncherStartTimestamp();
MOZ_ASSERT(clearedLauncherTimestamp.isOk());
if (clearedLauncherTimestamp.isErr()) {
return clearedLauncherTimestamp.propagateErr();
}
// Allow commit when we enable the launcher, otherwise block.
sAllowCommit = aEnable;
if (!aEnable) {
// Set the browser timestamp to 0 to indicate force-disabled
return WriteBrowserStartTimestamp(0ULL);
}
// Otherwise we delete the browser timestamp to start over fresh
LauncherResult<bool> clearedBrowserTimestamp = ClearBrowserStartTimestamp();
MOZ_ASSERT(clearedBrowserTimestamp.isOk());
if (clearedBrowserTimestamp.isErr()) {
return clearedBrowserTimestamp.propagateErr();
}
return Ok();
}
LauncherVoidResult LauncherRegistryInfo::ReflectTelemetryPrefToRegistry(
const bool aEnable) {
LauncherResult<Disposition> disposition = Open();
if (disposition.isErr()) {
return disposition.propagateErr();
}
return WriteRegistryValueData(mRegKey, ResolveTelemetryValueName(), REG_DWORD,
aEnable ? 1UL : 0UL);
}
LauncherResult<LauncherRegistryInfo::ProcessType> LauncherRegistryInfo::Check(
const ProcessType aDesiredType, const CheckOption aOption) {
LauncherResult<Disposition> disposition = Open();
if (disposition.isErr()) {
return disposition.propagateErr();
}
LauncherResult<DWORD> ourImageTimestamp = GetCurrentImageTimestamp();
if (ourImageTimestamp.isErr()) {
return ourImageTimestamp.propagateErr();
}
LauncherResult<Maybe<DWORD>> savedImageTimestamp = GetSavedImageTimestamp();
if (savedImageTimestamp.isErr()) {
return savedImageTimestamp.propagateErr();
}
// If we don't have a saved timestamp, or we do but it doesn't match with
// our current timestamp, clear previous values unless we're force-disabled.
if (savedImageTimestamp.inspect().isNothing() ||
savedImageTimestamp.inspect().value() != ourImageTimestamp.inspect()) {
LauncherVoidResult clearResult = ClearStartTimestamps();
if (clearResult.isErr()) {
return clearResult.propagateErr();
}
LauncherVoidResult writeResult =
WriteImageTimestamp(ourImageTimestamp.inspect());
if (writeResult.isErr()) {
return writeResult.propagateErr();
}
}
// If we're going to be running as the browser process, or there is no
// existing values to check, just write our timestamp and return.
if (aDesiredType == ProcessType::Browser) {
mBrowserTimestampToWrite = Some(QPCNowRaw());
return ProcessType::Browser;
}
if (disposition.inspect() == Disposition::CreatedNew) {
mLauncherTimestampToWrite = Some(QPCNowRaw());
return ProcessType::Launcher;
}
if (disposition.inspect() != Disposition::OpenedExisting) {
MOZ_ASSERT_UNREACHABLE("Invalid |disposition|");
return LAUNCHER_ERROR_GENERIC();
}
LauncherResult<Maybe<uint64_t>> lastLauncherTimestampResult =
GetLauncherStartTimestamp();
if (lastLauncherTimestampResult.isErr()) {
return lastLauncherTimestampResult.propagateErr();
}
LauncherResult<Maybe<uint64_t>> lastBrowserTimestampResult =
GetBrowserStartTimestamp();
if (lastBrowserTimestampResult.isErr()) {
return lastBrowserTimestampResult.propagateErr();
}
const Maybe<uint64_t>& lastLauncherTimestamp =
lastLauncherTimestampResult.inspect();
const Maybe<uint64_t>& lastBrowserTimestamp =
lastBrowserTimestampResult.inspect();
ProcessType typeToRunAs = aDesiredType;
if (lastLauncherTimestamp.isSome() != lastBrowserTimestamp.isSome()) {
// If we have a launcher timestamp but no browser timestamp (or vice versa),
// that's bad because it is indicating that the browser can't run with
// the launcher process.
typeToRunAs = ProcessType::Browser;
} else if (lastLauncherTimestamp.isSome()) {
// if we have both timestamps, we want to ensure that the launcher timestamp
// is earlier than the browser timestamp.
if (aDesiredType == ProcessType::Launcher) {
bool areTimestampsOk =
lastLauncherTimestamp.value() < lastBrowserTimestamp.value();
if (!areTimestampsOk) {
typeToRunAs = ProcessType::Browser;
}
}
} else {
// If we have neither timestamp, then we should try running as suggested
// by |aDesiredType|.
// We shouldn't really have this scenario unless we're going to be running
// as the launcher process.
MOZ_ASSERT(typeToRunAs == ProcessType::Launcher);
// No change to typeToRunAs
}
// Debugging setting that forces the desired type regardless of the various
// tests that have been performed.
if (aOption == CheckOption::Force) {
typeToRunAs = aDesiredType;
}
switch (typeToRunAs) {
case ProcessType::Browser:
if (aDesiredType != typeToRunAs) {
// We were hoping to run as the launcher, but some failure has caused
// us to run as the browser. Set the browser timestamp to zero as an
// indicator.
mBrowserTimestampToWrite = Some(0ULL);
} else {
mBrowserTimestampToWrite = Some(QPCNowRaw());
}
break;
case ProcessType::Launcher:
mLauncherTimestampToWrite = Some(QPCNowRaw());
break;
default:
MOZ_ASSERT_UNREACHABLE("Invalid |typeToRunAs|");
return LAUNCHER_ERROR_GENERIC();
}
return typeToRunAs;
}
LauncherVoidResult LauncherRegistryInfo::DisableDueToFailure() {
LauncherResult<Disposition> disposition = Open();
if (disposition.isErr()) {
return disposition.propagateErr();
}
LauncherVoidResult result = WriteBrowserStartTimestamp(0ULL);
if (result.isOk()) {
// Block commit when we disable the launcher. It could be allowed
// when the image timestamp is updated.
sAllowCommit = false;
}
return result;
}
LauncherVoidResult LauncherRegistryInfo::Commit() {
if (!sAllowCommit) {
Abort();
return Ok();
}
LauncherResult<Disposition> disposition = Open();
if (disposition.isErr()) {
return disposition.propagateErr();
}
if (mLauncherTimestampToWrite.isSome()) {
LauncherVoidResult writeResult =
WriteLauncherStartTimestamp(mLauncherTimestampToWrite.value());
if (writeResult.isErr()) {
return writeResult.propagateErr();
}
mLauncherTimestampToWrite = Nothing();
}
if (mBrowserTimestampToWrite.isSome()) {
LauncherVoidResult writeResult =
WriteBrowserStartTimestamp(mBrowserTimestampToWrite.value());
if (writeResult.isErr()) {
return writeResult.propagateErr();
}
mBrowserTimestampToWrite = Nothing();
}
return Ok();
}
void LauncherRegistryInfo::Abort() {
mLauncherTimestampToWrite = mBrowserTimestampToWrite = Nothing();
}
LauncherRegistryInfo::EnabledState LauncherRegistryInfo::GetEnabledState(
const Maybe<uint64_t>& aLauncherTs, const Maybe<uint64_t>& aBrowserTs) {
if (aBrowserTs.isSome()) {
if (aLauncherTs.isSome()) {
if (aLauncherTs.value() < aBrowserTs.value()) {
// Both timestamps exist and the browser's timestamp is later.
return EnabledState::Enabled;
}
} else if (aBrowserTs.value() == 0ULL) {
// Only browser's timestamp exists and its value is 0.
return EnabledState::ForceDisabled;
}
} else if (aLauncherTs.isNothing()) {
// Neither timestamps exist.
return EnabledState::Enabled;
}
// Everything else is FailDisabled.
return EnabledState::FailDisabled;
}
LauncherResult<LauncherRegistryInfo::EnabledState>
LauncherRegistryInfo::IsEnabled() {
LauncherResult<Disposition> disposition = Open();
if (disposition.isErr()) {
return disposition.propagateErr();
}
LauncherResult<Maybe<uint64_t>> lastLauncherTimestamp =
GetLauncherStartTimestamp();
if (lastLauncherTimestamp.isErr()) {
return lastLauncherTimestamp.propagateErr();
}
LauncherResult<Maybe<uint64_t>> lastBrowserTimestamp =
GetBrowserStartTimestamp();
if (lastBrowserTimestamp.isErr()) {
return lastBrowserTimestamp.propagateErr();
}
return GetEnabledState(lastLauncherTimestamp.inspect(),
lastBrowserTimestamp.inspect());
}
LauncherResult<bool> LauncherRegistryInfo::IsTelemetryEnabled() {
LauncherResult<Disposition> disposition = Open();
if (disposition.isErr()) {
return disposition.propagateErr();
}
LauncherResult<Maybe<DWORD>> result = ReadRegistryValueData<DWORD>(
mRegKey, ResolveTelemetryValueName(), REG_DWORD);
if (result.isErr()) {
return result.propagateErr();
}
if (result.inspect().isNothing()) {
// Value does not exist, treat as false
return false;
}
return result.inspect().value() != 0;
}
const std::wstring& LauncherRegistryInfo::ResolveLauncherValueName() {
if (mLauncherValueName.empty()) {
mLauncherValueName.assign(mBinPath);
mLauncherValueName.append(kLauncherSuffix,
ArrayLength(kLauncherSuffix) - 1);
}
return mLauncherValueName;
}
const std::wstring& LauncherRegistryInfo::ResolveBrowserValueName() {
if (mBrowserValueName.empty()) {
mBrowserValueName.assign(mBinPath);
mBrowserValueName.append(kBrowserSuffix, ArrayLength(kBrowserSuffix) - 1);
}
return mBrowserValueName;
}
const std::wstring& LauncherRegistryInfo::ResolveImageTimestampValueName() {
if (mImageValueName.empty()) {
mImageValueName.assign(mBinPath);
mImageValueName.append(kImageTimestampSuffix,
ArrayLength(kImageTimestampSuffix) - 1);
}
return mImageValueName;
}
const std::wstring& LauncherRegistryInfo::ResolveTelemetryValueName() {
if (mTelemetryValueName.empty()) {
mTelemetryValueName.assign(mBinPath);
mTelemetryValueName.append(kTelemetrySuffix,
ArrayLength(kTelemetrySuffix) - 1);
}
return mTelemetryValueName;
}
const std::wstring& LauncherRegistryInfo::ResolveBlocklistValueName() {
if (mBlocklistValueName.empty()) {
mBlocklistValueName.assign(mBinPath);
mBlocklistValueName.append(kBlocklistSuffix,
ArrayLength(kBlocklistSuffix) - 1);
}
return mBlocklistValueName;
}
LauncherVoidResult LauncherRegistryInfo::WriteLauncherStartTimestamp(
uint64_t aValue) {
return WriteRegistryValueData(mRegKey, ResolveLauncherValueName(), REG_QWORD,
aValue);
}
LauncherVoidResult LauncherRegistryInfo::WriteBrowserStartTimestamp(
uint64_t aValue) {
return WriteRegistryValueData(mRegKey, ResolveBrowserValueName(), REG_QWORD,
aValue);
}
LauncherVoidResult LauncherRegistryInfo::WriteImageTimestamp(DWORD aTimestamp) {
return WriteRegistryValueData(mRegKey, ResolveImageTimestampValueName(),
REG_DWORD, aTimestamp);
}
LauncherResult<bool> LauncherRegistryInfo::ClearLauncherStartTimestamp() {
return DeleteRegistryValueData(mRegKey, ResolveLauncherValueName());
}
LauncherResult<bool> LauncherRegistryInfo::ClearBrowserStartTimestamp() {
return DeleteRegistryValueData(mRegKey, ResolveBrowserValueName());
}
LauncherVoidResult LauncherRegistryInfo::ClearStartTimestamps() {
LauncherResult<EnabledState> enabled = IsEnabled();
if (enabled.isOk() && enabled.inspect() == EnabledState::ForceDisabled) {
// We don't clear anything when we're force disabled - we need to maintain
// the current registry state in this case.
return Ok();
}
LauncherResult<bool> clearedLauncherTimestamp = ClearLauncherStartTimestamp();
if (clearedLauncherTimestamp.isErr()) {
return clearedLauncherTimestamp.propagateErr();
}
LauncherResult<bool> clearedBrowserTimestamp = ClearBrowserStartTimestamp();
if (clearedBrowserTimestamp.isErr()) {
return clearedBrowserTimestamp.propagateErr();
}
// Reset both timestamps to align with registry deletion
mLauncherTimestampToWrite = mBrowserTimestampToWrite = Nothing();
// Disablement is gone. Let's allow commit.
sAllowCommit = true;
return Ok();
}
LauncherResult<Maybe<DWORD>> LauncherRegistryInfo::GetSavedImageTimestamp() {
return ReadRegistryValueData<DWORD>(mRegKey, ResolveImageTimestampValueName(),
REG_DWORD);
}
LauncherResult<Maybe<uint64_t>>
LauncherRegistryInfo::GetLauncherStartTimestamp() {
return ReadRegistryValueData<uint64_t>(mRegKey, ResolveLauncherValueName(),
REG_QWORD);
}
LauncherResult<Maybe<uint64_t>>
LauncherRegistryInfo::GetBrowserStartTimestamp() {
return ReadRegistryValueData<uint64_t>(mRegKey, ResolveBrowserValueName(),
REG_QWORD);
}
LauncherResult<std::wstring>
LauncherRegistryInfo::BuildDefaultBlocklistFilename() {
// These flags are chosen to avoid I/O, see bug 1363398.
const DWORD flags =
KF_FLAG_SIMPLE_IDLIST | KF_FLAG_DONT_VERIFY | KF_FLAG_NO_ALIAS;
PWSTR rawPath = nullptr;
HRESULT hr =
::SHGetKnownFolderPath(FOLDERID_RoamingAppData, flags, nullptr, &rawPath);
if (FAILED(hr)) {
::CoTaskMemFree(rawPath);
return LAUNCHER_ERROR_FROM_HRESULT(hr);
}
UniquePtr<wchar_t, CoTaskMemFreeDeleter> appDataPath(rawPath);
std::wstring defaultBlocklistPath(appDataPath.get());
UniquePtr<NS_tchar[]> hash;
std::wstring binPathLower;
binPathLower.reserve(mBinPath.size());
std::transform(mBinPath.begin(), mBinPath.end(),
std::back_inserter(binPathLower), std::towlower);
if (!::GetInstallHash(reinterpret_cast<const char16_t*>(binPathLower.c_str()),
hash)) {
return LAUNCHER_ERROR_FROM_WIN32(ERROR_INVALID_DATA);
}
defaultBlocklistPath.append(
L"\\" MOZ_APP_VENDOR L"\\" MOZ_APP_BASENAME L"\\blocklist-");
defaultBlocklistPath.append(hash.get());
return defaultBlocklistPath;
}
LauncherResult<std::wstring> LauncherRegistryInfo::GetBlocklistFileName() {
LauncherResult<Disposition> disposition = Open();
if (disposition.isErr()) {
return disposition.propagateErr();
}
LauncherResult<UniquePtr<wchar_t[]>> readResult =
ReadRegistryValueString(mRegKey, ResolveBlocklistValueName());
if (readResult.isErr()) {
return readResult.propagateErr();
}
if (readResult.inspect()) {
UniquePtr<wchar_t[]> buf = readResult.unwrap();
return std::wstring(buf.get());
}
LauncherResult<std::wstring> defaultBlocklistPath =
BuildDefaultBlocklistFilename();
if (defaultBlocklistPath.isErr()) {
return defaultBlocklistPath.propagateErr();
}
LauncherVoidResult writeResult = WriteRegistryValueString(
mRegKey, ResolveBlocklistValueName(), defaultBlocklistPath.inspect());
if (writeResult.isErr()) {
return writeResult.propagateErr();
}
return defaultBlocklistPath;
}
} // namespace mozilla