Bug 1788004 - Implement a hook-based fallback for BCryptGenRandom to mitigate Rust panics. r=cmartin

BCryptGenRandom can be broken, but the Rust stdlib and the getrandom
crate rely on it, and this is a source of crashes which are Rust
panics. This happens the most on Windows 7 after bcryptprimitives.dll
fails to load (see bug 1788004).

To mitigate these crashes, we hook BCryptGenRandom if we detect that it
is broken, and install a fallback based on RtlGenRandom. We only protect
calls that use BCRYPT_USE_SYSTEM_PREFERRED_RNG; so code that relies on
using BCryptOpenAlgorithmProvider and doesn't have its own fallback can
still fail.

We will hopefully remove this hook when the Rust stdlib and the
getrandom crate both have their own RtlGenRandom-based fallback.

Differential Revision: https://phabricator.services.mozilla.com/D170662
This commit is contained in:
Yannis Juglaret 2023-02-24 15:47:14 +00:00
parent 6a8f812670
commit 45cce5b7c0
8 changed files with 120 additions and 14 deletions

View File

@ -32,6 +32,7 @@
# include "LauncherProcessWin.h"
# include "mozilla/GeckoArgs.h"
# include "mozilla/mscom/ProcessRuntime.h"
# include "mozilla/WindowsBCryptInitialization.h"
# include "mozilla/WindowsDllBlocklist.h"
# include "mozilla/WindowsDpiInitialization.h"
# include "mozilla/WindowsProcessMitigations.h"
@ -401,6 +402,12 @@ int main(int argc, char* argv[], char* envp[]) {
(void)result; // Ignore errors since some tools block DPI calls
}
// BCrypt initialization for the main process. This code runs too early to
// crash in a reportable way, so we ignore the result even in debug. If this
// fails, let's continue and crash later when we encounter a fatal
// BCryptGenRandom failure, if any.
mozilla::WindowsBCryptInitialization();
// Once the browser process hits the main function, we no longer need
// a writable section handle because all dependent modules have been
// loaded.

View File

