Bug 1659438 - Part2: Transfer Kernel32ExportsSolver as a shared memory. r=mhowell

We transfer several ntdll's function addresses to a child process directly via
`WriteProcessMemory`.  This patch changes the way to transfer data to using
a section object as Chromium sandbox does, so that we can transfer more data
with the same cost as transferring a single handle value.

Depends on D96282

Differential Revision: https://phabricator.services.mozilla.com/D96283
This commit is contained in:
Toshihito Kikuchi 2020-11-10 20:51:00 +00:00
Родитель c256f944cc
Коммит 187d19452d
6 изменённых файлов: 287 добавлений и 98 удалений

Просмотреть файл

@ -41,30 +41,26 @@ LauncherVoidResultWithLineInfo InitializeDllBlocklistOOPFromLauncher(
#else
static LauncherVoidResultWithLineInfo InitializeDllBlocklistOOPInternal(
const wchar_t* aFullImagePath, HANDLE aChildProcess,
const wchar_t* aFullImagePath, nt::CrossExecTransferManager& aTransferMgr,
const IMAGE_THUNK_DATA* aCachedNtdllThunk) {
nt::CrossExecTransferManager transferMgr(aChildProcess);
if (!transferMgr) {
return LAUNCHER_ERROR_FROM_WIN32(ERROR_BAD_EXE_FORMAT);
LauncherVoidResult transferResult =
freestanding::gSharedSection.TransferHandle(aTransferMgr);
if (transferResult.isErr()) {
return transferResult.propagateErr();
}
freestanding::gK32.Init();
if (freestanding::gK32.IsInitialized()) {
Unused << freestanding::gK32.Transfer(transferMgr, &freestanding::gK32);
}
CrossProcessDllInterceptor intcpt(aChildProcess);
CrossProcessDllInterceptor intcpt(aTransferMgr.RemoteProcess());
intcpt.Init(L"ntdll.dll");
bool ok = freestanding::stub_NtMapViewOfSection.SetDetour(
transferMgr, intcpt, "NtMapViewOfSection",
aTransferMgr, intcpt, "NtMapViewOfSection",
&freestanding::patched_NtMapViewOfSection);
if (!ok) {
return LAUNCHER_ERROR_FROM_DETOUR_ERROR(intcpt.GetLastDetourError());
}
ok = freestanding::stub_LdrLoadDll.SetDetour(
transferMgr, intcpt, "LdrLoadDll", &freestanding::patched_LdrLoadDll);
aTransferMgr, intcpt, "LdrLoadDll", &freestanding::patched_LdrLoadDll);
if (!ok) {
return LAUNCHER_ERROR_FROM_DETOUR_ERROR(intcpt.GetLastDetourError());
}
@ -80,12 +76,12 @@ static LauncherVoidResultWithLineInfo InitializeDllBlocklistOOPInternal(
// it onto the child process's IAT, thus enabling the child process's hook to
// safely make its ntdll calls.
const nt::PEHeaders& ourExeImage = transferMgr.LocalPEHeaders();
const nt::PEHeaders& ourExeImage = aTransferMgr.LocalPEHeaders();
// As part of our mitigation of binary tampering, copy our import directory
// from the original in our executable file.
LauncherVoidResult importDirRestored =
RestoreImportDirectory(aFullImagePath, transferMgr);
RestoreImportDirectory(aFullImagePath, aTransferMgr);
if (importDirRestored.isErr()) {
return importDirRestored;
}
@ -126,13 +122,13 @@ static LauncherVoidResultWithLineInfo InitializeDllBlocklistOOPInternal(
SIZE_T iatLength = ntdllThunks.value().LengthBytes();
AutoVirtualProtect prot =
transferMgr.Protect(firstIatThunkDst, iatLength, PAGE_READWRITE);
aTransferMgr.Protect(firstIatThunkDst, iatLength, PAGE_READWRITE);
if (!prot) {
return LAUNCHER_ERROR_FROM_MOZ_WINDOWS_ERROR(prot.GetError());
}
LauncherVoidResult writeResult =
transferMgr.Transfer(firstIatThunkDst, firstIatThunkSrc, iatLength);
aTransferMgr.Transfer(firstIatThunkDst, firstIatThunkSrc, iatLength);
if (writeResult.isErr()) {
return writeResult.propagateErr();
}
@ -148,7 +144,7 @@ static LauncherVoidResultWithLineInfo InitializeDllBlocklistOOPInternal(
}
LauncherVoidResult writeResult =
transferMgr.Transfer(&gBlocklistInitFlags, &newFlags, sizeof(newFlags));
aTransferMgr.Transfer(&gBlocklistInitFlags, &newFlags, sizeof(newFlags));
if (writeResult.isErr()) {
return writeResult.propagateErr();
}
@ -159,26 +155,44 @@ static LauncherVoidResultWithLineInfo InitializeDllBlocklistOOPInternal(
LauncherVoidResultWithLineInfo InitializeDllBlocklistOOP(
const wchar_t* aFullImagePath, HANDLE aChildProcess,
const IMAGE_THUNK_DATA* aCachedNtdllThunk) {
nt::CrossExecTransferManager transferMgr(aChildProcess);
if (!transferMgr) {
return LAUNCHER_ERROR_FROM_WIN32(ERROR_BAD_EXE_FORMAT);
}
// We come here when the browser process launches a sandbox process.
// If the launcher process already failed to bootstrap the browser process,
// we should not attempt to bootstrap a child process because it's likely
// to fail again. Instead, we only restore the import directory entry.
if (!(gBlocklistInitFlags & eDllBlocklistInitFlagWasBootstrapped)) {
nt::CrossExecTransferManager transferMgr(aChildProcess);
if (!transferMgr) {
return LAUNCHER_ERROR_FROM_WIN32(ERROR_BAD_EXE_FORMAT);
}
return RestoreImportDirectory(aFullImagePath, transferMgr);
}
return InitializeDllBlocklistOOPInternal(aFullImagePath, aChildProcess,
return InitializeDllBlocklistOOPInternal(aFullImagePath, transferMgr,
aCachedNtdllThunk);
}
LauncherVoidResultWithLineInfo InitializeDllBlocklistOOPFromLauncher(
const wchar_t* aFullImagePath, HANDLE aChildProcess) {
return InitializeDllBlocklistOOPInternal(aFullImagePath, aChildProcess,
nt::CrossExecTransferManager transferMgr(aChildProcess);
if (!transferMgr) {
return LAUNCHER_ERROR_FROM_WIN32(ERROR_BAD_EXE_FORMAT);
}
// The launcher process initializes a section object, whose handle is
// transferred to the browser process, and that transferred handle in
// the browser process is transferred to the sandbox processes.
LauncherVoidResultWithLineInfo result =
freestanding::gSharedSection.Init(transferMgr.LocalPEHeaders());
if (result.isErr()) {
return result.propagateErr();
}
auto clearInstance = MakeScopeExit([]() {
// After transfer, the launcher process does not need the object anymore.
freestanding::gSharedSection.Reset(nullptr);
});
return InitializeDllBlocklistOOPInternal(aFullImagePath, transferMgr,
nullptr);
}

Просмотреть файл

@ -279,14 +279,19 @@ static BlockAction DetermineBlockAction(const UNICODE_STRING& aLeafName,
// If we fail to detour a module's entrypoint, we reluctantly allow the
// module for free.
auto resultView = mozilla::freestanding::gSharedSection.GetView();
if (resultView.isErr()) {
return BlockAction::Allow;
}
static RTL_RUN_ONCE sRunOnce = RTL_RUN_ONCE_INIT;
mozilla::freestanding::gK32.Resolve(sRunOnce);
if (!mozilla::freestanding::gK32.IsResolved()) {
resultView.inspect()->mK32Exports.Resolve(sRunOnce);
if (!resultView.inspect()->mK32Exports.IsResolved()) {
return BlockAction::Allow;
}
mozilla::interceptor::WindowsDllEntryPointInterceptor interceptor(
mozilla::freestanding::gK32);
resultView.inspect()->mK32Exports);
if (!interceptor.Set(headers, NoOp_DllMain)) {
return BlockAction::Allow;
}

Просмотреть файл

@ -9,7 +9,7 @@
namespace mozilla {
namespace freestanding {
Kernel32ExportsSolver gK32;
SharedSection gSharedSection;
bool Kernel32ExportsSolver::IsInitialized() const {
return mState == State::Initialized || IsResolved();
@ -33,11 +33,12 @@ bool Kernel32ExportsSolver::IsResolved() const {
if (!rvaToFunction) { \
return; \
} \
mOffsets.m##name = *rvaToFunction; \
m##name = reinterpret_cast<decltype(m##name)>(*rvaToFunction); \
} while (0)
#define RESOLVE_FUNCTION(base, name) \
m##name = reinterpret_cast<decltype(m##name)>(base + mOffsets.m##name)
#define RESOLVE_FUNCTION(base, name) \
m##name = reinterpret_cast<decltype(m##name)>( \
base + reinterpret_cast<uintptr_t>(m##name))
void Kernel32ExportsSolver::Init() {
if (mState == State::Initialized || mState == State::Resolved) {
@ -95,18 +96,67 @@ void Kernel32ExportsSolver::Resolve(RTL_RUN_ONCE& aRunOnce) {
::RtlRunOnceExecuteOnce(&aRunOnce, &ResolveOnce, this, nullptr);
}
LauncherVoidResult Kernel32ExportsSolver::Transfer(
nt::CrossExecTransferManager& aTransferMgr,
Kernel32ExportsSolver* aTargetAddress) const {
LauncherVoidResult writeResult = aTransferMgr.Transfer(
&aTargetAddress->mOffsets, &mOffsets, sizeof(mOffsets));
if (writeResult.isErr()) {
return writeResult.propagateErr();
HANDLE SharedSection::sSectionHandle = nullptr;
void* SharedSection::sWriteCopyView = nullptr;
void SharedSection::Reset(HANDLE aNewSecionObject) {
if (sWriteCopyView) {
nt::AutoMappedView view(sWriteCopyView);
sWriteCopyView = nullptr;
}
State stateInChild = State::Initialized;
return aTransferMgr.Transfer(&aTargetAddress->mState, &stateInChild,
sizeof(stateInChild));
if (sSectionHandle) {
::CloseHandle(sSectionHandle);
}
sSectionHandle = aNewSecionObject;
}
LauncherVoidResult SharedSection::Init(const nt::PEHeaders& aPEHeaders) {
HANDLE section =
::CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr, PAGE_READWRITE, 0,
sizeof(Kernel32ExportsSolver), nullptr);
if (!section) {
return LAUNCHER_ERROR_FROM_LAST();
}
Reset(section);
// The initial contents of the pages in a file mapping object backed by
// the operating system paging file are 0 (zero). No need to zero it out
// ourselves.
// https://docs.microsoft.com/en-us/windows/win32/api/memoryapi/nf-memoryapi-createfilemappingw
nt::AutoMappedView writableView(sSectionHandle, PAGE_READWRITE);
if (!writableView) {
return LAUNCHER_ERROR_FROM_LAST();
}
SharedSection::Layout* view = writableView.as<SharedSection::Layout>();
view->mK32Exports.Init();
return Ok();
}
LauncherResult<SharedSection::Layout*> SharedSection::GetView() {
if (!sWriteCopyView) {
nt::AutoMappedView view(sSectionHandle, PAGE_WRITECOPY);
if (!view) {
return LAUNCHER_ERROR_FROM_LAST();
}
sWriteCopyView = view.release();
}
return reinterpret_cast<SharedSection::Layout*>(sWriteCopyView);
}
LauncherVoidResult SharedSection::TransferHandle(
nt::CrossExecTransferManager& aTransferMgr, HANDLE* aDestinationAddress) {
HANDLE remoteHandle;
if (!::DuplicateHandle(nt::kCurrentProcess, sSectionHandle,
aTransferMgr.RemoteProcess(), &remoteHandle,
GENERIC_READ, FALSE, 0)) {
return LAUNCHER_ERROR_FROM_LAST();
}
return aTransferMgr.Transfer(aDestinationAddress, &remoteHandle,
sizeof(remoteHandle));
}
} // namespace freestanding

Просмотреть файл

@ -25,12 +25,6 @@ class MOZ_TRIVIAL_CTOR_DTOR Kernel32ExportsSolver final
Resolved,
} mState;
struct FunctionOffsets {
uint32_t mFlushInstructionCache;
uint32_t mGetSystemInfo;
uint32_t mVirtualProtect;
} mOffsets;
static ULONG NTAPI ResolveOnce(PRTL_RUN_ONCE aRunOnce, PVOID aParameter,
PVOID*);
void ResolveInternal();
@ -48,11 +42,38 @@ class MOZ_TRIVIAL_CTOR_DTOR Kernel32ExportsSolver final
void Init();
void Resolve(RTL_RUN_ONCE& aRunOnce);
LauncherVoidResult Transfer(nt::CrossExecTransferManager& aTransferMgr,
Kernel32ExportsSolver* aTargetAddress) const;
};
extern Kernel32ExportsSolver gK32;
// This class manages a section which is created in the launcher process and
// mapped in the browser process and the sandboxed processes as a copy-on-write
// region. The section's layout is represented as SharedSection::Layout.
class MOZ_TRIVIAL_CTOR_DTOR SharedSection final {
// As we define a global variable of this class and use it in our blocklist
// which is excuted in a process's early stage. If we have a complex dtor,
// the static initializer tries to register that dtor with onexit() of
// ucrtbase.dll which is not loaded yet, resulting in crash. Thus, we have
// a raw handle and a pointer as a static variable and manually release them
// by calling Reset() where possible.
static HANDLE sSectionHandle;
static void* sWriteCopyView;
public:
struct Layout final {
Kernel32ExportsSolver mK32Exports;
Layout() = delete; // disallow instantiation
};
static void Reset(HANDLE aNewSecionObject);
static LauncherVoidResult Init(const nt::PEHeaders& aPEHeaders);
static LauncherResult<Layout*> GetView();
static LauncherVoidResult TransferHandle(
nt::CrossExecTransferManager& aTransferMgr,
HANDLE* aDestinationAddress = &sSectionHandle);
};
extern SharedSection gSharedSection;
} // namespace freestanding
} // namespace mozilla

Просмотреть файл

@ -13,6 +13,7 @@
const wchar_t kChildArg[] = L"--child";
using namespace mozilla;
using namespace mozilla::freestanding;
template <typename T, int N>
void PrintLauncherError(const LauncherResult<T>& aResult,
@ -22,17 +23,38 @@ void PrintLauncherError(const LauncherResult<T>& aResult,
err.mError.AsHResult(), err.mFile, err.mLine);
}
#define VERIFY_FUNCTION_RESOLVED(mod, name) \
do { \
if (reinterpret_cast<FARPROC>(freestanding::gK32.m##name) != \
::GetProcAddress(mod, #name)) { \
printf( \
"TEST-FAILED | TestCrossProcessWin | " \
"Kernel32ExportsSolver::" #name " did not match.\n"); \
return 1; \
} \
#define VERIFY_FUNCTION_RESOLVED(mod, exports, name) \
do { \
if (reinterpret_cast<FARPROC>(exports.m##name) != \
::GetProcAddress(mod, #name)) { \
printf( \
"TEST-FAILED | TestCrossProcessWin | " \
"Kernel32ExportsSolver::" #name " did not match.\n"); \
return false; \
} \
} while (0)
static bool VerifySharedSection(SharedSection& aSharedSection) {
LauncherResult<SharedSection::Layout*> resultView = aSharedSection.GetView();
if (resultView.isErr()) {
PrintLauncherError(resultView, "Failed to map a shared section");
return false;
}
SharedSection::Layout* view = resultView.unwrap();
// Use a local variable of RTL_RUN_ONCE to resolve Kernel32Exports every time
RTL_RUN_ONCE sRunEveryTime = RTL_RUN_ONCE_INIT;
view->mK32Exports.Resolve(sRunEveryTime);
HMODULE k32mod = ::GetModuleHandleW(L"kernel32.dll");
VERIFY_FUNCTION_RESOLVED(k32mod, view->mK32Exports, FlushInstructionCache);
VERIFY_FUNCTION_RESOLVED(k32mod, view->mK32Exports, GetSystemInfo);
VERIFY_FUNCTION_RESOLVED(k32mod, view->mK32Exports, VirtualProtect);
return true;
}
class ChildProcess final {
nsAutoHandle mChildProcess;
nsAutoHandle mChildMainThread;
@ -69,18 +91,24 @@ class ChildProcess final {
return 1;
}
static RTL_RUN_ONCE sRunOnce = RTL_RUN_ONCE_INIT;
freestanding::gK32.Resolve(sRunOnce);
if (!freestanding::gK32.IsResolved()) {
printf(
"TEST-FAILED | TestCrossProcessWin | "
"Kernel32ExportsSolver::Resolve failed.\n");
if (!VerifySharedSection(gSharedSection)) {
return 1;
}
HMODULE k32mod = ::GetModuleHandleW(L"kernel32.dll");
VERIFY_FUNCTION_RESOLVED(k32mod, FlushInstructionCache);
VERIFY_FUNCTION_RESOLVED(k32mod, GetSystemInfo);
VERIFY_FUNCTION_RESOLVED(k32mod, VirtualProtect);
// Test a scenario to transfer a transferred section
static HANDLE copiedHandle = nullptr;
nt::CrossExecTransferManager tansferToSelf(::GetCurrentProcess());
LauncherVoidResult result =
gSharedSection.TransferHandle(tansferToSelf, &copiedHandle);
if (result.isErr()) {
PrintLauncherError(result, "SharedSection::TransferHandle(self) failed");
return 1;
}
gSharedSection.Reset(copiedHandle);
if (!VerifySharedSection(gSharedSection)) {
return 1;
}
return 0;
}
@ -175,35 +203,19 @@ int wmain(int argc, wchar_t* argv[]) {
return 1;
}
freestanding::gK32.Init();
if (!freestanding::gK32.IsInitialized()) {
printf(
"TEST-FAILED | TestCrossProcessWin | "
"Kernel32ExportsSolver initialization failed.\n");
return 1;
}
LauncherVoidResult writeResult =
freestanding::gK32.Transfer(transferMgr, &freestanding::gK32);
if (writeResult.isErr()) {
PrintLauncherError(writeResult, "Kernel32ExportsSolver::Transfer failed");
return 1;
}
writeResult =
LauncherVoidResult result =
transferMgr.Transfer(&ChildProcess::sExecutableImageBase,
&remoteImageBase.inspect(), sizeof(HMODULE));
if (writeResult.isErr()) {
PrintLauncherError(writeResult,
"ChildProcess::WriteData(Imagebase) failed");
if (result.isErr()) {
PrintLauncherError(result, "ChildProcess::WriteData(Imagebase) failed");
return 1;
}
DWORD childPid = childProcess.GetProcessId();
DWORD* readOnlyData = const_cast<DWORD*>(&ChildProcess::sReadOnlyProcessId);
writeResult = transferMgr.Transfer(readOnlyData, &childPid, sizeof(DWORD));
if (writeResult.isOk()) {
result = transferMgr.Transfer(readOnlyData, &childPid, sizeof(DWORD));
if (result.isOk()) {
printf(
"TEST-UNEXPECTED | TestCrossProcessWin | "
"A constant was located in a writable section.");
@ -220,12 +232,28 @@ int wmain(int argc, wchar_t* argv[]) {
return 1;
}
writeResult = transferMgr.Transfer(readOnlyData, &childPid, sizeof(DWORD));
if (writeResult.isErr()) {
PrintLauncherError(writeResult, "ChildProcess::WriteData(PID) failed");
result = transferMgr.Transfer(readOnlyData, &childPid, sizeof(DWORD));
if (result.isErr()) {
PrintLauncherError(result, "ChildProcess::WriteData(PID) failed");
return 1;
}
{
// Define a scope for |sharedSection| to resume the child process after
// the section is deleted in the parent process.
result = gSharedSection.Init(transferMgr.LocalPEHeaders());
if (result.isErr()) {
PrintLauncherError(result, "SharedSection::Init failed");
return 1;
}
result = gSharedSection.TransferHandle(transferMgr);
if (result.isErr()) {
PrintLauncherError(result, "SharedSection::TransferHandle failed");
return 1;
}
}
if (!childProcess.ResumeAndWaitUntilExit()) {
return 1;
}

Просмотреть файл

@ -655,10 +655,8 @@ class MOZ_RAII PEHeaders final {
return nullptr;
}
#if defined(MOZILLA_INTERNAL_API)
nsTHashtable<nsStringCaseInsensitiveHashKey> GenerateDependentModuleSet() {
nsTHashtable<nsStringCaseInsensitiveHashKey> dependentModuleSet;
template <typename CallbackT>
void EnumImportChunks(const CallbackT& aCallback) const {
for (PIMAGE_IMPORT_DESCRIPTOR curImpDesc = GetImportDirectory();
IsValid(curImpDesc); ++curImpDesc) {
auto curName = mIsImportDirectoryTampered
@ -668,9 +666,17 @@ class MOZ_RAII PEHeaders final {
continue;
}
dependentModuleSet.PutEntry(GetLeafName(NS_ConvertASCIItoUTF16(curName)));
aCallback(curName);
}
}
#if defined(MOZILLA_INTERNAL_API)
nsTHashtable<nsStringCaseInsensitiveHashKey> GenerateDependentModuleSet()
const {
nsTHashtable<nsStringCaseInsensitiveHashKey> dependentModuleSet;
EnumImportChunks([&dependentModuleSet](const char* aModule) {
dependentModuleSet.PutEntry(GetLeafName(NS_ConvertASCIItoUTF16(aModule)));
});
return dependentModuleSet;
}
#endif // defined(MOZILLA_INTERNAL_API)
@ -1530,6 +1536,71 @@ class RtlAllocPolicy {
[[nodiscard]] bool checkSimulatedOOM() const { return true; }
};
class AutoMappedView final {
void* mView;
void Unmap() {
if (!mView) {
return;
}
#if defined(MOZILLA_INTERNAL_API)
::UnmapViewOfFile(mView);
#else
NTSTATUS status = ::NtUnmapViewOfSection(nt::kCurrentProcess, mView);
if (!NT_SUCCESS(status)) {
::RtlSetLastWin32Error(::RtlNtStatusToDosError(status));
}
#endif
mView = nullptr;
}
public:
explicit AutoMappedView(void* aView) : mView(aView) {}
AutoMappedView(HANDLE aSection, ULONG aProtectionFlags) : mView(nullptr) {
#if defined(MOZILLA_INTERNAL_API)
mView = ::MapViewOfFile(aSection, aProtectionFlags, 0, 0, 0);
#else
SIZE_T viewSize = 0;
NTSTATUS status = ::NtMapViewOfSection(aSection, nt::kCurrentProcess,
&mView, 0, 0, nullptr, &viewSize,
ViewUnmap, 0, aProtectionFlags);
if (!NT_SUCCESS(status)) {
::RtlSetLastWin32Error(::RtlNtStatusToDosError(status));
}
#endif
}
~AutoMappedView() { Unmap(); }
// Allow move & Disallow copy
AutoMappedView(AutoMappedView&& aOther) : mView(aOther.mView) {
aOther.mView = nullptr;
}
AutoMappedView& operator=(AutoMappedView&& aOther) {
if (this != &aOther) {
Unmap();
mView = aOther.mView;
aOther.mView = nullptr;
}
return *this;
}
AutoMappedView(const AutoMappedView&) = delete;
AutoMappedView& operator=(const AutoMappedView&) = delete;
explicit operator bool() const { return !!mView; }
template <typename T>
T* as() {
return reinterpret_cast<T*>(mView);
}
void* release() {
void* p = mView;
mView = nullptr;
return p;
}
};
} // namespace nt
} // namespace mozilla