From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- toolkit/xre/LauncherRegistryInfo.cpp | 652 +++++++++++++++++++++++++++++++++++ 1 file changed, 652 insertions(+) create mode 100644 toolkit/xre/LauncherRegistryInfo.cpp (limited to 'toolkit/xre/LauncherRegistryInfo.cpp') diff --git a/toolkit/xre/LauncherRegistryInfo.cpp b/toolkit/xre/LauncherRegistryInfo.cpp new file mode 100644 index 0000000000..b80ee77f4d --- /dev/null +++ b/toolkit/xre/LauncherRegistryInfo.cpp @@ -0,0 +1,652 @@ +/* -*- 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 +#include +#include +#include + +#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. + // https://docs.microsoft.com/en-us/windows/win32/api/profileapi/nf-profileapi-queryperformancecounter + ::QueryPerformanceCounter(&perf_counter_now); + return perf_counter_now.QuadPart; +} + +static mozilla::LauncherResult 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 +static mozilla::LauncherResult> ReadRegistryValueData( + const nsAutoRegKey& key, const std::wstring& name, DWORD expectedType) { + static_assert(std::is_trivial_v && std::is_standard_layout_v, + "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(&data), &dataLen); + if (status == ERROR_FILE_NOT_FOUND) { + return mozilla::Maybe(); + } + + 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> +ReadRegistryValueString(const nsAutoRegKey& aKey, const std::wstring& aName) { + mozilla::UniquePtr 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(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(aValue.c_str()), dataBytes); + if (status != ERROR_SUCCESS) { + return LAUNCHER_ERROR_FROM_WIN32(status); + } + + return mozilla::Ok(); +} + +template +static mozilla::LauncherVoidResult WriteRegistryValueData( + const nsAutoRegKey& key, const std::wstring& name, DWORD type, T data) { + static_assert(std::is_trivial_v && std::is_standard_layout_v, + "Registry value type must be primitive."); + LSTATUS status = + ::RegSetValueExW(key.get(), name.c_str(), 0, type, + reinterpret_cast(&data), sizeof(data)); + if (status != ERROR_SUCCESS) { + return LAUNCHER_ERROR_FROM_WIN32(status); + } + + return mozilla::Ok(); +} + +static mozilla::LauncherResult 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::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 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 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 clearedBrowserTimestamp = ClearBrowserStartTimestamp(); + MOZ_ASSERT(clearedBrowserTimestamp.isOk()); + if (clearedBrowserTimestamp.isErr()) { + return clearedBrowserTimestamp.propagateErr(); + } + + return Ok(); +} + +LauncherVoidResult LauncherRegistryInfo::ReflectTelemetryPrefToRegistry( + const bool aEnable) { + LauncherResult disposition = Open(); + if (disposition.isErr()) { + return disposition.propagateErr(); + } + + return WriteRegistryValueData(mRegKey, ResolveTelemetryValueName(), REG_DWORD, + aEnable ? 1UL : 0UL); +} + +LauncherResult LauncherRegistryInfo::Check( + const ProcessType aDesiredType, const CheckOption aOption) { + LauncherResult disposition = Open(); + if (disposition.isErr()) { + return disposition.propagateErr(); + } + + LauncherResult ourImageTimestamp = GetCurrentImageTimestamp(); + if (ourImageTimestamp.isErr()) { + return ourImageTimestamp.propagateErr(); + } + + LauncherResult> 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> lastLauncherTimestampResult = + GetLauncherStartTimestamp(); + if (lastLauncherTimestampResult.isErr()) { + return lastLauncherTimestampResult.propagateErr(); + } + + LauncherResult> lastBrowserTimestampResult = + GetBrowserStartTimestamp(); + if (lastBrowserTimestampResult.isErr()) { + return lastBrowserTimestampResult.propagateErr(); + } + + const Maybe& lastLauncherTimestamp = + lastLauncherTimestampResult.inspect(); + const Maybe& 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 = 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 = 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& aLauncherTs, const Maybe& 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::IsEnabled() { + LauncherResult disposition = Open(); + if (disposition.isErr()) { + return disposition.propagateErr(); + } + + LauncherResult> lastLauncherTimestamp = + GetLauncherStartTimestamp(); + if (lastLauncherTimestamp.isErr()) { + return lastLauncherTimestamp.propagateErr(); + } + + LauncherResult> lastBrowserTimestamp = + GetBrowserStartTimestamp(); + if (lastBrowserTimestamp.isErr()) { + return lastBrowserTimestamp.propagateErr(); + } + + return GetEnabledState(lastLauncherTimestamp.inspect(), + lastBrowserTimestamp.inspect()); +} + +LauncherResult LauncherRegistryInfo::IsTelemetryEnabled() { + LauncherResult disposition = Open(); + if (disposition.isErr()) { + return disposition.propagateErr(); + } + + LauncherResult> result = ReadRegistryValueData( + 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 LauncherRegistryInfo::ClearLauncherStartTimestamp() { + return DeleteRegistryValueData(mRegKey, ResolveLauncherValueName()); +} + +LauncherResult LauncherRegistryInfo::ClearBrowserStartTimestamp() { + return DeleteRegistryValueData(mRegKey, ResolveBrowserValueName()); +} + +LauncherVoidResult LauncherRegistryInfo::ClearStartTimestamps() { + LauncherResult 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 clearedLauncherTimestamp = ClearLauncherStartTimestamp(); + if (clearedLauncherTimestamp.isErr()) { + return clearedLauncherTimestamp.propagateErr(); + } + + LauncherResult 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> LauncherRegistryInfo::GetSavedImageTimestamp() { + return ReadRegistryValueData(mRegKey, ResolveImageTimestampValueName(), + REG_DWORD); +} + +LauncherResult> +LauncherRegistryInfo::GetLauncherStartTimestamp() { + return ReadRegistryValueData(mRegKey, ResolveLauncherValueName(), + REG_QWORD); +} + +LauncherResult> +LauncherRegistryInfo::GetBrowserStartTimestamp() { + return ReadRegistryValueData(mRegKey, ResolveBrowserValueName(), + REG_QWORD); +} + +LauncherResult +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 appDataPath(rawPath); + std::wstring defaultBlocklistPath(appDataPath.get()); + + UniquePtr hash; + std::wstring binPathLower; + binPathLower.reserve(mBinPath.size()); + std::transform(mBinPath.begin(), mBinPath.end(), + std::back_inserter(binPathLower), std::towlower); + if (!::GetInstallHash(reinterpret_cast(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 LauncherRegistryInfo::GetBlocklistFileName() { + LauncherResult disposition = Open(); + if (disposition.isErr()) { + return disposition.propagateErr(); + } + + LauncherResult> readResult = + ReadRegistryValueString(mRegKey, ResolveBlocklistValueName()); + if (readResult.isErr()) { + return readResult.propagateErr(); + } + + if (readResult.inspect()) { + UniquePtr buf = readResult.unwrap(); + return std::wstring(buf.get()); + } + + LauncherResult 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 -- cgit v1.2.3