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
#include "nsWindowsHelpers.h"
#include "mozilla/Array.h"
#include "mozilla/Attributes.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/WindowsStackWalkInitialization.h"
#include <windows.h>
#include <cstdio>
#define TEST_FAILED(format, ...) \
do { \
wprintf(L"TEST-FAILED | TestStackWalkInitialization | " format __VA_OPT__( \
, ) __VA_ARGS__); \
::exit(1); \
} while (0)
#define TEST_PASS(format, ...) \
do { \
wprintf(L"TEST-PASS | TestStackWalkInitialization | " format __VA_OPT__( \
, ) __VA_ARGS__); \
} while (0)
#define MAX_TIMEOUT_MS 5000
extern "C" __declspec(dllexport) uint64_t gPseudoLock{};
MOZ_NEVER_INLINE MOZ_NAKED __declspec(dllexport) void LockThroughRegisterRsi() {
asm volatile(
// Found in RtlAcquireSRWLockShared
"lock cmpxchgq %rcx, (%rsi)");
}
MOZ_NEVER_INLINE MOZ_NAKED __declspec(dllexport) void LockThroughRegisterRcx() {
asm volatile(
// Found in RtlReleaseSRWLockShared
"lock cmpxchgq %r10, (%rcx)");
}
MOZ_NEVER_INLINE MOZ_NAKED __declspec(dllexport) void LockThroughRegisterR10() {
asm volatile("lock cmpxchgq %rcx, (%r10)");
}
MOZ_NEVER_INLINE MOZ_NAKED __declspec(dllexport) void
LockThroughRipRelativeAddr() {
asm volatile(
// Found in an inlined call to RtlAcquireSRWLockShared in
// RtlpxLookupFunctionTable on Windows 10
"lock cmpxchgq %r11, gPseudoLock(%rip)");
}
void TestLockExtraction() {
void* extractedLock{};
CONTEXT context{};
context.Rip = reinterpret_cast<DWORD64>(LockThroughRegisterRsi);
context.Rsi = reinterpret_cast<DWORD64>(&gPseudoLock);
extractedLock = mozilla::ExtractLockFromCurrentCpuContext(&context);
context.Rsi = 0;
if (extractedLock != &gPseudoLock) {
TEST_FAILED(
L"Failed to extract the lock through register RSI (expected: %p, got: "
L"%p)\n",
&gPseudoLock, extractedLock);
}
context.Rip = reinterpret_cast<DWORD64>(LockThroughRegisterRcx);
context.Rcx = reinterpret_cast<DWORD64>(&gPseudoLock);
extractedLock = mozilla::ExtractLockFromCurrentCpuContext(&context);
context.Rcx = 0;
if (extractedLock != &gPseudoLock) {
TEST_FAILED(
L"Failed to extract the lock through register RCX (expected: %p, got: "
L"%p)\n",
&gPseudoLock, extractedLock);
}
context.Rip = reinterpret_cast<DWORD64>(LockThroughRegisterR10);
context.R10 = reinterpret_cast<DWORD64>(&gPseudoLock);
extractedLock = mozilla::ExtractLockFromCurrentCpuContext(&context);
context.R10 = 0;
if (extractedLock != &gPseudoLock) {
TEST_FAILED(
L"Failed to extract the lock through register R10 (expected: %p, got: "
L"%p)\n",
&gPseudoLock, extractedLock);
}
context.Rip = reinterpret_cast<DWORD64>(LockThroughRipRelativeAddr);
extractedLock = mozilla::ExtractLockFromCurrentCpuContext(&context);
if (extractedLock != &gPseudoLock) {
TEST_FAILED(
L"Failed to extract the lock through RIP-relative address (expected: "
L"%p, got: %p)\n",
&gPseudoLock, extractedLock);
}
TEST_PASS(L"Managed to extract the lock with all test patterns\n");
}
void TestLockCollectionAndValidation(
mozilla::Array<void*, 2>& aStackWalkLocks) {
if (!mozilla::CollectStackWalkLocks(aStackWalkLocks)) {
TEST_FAILED(L"Failed to collect stack walk locks\n");
}
if (!mozilla::ValidateStackWalkLocks(aStackWalkLocks)) {
TEST_FAILED(L"Failed to validate stack walk locks\n");
}
TEST_PASS(L"Collected and validated locks successfully\n");
}
DWORD WINAPI LookupThreadProc(LPVOID aEvents) {
auto events = reinterpret_cast<nsAutoHandle*>(aEvents);
auto& lookupThreadReady = events[0];
auto& initiateLookup = events[1];
auto& lookupThreadDone = events[2];
// Signal that we are ready to enter lookup.
::SetEvent(lookupThreadReady);
// Wait for the main thread to acquire the locks exclusively.
if (::WaitForSingleObject(initiateLookup, MAX_TIMEOUT_MS) == WAIT_OBJECT_0) {
// Do a lookup. We are supposed to get stuck until the locks are released.
DWORD64 imageBase;
::RtlLookupFunctionEntry(reinterpret_cast<DWORD64>(LookupThreadProc),
&imageBase, nullptr);
// Signal that we are not or no longer stuck.
::SetEvent(lookupThreadDone);
}
return 0;
}
// This test checks that the locks in aStackWalkLocks cause
// RtlLookupFunctionEntry to get stuck if they are held exclusively, i.e. there
// is a good chance that these are indeed the locks we are looking for.
void TestLocksPreventLookup(const mozilla::Array<void*, 2>& aStackWalkLocks) {
nsAutoHandle events[3]{};
for (int i = 0; i < 3; ++i) {
nsAutoHandle event(::CreateEventW(nullptr, /* bManualReset */ TRUE,
/* bInitialState */ FALSE, nullptr));
if (!event) {
TEST_FAILED(L"Failed to create event %d\n", i);
}
events[i].swap(event);
}
auto& lookupThreadReady = events[0];
auto& initiateLookup = events[1];
auto& lookupThreadDone = events[2];
nsAutoHandle lookupThread(::CreateThread(nullptr, 0, LookupThreadProc,
reinterpret_cast<void*>(events), 0,
nullptr));
if (!lookupThread) {
TEST_FAILED(L"Failed to create lookup thread\n");
}
if (::WaitForSingleObject(lookupThreadReady, MAX_TIMEOUT_MS) !=
WAIT_OBJECT_0) {
TEST_FAILED(L"Lookup thread did not signal the lookupThreadReady event\n");
}
mozilla::Array<SRWLOCK*, 2> stackWalkLocks{
reinterpret_cast<SRWLOCK*>(aStackWalkLocks[0]),
reinterpret_cast<SRWLOCK*>(aStackWalkLocks[1])};
if (!::TryAcquireSRWLockExclusive(stackWalkLocks[0])) {
TEST_FAILED(L"Failed to acquire lock 0\n");
}
if (!::TryAcquireSRWLockExclusive(stackWalkLocks[1])) {
::ReleaseSRWLockExclusive(stackWalkLocks[0]);
TEST_FAILED(L"Failed to acquire lock 1\n");
}
{
auto onExitScope = mozilla::MakeScopeExit([&stackWalkLocks]() {
::ReleaseSRWLockExclusive(stackWalkLocks[1]);
::ReleaseSRWLockExclusive(stackWalkLocks[0]);
});
if (!::SetEvent(initiateLookup)) {
TEST_FAILED(L"Failed to signal the initiateLookup event\n");
}
if (::WaitForSingleObject(lookupThreadDone, MAX_TIMEOUT_MS) !=
WAIT_TIMEOUT) {
TEST_FAILED(
L"Lookup thread was not stuck during lookup while we acquired the "
L"locks exclusively\n");
}
}
if (::WaitForSingleObject(lookupThreadDone, MAX_TIMEOUT_MS) !=
WAIT_OBJECT_0) {
TEST_FAILED(
L"Lookup thread did not signal the lookupThreadDone event after locks "
L"were released\n");
}
TEST_PASS(L"Locks prevented lookup while acquired exclusively\n");
}
int wmain(int argc, wchar_t* argv[]) {
TestLockExtraction();
mozilla::Array<void*, 2> stackWalkLocks;
TestLockCollectionAndValidation(stackWalkLocks);
TestLocksPreventLookup(stackWalkLocks);
return 0;
}