@ -91,7 +91,8 @@ MFBT_API bool GenerateRandomBytesFromOS(void* aBuffer, size_t aLength) {
MOZ_ASSERT(aLength > 0);
#if defined(XP_WIN)
// Note: This function is used as a fallback for BCryptGenRandom in
// WindowsBCryptInitialization(). Do not use BCryptGenRandom here!
return !!RtlGenRandom(aBuffer, aLength);
#elif defined(USE_ARC4RANDOM) // defined(XP_WIN)

View File

@ -0,0 +1,53 @@
/* -*- 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 http://mozilla.org/MPL/2.0/. */
#include "mozilla/WindowsBCryptInitialization.h"
#include "mozilla/RandomNum.h"
#include "nsWindowsDllInterceptor.h"
#include <bcrypt.h>
#pragma comment(lib, "bcrypt.lib")
namespace mozilla {
static mozilla::WindowsDllInterceptor BCryptIntercept;
static mozilla::WindowsDllInterceptor::FuncHookType<
decltype(&::BCryptGenRandom)>
stub_BCryptGenRandom;
NTSTATUS WINAPI patched_BCryptGenRandom(BCRYPT_ALG_HANDLE aAlgorithm,
PUCHAR aBuffer, ULONG aSize,
ULONG aFlags) {
// If we are using the hook, we know that BCRYPT_USE_SYSTEM_PREFERRED_RNG is
// broken, so let's use the fallback directly in that case.
if (!aAlgorithm && (aFlags & BCRYPT_USE_SYSTEM_PREFERRED_RNG) && aBuffer &&
aSize && mozilla::GenerateRandomBytesFromOS(aBuffer, aSize)) {
return STATUS_SUCCESS;
}
return stub_BCryptGenRandom(aAlgorithm, aBuffer, aSize, aFlags);
}
bool WindowsBCryptInitialization() {
UCHAR buffer[32];
NTSTATUS status = ::BCryptGenRandom(nullptr, buffer, sizeof(buffer),
BCRYPT_USE_SYSTEM_PREFERRED_RNG);
if (NT_SUCCESS(status)) {
return true;
}
BCryptIntercept.Init(L"bcrypt.dll");
if (!stub_BCryptGenRandom.Set(BCryptIntercept, "BCryptGenRandom",
patched_BCryptGenRandom)) {
return false;
}
status = ::BCryptGenRandom(nullptr, buffer, sizeof(buffer),
BCRYPT_USE_SYSTEM_PREFERRED_RNG);
return NT_SUCCESS(status);
}
} // namespace mozilla

View File

@ -0,0 +1,31 @@
/* -*- 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 http://mozilla.org/MPL/2.0/. */
#ifndef mozilla_WindowsBCryptInitialization_h
#define mozilla_WindowsBCryptInitialization_h
#include "mozilla/Types.h"
namespace mozilla {
// This functions ensures that calling BCryptGenRandom will work later:
// - It triggers a first call to BCryptGenRandom() to pre-load
// bcryptPrimitives.dll while the current thread still has an unrestricted
// impersonation token. We need to perform that operation in sandboxed
// processes to warmup the BCryptGenRandom() call that is used by others,
// especially Rust. See bug 1746524, bug 1751094, bug 1751177.
// - If that first call fails, we detect it and hook BCryptGenRandom to
// install a fallback based on RtlGenRandom for calls that use flag
// BCRYPT_USE_SYSTEM_PREFERRED_RNG. We need this because BCryptGenRandom
// failures are currently fatal and on some machines BCryptGenRandom is
// broken (usually Windows 7). We hope to remove this hook in the future
// once the Rust stdlib and the getrandom crate both have their own
// RtlGenRandom-based fallback. See bug 1788004.
MFBT_API bool WindowsBCryptInitialization();
} // namespace mozilla
#endif // mozilla_WindowsBCryptInitialization_h

View File

@ -77,6 +77,7 @@ if CONFIG["OS_ARCH"] == "WINNT":
"ImportDir.h",
"MozProcessMitigationDynamicCodePolicy.h",
"NativeNt.h",
"WindowsBCryptInitialization.h",
"WindowsDpiInitialization.h",
"WindowsEnumProcessModules.h",
"WindowsMapRemoteView.h",
@ -90,6 +91,7 @@ if CONFIG["OS_ARCH"] == "WINNT":
SOURCES += [
"GetKnownFolderPath.cpp",
"TimeStamp_windows.cpp",
"WindowsBCryptInitialization.cpp",
"WindowsDllMain.cpp",
"WindowsDpiInitialization.cpp",
"WindowsMapRemoteView.cpp",

View File

@ -1180,6 +1180,14 @@ class WindowsDllDetourPatcher final
} else if (*origBytes >= 0xb8 && *origBytes <= 0xbf) {
// mov r32, imm32
COPY_CODES(5);
} else if (*origBytes == 0x8b && (origBytes[1] & kMaskMod) == kModReg) {
// 8B /r: mov r32, r/m32
COPY_CODES(2);
} else if (*origBytes == 0xf7 &&
(origBytes[1] & (kMaskMod | kMaskReg)) ==
(kModReg | (0 << kRegFieldShift))) {
// F7 /0 id: test r/m32, imm32
COPY_CODES(6);
} else {
MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
return;

View File

@ -14,6 +14,9 @@
#include <winternl.h>
#include <processthreadsapi.h>
#include <bcrypt.h>
#pragma comment(lib, "bcrypt.lib")
#include "AssemblyPayloads.h"
#include "mozilla/DynamicallyLinkedFunctionPtr.h"
#include "mozilla/UniquePtr.h"
@ -1380,6 +1383,8 @@ extern "C" int wmain(int argc, wchar_t* argv[]) {
TEST_HOOK("user32.dll", InSendMessageEx, Equals, ISMEX_NOSEND) &&
TEST_HOOK("user32.dll", SendMessageTimeoutW, Equals, 0) &&
TEST_HOOK("user32.dll", SetCursorPos, NotEquals, FALSE) &&
TEST_HOOK("bcrypt.dll", BCryptGenRandom, Equals,
static_cast<NTSTATUS>(STATUS_INVALID_HANDLE)) &&
#if !defined(_M_ARM64)
TEST_HOOK("imm32.dll", ImmGetContext, Equals, nullptr) &&
#endif // !defined(_M_ARM64)

View File

@ -25,6 +25,7 @@
# endif
# include "mozilla/ScopeExit.h"
# include "mozilla/WinDllServices.h"
# include "mozilla/WindowsBCryptInitialization.h"
# include "WinUtils.h"
# ifdef ACCESSIBILITY
# include "mozilla/GeckoArgs.h"
@ -534,24 +535,22 @@ nsresult XRE_InitChildProcess(int aArgc, char* aArgv[],
break;
}
#if defined(MOZ_SANDBOX) && defined(XP_WIN)
#if defined(XP_WIN)
# if defined(MOZ_SANDBOX)
if (aChildData->sandboxBrokerServices) {
SandboxBroker::Initialize(aChildData->sandboxBrokerServices);
SandboxBroker::GeckoDependentInitialize();
}
# endif // defined(MOZ_SANDBOX)
// Call BCryptGenRandom() to pre-load bcryptPrimitives.dll while the current
// thread still has an unrestricted impersonation token. We need to perform
// that operation to warmup the BCryptGenRandom() call that is used by
// others, especially rust. See bug 1746524, bug 1751094, bug 1751177
UCHAR buffer[32];
NTSTATUS status = BCryptGenRandom(NULL, // hAlgorithm
buffer, // pbBuffer
sizeof(buffer), // cbBuffer
BCRYPT_USE_SYSTEM_PREFERRED_RNG // dwFlags
);
MOZ_RELEASE_ASSERT(status == STATUS_SUCCESS);
#endif // defined(MOZ_SANDBOX) && defined(XP_WIN)
{
// BCrypt initialization for child processes. If this fails, let's continue
// and crash later when we encounter a fatal BCryptGenRandom failure (if
// any), unless we are in a debug build.
DebugOnly<bool> result = mozilla::WindowsBCryptInitialization();
MOZ_ASSERT(result);
}
#endif // defined(XP_WIN)
{
// This is a lexical scope for the MessageLoop below. We want it