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 <thread>
#include <winternl.h>
#define MOZ_USE_LAUNCHER_ERROR
#include <atomic>
#include <thread>
#include "freestanding/SharedSection.cpp"
#include "mozilla/CmdLineAndEnvUtils.h"
#include "mozilla/DynamicBlocklist.h"
#include "mozilla/NativeNt.h"
#include "mozilla/Vector.h"
#define DLL_BLOCKLIST_ENTRY(name, ...) \
{MOZ_LITERAL_UNICODE_STRING(L##name), __VA_ARGS__},
#define DLL_BLOCKLIST_STRING_TYPE UNICODE_STRING
#include "mozilla/WindowsDllBlocklistLauncherDefs.h"
const wchar_t kChildArg[] = L"--child";
const char* kTestDependentModulePaths[] = {
"\\Device\\HarddiskVolume4\\Windows\\system32\\A B C",
"\\Device\\HarddiskVolume4\\Windows\\system32\\a b c.dll",
"\\Device\\HarddiskVolume4\\Windows\\system32\\A B C.exe",
"\\Device\\HarddiskVolume4\\Windows\\system32\\X Y Z.dll",
"\\Device\\HarddiskVolume1\\a b C",
"\\Device\\HarddiskVolume2\\A b c.DLL",
"\\Device\\HarddiskVolume3\\A B c.exe",
"\\Device\\HarddiskVolume4\\X y Z.dll",
};
const wchar_t* kExpectedDependentModules[] = {
L"A B C",
L"a b c.dll",
L"A B C.exe",
L"X Y Z.dll",
};
const UNICODE_STRING kStringNotInBlocklist =
MOZ_LITERAL_UNICODE_STRING(L"Test_NotInBlocklist.dll");
const UNICODE_STRING kTestDependentModuleString =
MOZ_LITERAL_UNICODE_STRING(L"Test_DependentModule.dll");
using namespace mozilla;
using namespace mozilla::freestanding;
// clang-format off
const DllBlockInfo kDllBlocklistShort[] = {
// The entries do not have to be sorted.
DLL_BLOCKLIST_ENTRY("X Y Z_Test", MAKE_VERSION(1, 2, 65535, 65535))
DLL_BLOCKLIST_ENTRY("\u30E9\u30FC\u30E1\u30F3_Test")
DLL_BLOCKLIST_ENTRY("Avmvirtualsource_Test.ax", MAKE_VERSION(1, 0, 0, 3),
DllBlockInfoFlags::BROWSER_PROCESS_ONLY)
DLL_BLOCKLIST_ENTRY("1ccelerator_Test.dll", MAKE_VERSION(3, 2, 1, 6))
DLL_BLOCKLIST_ENTRY("atkdx11disp_Test.dll", DllBlockInfo::ALL_VERSIONS)
{},
};
// clang-format on
namespace mozilla::freestanding {
class SharedSectionTestHelper {
public:
static constexpr size_t GetModulePathArraySize() {
return SharedSection::kSharedViewSize -
(offsetof(SharedSection::Layout, mFirstBlockEntry) +
sizeof(DllBlockInfo));
}
};
} // namespace mozilla::freestanding
class TempFile final {
wchar_t mFullPath[MAX_PATH + 1];
public:
TempFile() : mFullPath{0} {
wchar_t tempDir[MAX_PATH + 1];
DWORD len = ::GetTempPathW(std::size(tempDir), tempDir);
if (!len) {
return;
}
len = ::GetTempFileNameW(tempDir, L"blocklist", 0, mFullPath);
if (!len) {
return;
}
}
operator const wchar_t*() const { return mFullPath[0] ? mFullPath : nullptr; }
};
template <typename T, int N>
void PrintLauncherError(const LauncherResult<T>& aResult,
const char (&aMsg)[N]) {
const LauncherError& err = aResult.inspectErr();
printf("TEST-FAILED | TestCrossProcessWin | %s - %lx at %s:%d\n", aMsg,
err.mError.AsHResult(), err.mFile, err.mLine);
}
#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) {
Kernel32ExportsSolver* k32Exports = aSharedSection.GetKernel32Exports();
if (!k32Exports) {
printf(
"TEST-FAILED | TestCrossProcessWin | Failed to map a shared section\n");
return false;
}
HMODULE k32mod = ::GetModuleHandleW(L"kernel32.dll");
VERIFY_FUNCTION_RESOLVED(k32mod, k32Exports, FlushInstructionCache);
VERIFY_FUNCTION_RESOLVED(k32mod, k32Exports, GetModuleHandleW);
VERIFY_FUNCTION_RESOLVED(k32mod, k32Exports, GetSystemInfo);
VERIFY_FUNCTION_RESOLVED(k32mod, k32Exports, VirtualProtect);
Maybe<Vector<const wchar_t*>> modulesArray =
aSharedSection.GetDependentModules();
if (modulesArray.isNothing()) {
printf(
"TEST-FAILED | TestCrossProcessWin | GetDependentModules returned "
"Nothing");
return false;
}
bool matched =
modulesArray->length() ==
sizeof(kExpectedDependentModules) / sizeof(kExpectedDependentModules[0]);
if (matched) {
for (size_t i = 0; i < modulesArray->length(); ++i) {
if (wcscmp((*modulesArray)[i], kExpectedDependentModules[i])) {
matched = false;
break;
}
}
}
if (!matched) {
// Print actual strings on error
for (const wchar_t* p : *modulesArray) {
printf("%p: %S\n", p, p);
}
return false;
}
for (const DllBlockInfo* info = kDllBlocklistShort; info->mName.Buffer;
++info) {
const DllBlockInfo* matched = aSharedSection.SearchBlocklist(info->mName);
if (!matched) {
printf(
"TEST-FAILED | TestCrossProcessWin | No blocklist entry match for "
"entry in blocklist.\n");
return false;
}
}
if (aSharedSection.SearchBlocklist(kStringNotInBlocklist)) {
printf(
"TEST-FAILED | TestCrossProcessWin | Found blocklist entry match for "
"something not in the blocklist.\n");
}
if (aSharedSection.IsDisabled()) {
printf("TEST-FAILED | TestCrossProcessWin | Wrong disabled value.\n");
}
return true;
}
static bool TestAddString() {
wchar_t testBuffer[3] = {0};
UNICODE_STRING ustr;
// This makes |testBuffer| full.
::RtlInitUnicodeString(&ustr, L"a");
if (!AddString(testBuffer, ustr)) {
printf(
"TEST-FAILED | TestCrossProcessWin | "
"AddString failed.\n");
return false;
}
// Adding a string to a full buffer should fail.
::RtlInitUnicodeString(&ustr, L"b");
if (AddString(testBuffer, ustr)) {
printf(
"TEST-FAILED | TestCrossProcessWin | "
"AddString caused OOB memory access.\n");
return false;
}
bool matched = memcmp(testBuffer, L"a\0", sizeof(testBuffer)) == 0;
if (!matched) {
printf(
"TEST-FAILED | TestCrossProcessWin | "
"AddString wrote wrong values.\n");
return false;
}
return true;
}
// Convert |aBlockEntries|, which is an array ending with an empty instance
// of DllBlockInfo, to DynamicBlockList by storing it to a temp file, loading
// as DynamicBlockList, and deleting the temp file.
static DynamicBlockList ConvertStaticBlocklistToDynamic(
const DllBlockInfo aBlockEntries[]) {
size_t originalLength = 0;
CheckedUint32 totalStringLen = 0;
for (const DllBlockInfo* entry = aBlockEntries; entry->mName.Length;
++entry) {
totalStringLen += entry->mName.Length;
MOZ_RELEASE_ASSERT(totalStringLen.isValid());
++originalLength;
}
// Pack all strings in this buffer without null characters
UniquePtr<uint8_t[]> stringBuffer =
MakeUnique<uint8_t[]>(totalStringLen.value());
// The string buffer is placed immediately after the array of DllBlockInfo
const size_t stringBufferOffset = (originalLength + 1) * sizeof(DllBlockInfo);
// Entries in the dynamic blocklist do have to be sorted,
// unlike in the static blocklist.
UniquePtr<DllBlockInfo[]> sortedBlockEntries =
MakeUnique<DllBlockInfo[]>(originalLength);
memcpy(sortedBlockEntries.get(), aBlockEntries,
sizeof(DllBlockInfo) * originalLength);
std::sort(sortedBlockEntries.get(), sortedBlockEntries.get() + originalLength,
[](const DllBlockInfo& a, const DllBlockInfo& b) {
return ::RtlCompareUnicodeString(&a.mName, &b.mName, TRUE) < 0;
});
Vector<DllBlockInfo> copied;
Unused << copied.resize(originalLength + 1); // aBlockEntries + sentinel
size_t currentStringOffset = 0;
for (size_t i = 0; i < originalLength; ++i) {
copied[i].mMaxVersion = sortedBlockEntries[i].mMaxVersion;
copied[i].mFlags = sortedBlockEntries[i].mFlags;
// Copy the module's name to the string buffer and store its offset
// in mName.Buffer
memcpy(stringBuffer.get() + currentStringOffset,
sortedBlockEntries[i].mName.Buffer,
sortedBlockEntries[i].mName.Length);
copied[i].mName.Buffer =
reinterpret_cast<wchar_t*>(stringBufferOffset + currentStringOffset);
// Only keep mName.Length and leave mName.MaximumLength to be zero
copied[i].mName.Length = sortedBlockEntries[i].mName.Length;
currentStringOffset += sortedBlockEntries[i].mName.Length;
}
TempFile blocklistFile;
nsAutoHandle file(::CreateFileW(blocklistFile, GENERIC_WRITE, FILE_SHARE_READ,
nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,
nullptr));
MOZ_RELEASE_ASSERT(file);
DynamicBlockListBase::FileHeader header;
header.mSignature = DynamicBlockListBase::kSignature;
header.mFileVersion = DynamicBlockListBase::kCurrentVersion;
header.mPayloadSize =
sizeof(DllBlockInfo) * copied.length() + totalStringLen.value();
DWORD written = 0;
MOZ_RELEASE_ASSERT(
::WriteFile(file.get(), &header, sizeof(header), &written, nullptr));
MOZ_RELEASE_ASSERT(::WriteFile(file.get(), copied.begin(),
sizeof(DllBlockInfo) * copied.length(),
&written, nullptr));
MOZ_RELEASE_ASSERT(::WriteFile(file.get(), stringBuffer.get(),
totalStringLen.value(), &written, nullptr));
DynamicBlockList blockList(blocklistFile);
::DeleteFileW(blocklistFile);
return blockList;
}
MOZ_RUNINIT const DynamicBlockList gFullList =
ConvertStaticBlocklistToDynamic(gWindowsDllBlocklist);
MOZ_RUNINIT const DynamicBlockList gShortList =
ConvertStaticBlocklistToDynamic(kDllBlocklistShort);
static bool TestDependentModules() {
LauncherVoidResult result = gSharedSection.Init();
if (result.isErr()) {
PrintLauncherError(result, "SharedSection::Init failed");
return false;
}
constexpr size_t sizeInBytes =
SharedSectionTestHelper::GetModulePathArraySize();
UniquePtr<uint8_t[]> bufferData = MakeUnique<uint8_t[]>(sizeInBytes);
Span<uint8_t> buffer(bufferData, sizeInBytes);
memset(buffer.data(), 0x88, buffer.size());
// Try to add a long string that does not fit in the section,
// since there's no room for the NULL character to indicate the final string.
UNICODE_STRING ustr;
ustr.Buffer = reinterpret_cast<wchar_t*>(buffer.data());
ustr.Length = ustr.MaximumLength = buffer.size();
result = gSharedSection.AddDependentModule(&ustr);
if (result.isOk() || result.inspectErr() != WindowsError::FromWin32Error(
ERROR_INSUFFICIENT_BUFFER)) {
printf(
"TEST-FAILED | TestCrossProcessWin | "
"Adding a too long string should fail.\n");
return false;
}
result = gSharedSection.Init();
if (result.isErr()) {
PrintLauncherError(result, "SharedSection::Init failed");
return false;
}
// Keep adding a single-char string until it fails and
// make sure no crash.
// We want to make sure no strings match any earlier strings so
// we can get the expected count. This is a little tricky since
// it includes case-insensitivity, so start at the "CJK Unified Ideographs
// Extension A" block of Unicode, which has no two characters that compare
// equal under a case insensitive comparison.
*(reinterpret_cast<wchar_t*>(buffer.data())) = 0x3400;
ustr.Length = ustr.MaximumLength = sizeof(wchar_t);
wchar_t numberOfStringsAdded = 0;
while (gSharedSection.AddDependentModule(&ustr).isOk()) {
++numberOfStringsAdded;
// Make sure the string doesn't match any earlier strings
wchar_t oldValue = *(reinterpret_cast<wchar_t*>(buffer.data()));
*(reinterpret_cast<wchar_t*>(buffer.data())) = oldValue + 1;
}
int numberOfCharactersInBuffer =
SharedSectionTestHelper::GetModulePathArraySize() / sizeof(wchar_t);
// Each string is two characters long (one "real" character and a null), but
// the whole buffer needs an additional null at the end.
int expectedNumberOfStringsAdded = (numberOfCharactersInBuffer - 1) / 2;
if (numberOfStringsAdded != expectedNumberOfStringsAdded) {
printf(
"TEST-FAILED | TestCrossProcessWin | "
"Added %d dependent strings before failing (expected %d).\n",
static_cast<int>(numberOfStringsAdded), expectedNumberOfStringsAdded);
return false;
}
// SetBlocklist is not allowed after AddDependentModule
result = gSharedSection.SetBlocklist(gShortList, false);
if (result.isOk() || result.inspectErr() !=
WindowsError::FromWin32Error(ERROR_INVALID_STATE)) {
printf(
"TEST-FAILED | TestCrossProcessWin | "
"SetBlocklist is not allowed after AddDependentModule\n");
return false;
}
gSharedSection.Reset();
return true;
}
static bool TestDynamicBlocklist() {
if (!gFullList.GetPayloadSize() || !gShortList.GetPayloadSize()) {
printf(
"TEST-FAILED | TestCrossProcessWin | DynamicBlockList::LoadFile "
"failed\n");
return false;
}
LauncherVoidResult result = gSharedSection.Init();
if (result.isErr()) {
PrintLauncherError(result, "SharedSection::Init failed");
return false;
}
// Set gShortList, and gShortList
// 1. Setting gShortList succeeds
// 2. Next try to set gShortList fails
result = gSharedSection.SetBlocklist(gShortList, false);
if (result.isErr()) {
PrintLauncherError(result, "SetBlocklist(gShortList) failed");
return false;
}
result = gSharedSection.SetBlocklist(gShortList, false);
if (result.isOk() || result.inspectErr() !=
WindowsError::FromWin32Error(ERROR_INVALID_STATE)) {
printf(
"TEST-FAILED | TestCrossProcessWin | "
"SetBlocklist is allowed only once\n");
return false;
}
result = gSharedSection.Init();
if (result.isErr()) {
PrintLauncherError(result, "SharedSection::Init failed");
return false;
}
// Add gFullList and gShortList
// 1. Adding gFullList always fails because it doesn't fit the section
// 2. Adding gShortList succeeds because no entry is added yet
MOZ_RELEASE_ASSERT(
gFullList.GetPayloadSize() >
SharedSectionTestHelper::GetModulePathArraySize(),
"Test assumes gFullList is too big to fit in shared section");
result = gSharedSection.SetBlocklist(gFullList, false);
if (result.isOk() || result.inspectErr() != WindowsError::FromWin32Error(
ERROR_INSUFFICIENT_BUFFER)) {
printf(
"TEST-FAILED | TestCrossProcessWin | "
"SetBlocklist(gFullList) should fail\n");
return false;
}
result = gSharedSection.SetBlocklist(gShortList, false);
if (result.isErr()) {
PrintLauncherError(result, "SetBlocklist(gShortList) failed");
return false;
}
// AddDependentModule is allowed after SetBlocklist
result = gSharedSection.AddDependentModule(&kTestDependentModuleString);
if (result.isErr()) {
PrintLauncherError(result, "SharedSection::AddDependentModule failed");
return false;
}
gSharedSection.Reset();
return true;
}
class ChildProcess final {
nsAutoHandle mChildProcess;
nsAutoHandle mChildMainThread;
DWORD mProcessId;
public:
// The following variables are updated from the parent process via
// WriteProcessMemory while the process is suspended as a part of
// TestWithChildProcess().
//
// Having both a non-const and a const is important because a constant
// is separately placed in the .rdata section which is read-only, so
// the region's attribute needs to be changed before modifying data via
// WriteProcessMemory.
// The keyword "volatile" is needed for a constant, otherwise the compiler
// evaluates a constant as a literal without fetching data from memory.
static HMODULE sExecutableImageBase;
static volatile const DWORD sReadOnlyProcessId;
static int Main() {
SRWLOCK lock = SRWLOCK_INIT;
::AcquireSRWLockExclusive(&lock);
Vector<std::thread> threads;
std::atomic<bool> success = true;
for (int i = 0; i < 10; ++i) {
Unused << threads.emplaceBack(
[&success](SRWLOCK* aLock) {
// All threads call GetKernel32Exports(), but only the first thread
// maps a write-copy section and populates it.
::AcquireSRWLockShared(aLock);
if (gSharedSection.GetKernel32Exports() == nullptr) {
success = false;
}
::ReleaseSRWLockShared(aLock);
},
&lock);
}
// Wait a msec for all threads to be ready and release the lock
::Sleep(1);
::ReleaseSRWLockExclusive(&lock);
for (auto& thread : threads) {
thread.join();
}
if (!success) {
printf(
"TEST-FAILED | TestCrossProcessWin | "
"GetKernel32Exports() returned null.\n");
return 1;
}
if (sExecutableImageBase != ::GetModuleHandle(nullptr)) {
printf(
"TEST-FAILED | TestCrossProcessWin | "
"sExecutableImageBase is expected to be %p, but actually was %p.\n",
::GetModuleHandle(nullptr), sExecutableImageBase);
return 1;
}
if (sReadOnlyProcessId != ::GetCurrentProcessId()) {
printf(
"TEST-FAILED | TestCrossProcessWin | "
"sReadOnlyProcessId is expected to be %lx, but actually was %lx.\n",
::GetCurrentProcessId(), sReadOnlyProcessId);
return 1;
}
if (!VerifySharedSection(gSharedSection)) {
return 1;
}
// Test a scenario to transfer a transferred section as a readonly handle
gSharedSection.ConvertToReadOnly();
// AddDependentModule fails as the handle is readonly.
LauncherVoidResult result =
gSharedSection.AddDependentModule(&kTestDependentModuleString);
if (result.inspectErr() !=
WindowsError::FromWin32Error(ERROR_ACCESS_DENIED)) {
PrintLauncherError(result, "The readonly section was writable");
return 1;
}
if (!VerifySharedSection(gSharedSection)) {
return 1;
}
return 0;
}
ChildProcess(const wchar_t* aExecutable, const wchar_t* aOption)
: mProcessId(0) {
const wchar_t* childArgv[] = {aExecutable, aOption};
auto cmdLine(mozilla::MakeCommandLine(std::size(childArgv), childArgv));
STARTUPINFOW si = {sizeof(si)};
PROCESS_INFORMATION pi;
BOOL ok =
::CreateProcessW(aExecutable, cmdLine.get(), nullptr, nullptr, FALSE,
CREATE_SUSPENDED, nullptr, nullptr, &si, &pi);
if (!ok) {
printf(
"TEST-FAILED | TestCrossProcessWin | "
"CreateProcessW falied - %08lx.\n",
GetLastError());
return;
}
mProcessId = pi.dwProcessId;
mChildProcess.own(pi.hProcess);
mChildMainThread.own(pi.hThread);
}
~ChildProcess() { ::TerminateProcess(mChildProcess, 0); }
operator HANDLE() const { return mChildProcess; }
DWORD GetProcessId() const { return mProcessId; }
bool ResumeAndWaitUntilExit() {
if (::ResumeThread(mChildMainThread) == 0xffffffff) {
printf(
"TEST-FAILED | TestCrossProcessWin | "
"ResumeThread failed - %08lx\n",
GetLastError());
return false;
}
if (::WaitForSingleObject(mChildProcess, 60000) != WAIT_OBJECT_0) {
printf(
"TEST-FAILED | TestCrossProcessWin | "
"Unexpected result from WaitForSingleObject\n");
return false;
}
DWORD exitCode;
if (!::GetExitCodeProcess(mChildProcess, &exitCode)) {
printf(
"TEST-FAILED | TestCrossProcessWin | "
"GetExitCodeProcess failed - %08lx\n",
GetLastError());
return false;
}
return exitCode == 0;
}
};
HMODULE ChildProcess::sExecutableImageBase = 0;
volatile const DWORD ChildProcess::sReadOnlyProcessId = 0;
int wmain(int argc, wchar_t* argv[]) {
printf("Process: %-8lx Base: %p\n", ::GetCurrentProcessId(),
::GetModuleHandle(nullptr));
if (argc == 2 && wcscmp(argv[1], kChildArg) == 0) {
return ChildProcess::Main();
}
ChildProcess childProcess(argv[0], kChildArg);
if (!childProcess) {
return 1;
}
if (!TestAddString()) {
return 1;
}
if (!TestDependentModules()) {
return 1;
}
if (!TestDynamicBlocklist()) {
return 1;
}
LauncherResult<HMODULE> remoteImageBase =
nt::GetProcessExeModule(childProcess);
if (remoteImageBase.isErr()) {
PrintLauncherError(remoteImageBase, "nt::GetProcessExeModule failed");
return 1;
}
nt::CrossExecTransferManager transferMgr(childProcess);
if (!transferMgr) {
printf(
"TEST-FAILED | TestCrossProcessWin | "
"CrossExecTransferManager instantiation failed.\n");
return 1;
}
LauncherVoidResult result =
transferMgr.Transfer(&ChildProcess::sExecutableImageBase,
&remoteImageBase.inspect(), sizeof(HMODULE));
if (result.isErr()) {
PrintLauncherError(result, "ChildProcess::WriteData(Imagebase) failed");
return 1;
}
DWORD childPid = childProcess.GetProcessId();
DWORD* readOnlyData = const_cast<DWORD*>(&ChildProcess::sReadOnlyProcessId);
result = transferMgr.Transfer(readOnlyData, &childPid, sizeof(DWORD));
if (result.isOk()) {
printf(
"TEST-UNEXPECTED | TestCrossProcessWin | "
"A constant was located in a writable section.");
return 1;
}
AutoVirtualProtect prot =
transferMgr.Protect(readOnlyData, sizeof(uint32_t), PAGE_READWRITE);
if (!prot) {
printf(
"TEST-FAILED | TestCrossProcessWin | "
"VirtualProtect failed - %08lx\n",
prot.GetError().AsHResult());
return 1;
}
result = transferMgr.Transfer(readOnlyData, &childPid, sizeof(DWORD));
if (result.isErr()) {
PrintLauncherError(result, "ChildProcess::WriteData(PID) failed");
return 1;
}
result = gSharedSection.Init();
if (result.isErr()) {
PrintLauncherError(result, "SharedSection::Init failed");
return 1;
}
result = gSharedSection.SetBlocklist(gShortList, false);
if (result.isErr()) {
PrintLauncherError(result, "SetBlocklist(gShortList) failed");
return false;
}
for (const char* testString : kTestDependentModulePaths) {
// Test AllocatedUnicodeString(const char*) that is used
// in IsDependentModule()
nt::AllocatedUnicodeString depModule(testString);
UNICODE_STRING depModuleLeafName;
nt::GetLeafName(&depModuleLeafName, depModule);
result = gSharedSection.AddDependentModule(&depModuleLeafName);
if (result.isErr()) {
PrintLauncherError(result, "SharedSection::AddDependentModule failed");
return 1;
}
}
result =
gSharedSection.TransferHandle(transferMgr, GENERIC_READ | GENERIC_WRITE);
if (result.isErr()) {
PrintLauncherError(result, "SharedSection::TransferHandle failed");
return 1;
}
// Close the section in the parent process before resuming the child process
gSharedSection.Reset(nullptr);
if (!childProcess.ResumeAndWaitUntilExit()) {
return 1;
}
printf("TEST-PASS | TestCrossProcessWin | All checks passed\n");
return 0;
}