/* -*- 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