tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

TestCrossProcessWin.cpp (23804B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
      6 
      7 #include <thread>
      8 #include <winternl.h>
      9 
     10 #define MOZ_USE_LAUNCHER_ERROR
     11 
     12 #include <atomic>
     13 #include <thread>
     14 #include "freestanding/SharedSection.cpp"
     15 #include "mozilla/CmdLineAndEnvUtils.h"
     16 #include "mozilla/DynamicBlocklist.h"
     17 #include "mozilla/NativeNt.h"
     18 #include "mozilla/Vector.h"
     19 
     20 #define DLL_BLOCKLIST_ENTRY(name, ...) \
     21  {MOZ_LITERAL_UNICODE_STRING(L##name), __VA_ARGS__},
     22 #define DLL_BLOCKLIST_STRING_TYPE UNICODE_STRING
     23 
     24 #include "mozilla/WindowsDllBlocklistLauncherDefs.h"
     25 
     26 const wchar_t kChildArg[] = L"--child";
     27 const char* kTestDependentModulePaths[] = {
     28    "\\Device\\HarddiskVolume4\\Windows\\system32\\A B C",
     29    "\\Device\\HarddiskVolume4\\Windows\\system32\\a b c.dll",
     30    "\\Device\\HarddiskVolume4\\Windows\\system32\\A B C.exe",
     31    "\\Device\\HarddiskVolume4\\Windows\\system32\\X Y Z.dll",
     32    "\\Device\\HarddiskVolume1\\a b C",
     33    "\\Device\\HarddiskVolume2\\A b c.DLL",
     34    "\\Device\\HarddiskVolume3\\A B c.exe",
     35    "\\Device\\HarddiskVolume4\\X y Z.dll",
     36 };
     37 const wchar_t* kExpectedDependentModules[] = {
     38    L"A B C",
     39    L"a b c.dll",
     40    L"A B C.exe",
     41    L"X Y Z.dll",
     42 };
     43 
     44 const UNICODE_STRING kStringNotInBlocklist =
     45    MOZ_LITERAL_UNICODE_STRING(L"Test_NotInBlocklist.dll");
     46 const UNICODE_STRING kTestDependentModuleString =
     47    MOZ_LITERAL_UNICODE_STRING(L"Test_DependentModule.dll");
     48 
     49 using namespace mozilla;
     50 using namespace mozilla::freestanding;
     51 
     52 // clang-format off
     53 const DllBlockInfo kDllBlocklistShort[] = {
     54  // The entries do not have to be sorted.
     55  DLL_BLOCKLIST_ENTRY("X Y Z_Test", MAKE_VERSION(1, 2, 65535, 65535))
     56  DLL_BLOCKLIST_ENTRY("\u30E9\u30FC\u30E1\u30F3_Test")
     57  DLL_BLOCKLIST_ENTRY("Avmvirtualsource_Test.ax", MAKE_VERSION(1, 0, 0, 3),
     58                      DllBlockInfoFlags::BROWSER_PROCESS_ONLY)
     59  DLL_BLOCKLIST_ENTRY("1ccelerator_Test.dll", MAKE_VERSION(3, 2, 1, 6))
     60  DLL_BLOCKLIST_ENTRY("atkdx11disp_Test.dll", DllBlockInfo::ALL_VERSIONS)
     61  {},
     62 };
     63 // clang-format on
     64 
     65 namespace mozilla::freestanding {
     66 class SharedSectionTestHelper {
     67 public:
     68  static constexpr size_t GetModulePathArraySize() {
     69    return SharedSection::kSharedViewSize -
     70           (offsetof(SharedSection::Layout, mFirstBlockEntry) +
     71            sizeof(DllBlockInfo));
     72  }
     73 };
     74 }  // namespace mozilla::freestanding
     75 
     76 class TempFile final {
     77  wchar_t mFullPath[MAX_PATH + 1];
     78 
     79 public:
     80  TempFile() : mFullPath{0} {
     81    wchar_t tempDir[MAX_PATH + 1];
     82    DWORD len = ::GetTempPathW(std::size(tempDir), tempDir);
     83    if (!len) {
     84      return;
     85    }
     86 
     87    len = ::GetTempFileNameW(tempDir, L"blocklist", 0, mFullPath);
     88    if (!len) {
     89      return;
     90    }
     91  }
     92 
     93  operator const wchar_t*() const { return mFullPath[0] ? mFullPath : nullptr; }
     94 };
     95 
     96 template <typename T, int N>
     97 void PrintLauncherError(const LauncherResult<T>& aResult,
     98                        const char (&aMsg)[N]) {
     99  const LauncherError& err = aResult.inspectErr();
    100  printf("TEST-FAILED | TestCrossProcessWin | %s - %lx at %s:%d\n", aMsg,
    101         err.mError.AsHResult(), err.mFile, err.mLine);
    102 }
    103 
    104 #define VERIFY_FUNCTION_RESOLVED(mod, exports, name)            \
    105  do {                                                          \
    106    if (reinterpret_cast<FARPROC>(exports->m##name) !=          \
    107        ::GetProcAddress(mod, #name)) {                         \
    108      printf(                                                   \
    109          "TEST-FAILED | TestCrossProcessWin | "                \
    110          "Kernel32ExportsSolver::" #name " did not match.\n"); \
    111      return false;                                             \
    112    }                                                           \
    113  } while (0)
    114 
    115 static bool VerifySharedSection(SharedSection& aSharedSection) {
    116  Kernel32ExportsSolver* k32Exports = aSharedSection.GetKernel32Exports();
    117  if (!k32Exports) {
    118    printf(
    119        "TEST-FAILED | TestCrossProcessWin | Failed to map a shared section\n");
    120    return false;
    121  }
    122 
    123  HMODULE k32mod = ::GetModuleHandleW(L"kernel32.dll");
    124  VERIFY_FUNCTION_RESOLVED(k32mod, k32Exports, FlushInstructionCache);
    125  VERIFY_FUNCTION_RESOLVED(k32mod, k32Exports, GetModuleHandleW);
    126  VERIFY_FUNCTION_RESOLVED(k32mod, k32Exports, GetSystemInfo);
    127  VERIFY_FUNCTION_RESOLVED(k32mod, k32Exports, VirtualProtect);
    128 
    129  Maybe<Vector<const wchar_t*>> modulesArray =
    130      aSharedSection.GetDependentModules();
    131  if (modulesArray.isNothing()) {
    132    printf(
    133        "TEST-FAILED | TestCrossProcessWin | GetDependentModules returned "
    134        "Nothing");
    135    return false;
    136  }
    137  bool matched =
    138      modulesArray->length() ==
    139      sizeof(kExpectedDependentModules) / sizeof(kExpectedDependentModules[0]);
    140  if (matched) {
    141    for (size_t i = 0; i < modulesArray->length(); ++i) {
    142      if (wcscmp((*modulesArray)[i], kExpectedDependentModules[i])) {
    143        matched = false;
    144        break;
    145      }
    146    }
    147  }
    148  if (!matched) {
    149    // Print actual strings on error
    150    for (const wchar_t* p : *modulesArray) {
    151      printf("%p: %S\n", p, p);
    152    }
    153    return false;
    154  }
    155 
    156  for (const DllBlockInfo* info = kDllBlocklistShort; info->mName.Buffer;
    157       ++info) {
    158    const DllBlockInfo* matched = aSharedSection.SearchBlocklist(info->mName);
    159    if (!matched) {
    160      printf(
    161          "TEST-FAILED | TestCrossProcessWin | No blocklist entry match for "
    162          "entry in blocklist.\n");
    163      return false;
    164    }
    165  }
    166 
    167  if (aSharedSection.SearchBlocklist(kStringNotInBlocklist)) {
    168    printf(
    169        "TEST-FAILED | TestCrossProcessWin | Found blocklist entry match for "
    170        "something not in the blocklist.\n");
    171  }
    172 
    173  if (aSharedSection.IsDisabled()) {
    174    printf("TEST-FAILED | TestCrossProcessWin | Wrong disabled value.\n");
    175  }
    176 
    177  return true;
    178 }
    179 
    180 static bool TestAddString() {
    181  wchar_t testBuffer[3] = {0};
    182  UNICODE_STRING ustr;
    183 
    184  // This makes |testBuffer| full.
    185  ::RtlInitUnicodeString(&ustr, L"a");
    186  if (!AddString(testBuffer, ustr)) {
    187    printf(
    188        "TEST-FAILED | TestCrossProcessWin | "
    189        "AddString failed.\n");
    190    return false;
    191  }
    192 
    193  // Adding a string to a full buffer should fail.
    194  ::RtlInitUnicodeString(&ustr, L"b");
    195  if (AddString(testBuffer, ustr)) {
    196    printf(
    197        "TEST-FAILED | TestCrossProcessWin | "
    198        "AddString caused OOB memory access.\n");
    199    return false;
    200  }
    201 
    202  bool matched = memcmp(testBuffer, L"a\0", sizeof(testBuffer)) == 0;
    203  if (!matched) {
    204    printf(
    205        "TEST-FAILED | TestCrossProcessWin | "
    206        "AddString wrote wrong values.\n");
    207    return false;
    208  }
    209 
    210  return true;
    211 }
    212 
    213 // Convert |aBlockEntries|, which is an array ending with an empty instance
    214 // of DllBlockInfo, to DynamicBlockList by storing it to a temp file, loading
    215 // as DynamicBlockList, and deleting the temp file.
    216 static DynamicBlockList ConvertStaticBlocklistToDynamic(
    217    const DllBlockInfo aBlockEntries[]) {
    218  size_t originalLength = 0;
    219  CheckedUint32 totalStringLen = 0;
    220  for (const DllBlockInfo* entry = aBlockEntries; entry->mName.Length;
    221       ++entry) {
    222    totalStringLen += entry->mName.Length;
    223    MOZ_RELEASE_ASSERT(totalStringLen.isValid());
    224    ++originalLength;
    225  }
    226 
    227  // Pack all strings in this buffer without null characters
    228  UniquePtr<uint8_t[]> stringBuffer =
    229      MakeUnique<uint8_t[]>(totalStringLen.value());
    230 
    231  // The string buffer is placed immediately after the array of DllBlockInfo
    232  const size_t stringBufferOffset = (originalLength + 1) * sizeof(DllBlockInfo);
    233 
    234  // Entries in the dynamic blocklist do have to be sorted,
    235  // unlike in the static blocklist.
    236  UniquePtr<DllBlockInfo[]> sortedBlockEntries =
    237      MakeUnique<DllBlockInfo[]>(originalLength);
    238  memcpy(sortedBlockEntries.get(), aBlockEntries,
    239         sizeof(DllBlockInfo) * originalLength);
    240  std::sort(sortedBlockEntries.get(), sortedBlockEntries.get() + originalLength,
    241            [](const DllBlockInfo& a, const DllBlockInfo& b) {
    242              return ::RtlCompareUnicodeString(&a.mName, &b.mName, TRUE) < 0;
    243            });
    244 
    245  Vector<DllBlockInfo> copied;
    246  (void)copied.resize(originalLength + 1);  // aBlockEntries + sentinel
    247 
    248  size_t currentStringOffset = 0;
    249  for (size_t i = 0; i < originalLength; ++i) {
    250    copied[i].mMaxVersion = sortedBlockEntries[i].mMaxVersion;
    251    copied[i].mFlags = sortedBlockEntries[i].mFlags;
    252 
    253    // Copy the module's name to the string buffer and store its offset
    254    // in mName.Buffer
    255    memcpy(stringBuffer.get() + currentStringOffset,
    256           sortedBlockEntries[i].mName.Buffer,
    257           sortedBlockEntries[i].mName.Length);
    258    copied[i].mName.Buffer =
    259        reinterpret_cast<wchar_t*>(stringBufferOffset + currentStringOffset);
    260    // Only keep mName.Length and leave mName.MaximumLength to be zero
    261    copied[i].mName.Length = sortedBlockEntries[i].mName.Length;
    262 
    263    currentStringOffset += sortedBlockEntries[i].mName.Length;
    264  }
    265 
    266  TempFile blocklistFile;
    267  nsAutoHandle file(::CreateFileW(blocklistFile, GENERIC_WRITE, FILE_SHARE_READ,
    268                                  nullptr, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL,
    269                                  nullptr));
    270  MOZ_RELEASE_ASSERT(file);
    271 
    272  DynamicBlockListBase::FileHeader header;
    273  header.mSignature = DynamicBlockListBase::kSignature;
    274  header.mFileVersion = DynamicBlockListBase::kCurrentVersion;
    275  header.mPayloadSize =
    276      sizeof(DllBlockInfo) * copied.length() + totalStringLen.value();
    277 
    278  DWORD written = 0;
    279  MOZ_RELEASE_ASSERT(
    280      ::WriteFile(file.get(), &header, sizeof(header), &written, nullptr));
    281  MOZ_RELEASE_ASSERT(::WriteFile(file.get(), copied.begin(),
    282                                 sizeof(DllBlockInfo) * copied.length(),
    283                                 &written, nullptr));
    284  MOZ_RELEASE_ASSERT(::WriteFile(file.get(), stringBuffer.get(),
    285                                 totalStringLen.value(), &written, nullptr));
    286 
    287  DynamicBlockList blockList(blocklistFile);
    288  ::DeleteFileW(blocklistFile);
    289  return blockList;
    290 }
    291 
    292 MOZ_RUNINIT const DynamicBlockList gFullList =
    293    ConvertStaticBlocklistToDynamic(gWindowsDllBlocklist);
    294 MOZ_RUNINIT const DynamicBlockList gShortList =
    295    ConvertStaticBlocklistToDynamic(kDllBlocklistShort);
    296 
    297 static bool TestDependentModules() {
    298  LauncherVoidResult result = gSharedSection.Init();
    299  if (result.isErr()) {
    300    PrintLauncherError(result, "SharedSection::Init failed");
    301    return false;
    302  }
    303 
    304  constexpr size_t sizeInBytes =
    305      SharedSectionTestHelper::GetModulePathArraySize();
    306  UniquePtr<uint8_t[]> bufferData = MakeUnique<uint8_t[]>(sizeInBytes);
    307  Span<uint8_t> buffer(bufferData, sizeInBytes);
    308  memset(buffer.data(), 0x88, buffer.size());
    309 
    310  // Try to add a long string that does not fit in the section,
    311  // since there's no room for the NULL character to indicate the final string.
    312  UNICODE_STRING ustr;
    313  ustr.Buffer = reinterpret_cast<wchar_t*>(buffer.data());
    314  ustr.Length = ustr.MaximumLength = buffer.size();
    315 
    316  result = gSharedSection.AddDependentModule(&ustr);
    317  if (result.isOk() || result.inspectErr() != WindowsError::FromWin32Error(
    318                                                  ERROR_INSUFFICIENT_BUFFER)) {
    319    printf(
    320        "TEST-FAILED | TestCrossProcessWin | "
    321        "Adding a too long string should fail.\n");
    322    return false;
    323  }
    324 
    325  result = gSharedSection.Init();
    326  if (result.isErr()) {
    327    PrintLauncherError(result, "SharedSection::Init failed");
    328    return false;
    329  }
    330 
    331  // Keep adding a single-char string until it fails and
    332  // make sure no crash.
    333  // We want to make sure no strings match any earlier strings so
    334  // we can get the expected count. This is a little tricky since
    335  // it includes case-insensitivity, so start at the "CJK Unified Ideographs
    336  // Extension A" block of Unicode, which has no two characters that compare
    337  // equal under a case insensitive comparison.
    338  *(reinterpret_cast<wchar_t*>(buffer.data())) = 0x3400;
    339  ustr.Length = ustr.MaximumLength = sizeof(wchar_t);
    340  wchar_t numberOfStringsAdded = 0;
    341  while (gSharedSection.AddDependentModule(&ustr).isOk()) {
    342    ++numberOfStringsAdded;
    343    // Make sure the string doesn't match any earlier strings
    344    wchar_t oldValue = *(reinterpret_cast<wchar_t*>(buffer.data()));
    345    *(reinterpret_cast<wchar_t*>(buffer.data())) = oldValue + 1;
    346  }
    347 
    348  int numberOfCharactersInBuffer =
    349      SharedSectionTestHelper::GetModulePathArraySize() / sizeof(wchar_t);
    350  // Each string is two characters long (one "real" character and a null), but
    351  // the whole buffer needs an additional null at the end.
    352  int expectedNumberOfStringsAdded = (numberOfCharactersInBuffer - 1) / 2;
    353  if (numberOfStringsAdded != expectedNumberOfStringsAdded) {
    354    printf(
    355        "TEST-FAILED | TestCrossProcessWin | "
    356        "Added %d dependent strings before failing (expected %d).\n",
    357        static_cast<int>(numberOfStringsAdded), expectedNumberOfStringsAdded);
    358    return false;
    359  }
    360 
    361  // SetBlocklist is not allowed after AddDependentModule
    362  result = gSharedSection.SetBlocklist(gShortList, false);
    363  if (result.isOk() || result.inspectErr() !=
    364                           WindowsError::FromWin32Error(ERROR_INVALID_STATE)) {
    365    printf(
    366        "TEST-FAILED | TestCrossProcessWin | "
    367        "SetBlocklist is not allowed after AddDependentModule\n");
    368    return false;
    369  }
    370 
    371  gSharedSection.Reset();
    372  return true;
    373 }
    374 
    375 static bool TestDynamicBlocklist() {
    376  if (!gFullList.GetPayloadSize() || !gShortList.GetPayloadSize()) {
    377    printf(
    378        "TEST-FAILED | TestCrossProcessWin | DynamicBlockList::LoadFile "
    379        "failed\n");
    380    return false;
    381  }
    382 
    383  LauncherVoidResult result = gSharedSection.Init();
    384  if (result.isErr()) {
    385    PrintLauncherError(result, "SharedSection::Init failed");
    386    return false;
    387  }
    388 
    389  // Set gShortList, and gShortList
    390  // 1. Setting gShortList succeeds
    391  // 2. Next try to set gShortList fails
    392  result = gSharedSection.SetBlocklist(gShortList, false);
    393  if (result.isErr()) {
    394    PrintLauncherError(result, "SetBlocklist(gShortList) failed");
    395    return false;
    396  }
    397  result = gSharedSection.SetBlocklist(gShortList, false);
    398  if (result.isOk() || result.inspectErr() !=
    399                           WindowsError::FromWin32Error(ERROR_INVALID_STATE)) {
    400    printf(
    401        "TEST-FAILED | TestCrossProcessWin | "
    402        "SetBlocklist is allowed only once\n");
    403    return false;
    404  }
    405 
    406  result = gSharedSection.Init();
    407  if (result.isErr()) {
    408    PrintLauncherError(result, "SharedSection::Init failed");
    409    return false;
    410  }
    411 
    412  // Add gFullList and gShortList
    413  // 1. Adding gFullList always fails because it doesn't fit the section
    414  // 2. Adding gShortList succeeds because no entry is added yet
    415  MOZ_RELEASE_ASSERT(
    416      gFullList.GetPayloadSize() >
    417          SharedSectionTestHelper::GetModulePathArraySize(),
    418      "Test assumes gFullList is too big to fit in shared section");
    419  result = gSharedSection.SetBlocklist(gFullList, false);
    420  if (result.isOk() || result.inspectErr() != WindowsError::FromWin32Error(
    421                                                  ERROR_INSUFFICIENT_BUFFER)) {
    422    printf(
    423        "TEST-FAILED | TestCrossProcessWin | "
    424        "SetBlocklist(gFullList) should fail\n");
    425    return false;
    426  }
    427  result = gSharedSection.SetBlocklist(gShortList, false);
    428  if (result.isErr()) {
    429    PrintLauncherError(result, "SetBlocklist(gShortList) failed");
    430    return false;
    431  }
    432 
    433  // AddDependentModule is allowed after SetBlocklist
    434  result = gSharedSection.AddDependentModule(&kTestDependentModuleString);
    435  if (result.isErr()) {
    436    PrintLauncherError(result, "SharedSection::AddDependentModule failed");
    437    return false;
    438  }
    439 
    440  gSharedSection.Reset();
    441  return true;
    442 }
    443 
    444 class ChildProcess final {
    445  nsAutoHandle mChildProcess;
    446  nsAutoHandle mChildMainThread;
    447  DWORD mProcessId;
    448 
    449 public:
    450  // The following variables are updated from the parent process via
    451  // WriteProcessMemory while the process is suspended as a part of
    452  // TestWithChildProcess().
    453  //
    454  // Having both a non-const and a const is important because a constant
    455  // is separately placed in the .rdata section which is read-only, so
    456  // the region's attribute needs to be changed before modifying data via
    457  // WriteProcessMemory.
    458  // The keyword "volatile" is needed for a constant, otherwise the compiler
    459  // evaluates a constant as a literal without fetching data from memory.
    460  static HMODULE sExecutableImageBase;
    461  static volatile const DWORD sReadOnlyProcessId;
    462 
    463  static int Main() {
    464    SRWLOCK lock = SRWLOCK_INIT;
    465    ::AcquireSRWLockExclusive(&lock);
    466 
    467    Vector<std::thread> threads;
    468    std::atomic<bool> success = true;
    469    for (int i = 0; i < 10; ++i) {
    470      (void)threads.emplaceBack(
    471          [&success](SRWLOCK* aLock) {
    472            // All threads call GetKernel32Exports(), but only the first thread
    473            // maps a write-copy section and populates it.
    474            ::AcquireSRWLockShared(aLock);
    475            if (gSharedSection.GetKernel32Exports() == nullptr) {
    476              success = false;
    477            }
    478            ::ReleaseSRWLockShared(aLock);
    479          },
    480          &lock);
    481    }
    482 
    483    // Wait a msec for all threads to be ready and release the lock
    484    ::Sleep(1);
    485    ::ReleaseSRWLockExclusive(&lock);
    486 
    487    for (auto& thread : threads) {
    488      thread.join();
    489    }
    490 
    491    if (!success) {
    492      printf(
    493          "TEST-FAILED | TestCrossProcessWin | "
    494          "GetKernel32Exports() returned null.\n");
    495      return 1;
    496    }
    497 
    498    if (sExecutableImageBase != ::GetModuleHandle(nullptr)) {
    499      printf(
    500          "TEST-FAILED | TestCrossProcessWin | "
    501          "sExecutableImageBase is expected to be %p, but actually was %p.\n",
    502          ::GetModuleHandle(nullptr), sExecutableImageBase);
    503      return 1;
    504    }
    505 
    506    if (sReadOnlyProcessId != ::GetCurrentProcessId()) {
    507      printf(
    508          "TEST-FAILED | TestCrossProcessWin | "
    509          "sReadOnlyProcessId is expected to be %lx, but actually was %lx.\n",
    510          ::GetCurrentProcessId(), sReadOnlyProcessId);
    511      return 1;
    512    }
    513 
    514    if (!VerifySharedSection(gSharedSection)) {
    515      return 1;
    516    }
    517 
    518    // Test a scenario to transfer a transferred section as a readonly handle
    519    gSharedSection.ConvertToReadOnly();
    520 
    521    // AddDependentModule fails as the handle is readonly.
    522    LauncherVoidResult result =
    523        gSharedSection.AddDependentModule(&kTestDependentModuleString);
    524    if (result.inspectErr() !=
    525        WindowsError::FromWin32Error(ERROR_ACCESS_DENIED)) {
    526      PrintLauncherError(result, "The readonly section was writable");
    527      return 1;
    528    }
    529 
    530    if (!VerifySharedSection(gSharedSection)) {
    531      return 1;
    532    }
    533 
    534    return 0;
    535  }
    536 
    537  ChildProcess(const wchar_t* aExecutable, const wchar_t* aOption)
    538      : mProcessId(0) {
    539    const wchar_t* childArgv[] = {aExecutable, aOption};
    540    auto cmdLine(mozilla::MakeCommandLine(std::size(childArgv), childArgv));
    541 
    542    STARTUPINFOW si = {sizeof(si)};
    543    PROCESS_INFORMATION pi;
    544    BOOL ok =
    545        ::CreateProcessW(aExecutable, cmdLine.get(), nullptr, nullptr, FALSE,
    546                         CREATE_SUSPENDED, nullptr, nullptr, &si, &pi);
    547    if (!ok) {
    548      printf(
    549          "TEST-FAILED | TestCrossProcessWin | "
    550          "CreateProcessW falied - %08lx.\n",
    551          GetLastError());
    552      return;
    553    }
    554 
    555    mProcessId = pi.dwProcessId;
    556 
    557    mChildProcess.own(pi.hProcess);
    558    mChildMainThread.own(pi.hThread);
    559  }
    560 
    561  ~ChildProcess() { ::TerminateProcess(mChildProcess, 0); }
    562 
    563  operator HANDLE() const { return mChildProcess; }
    564  DWORD GetProcessId() const { return mProcessId; }
    565 
    566  bool ResumeAndWaitUntilExit() {
    567    if (::ResumeThread(mChildMainThread) == 0xffffffff) {
    568      printf(
    569          "TEST-FAILED | TestCrossProcessWin | "
    570          "ResumeThread failed - %08lx\n",
    571          GetLastError());
    572      return false;
    573    }
    574 
    575    if (::WaitForSingleObject(mChildProcess, 60000) != WAIT_OBJECT_0) {
    576      printf(
    577          "TEST-FAILED | TestCrossProcessWin | "
    578          "Unexpected result from WaitForSingleObject\n");
    579      return false;
    580    }
    581 
    582    DWORD exitCode;
    583    if (!::GetExitCodeProcess(mChildProcess, &exitCode)) {
    584      printf(
    585          "TEST-FAILED | TestCrossProcessWin | "
    586          "GetExitCodeProcess failed - %08lx\n",
    587          GetLastError());
    588      return false;
    589    }
    590 
    591    return exitCode == 0;
    592  }
    593 };
    594 
    595 HMODULE ChildProcess::sExecutableImageBase = 0;
    596 volatile const DWORD ChildProcess::sReadOnlyProcessId = 0;
    597 
    598 int wmain(int argc, wchar_t* argv[]) {
    599  printf("Process: %-8lx Base: %p\n", ::GetCurrentProcessId(),
    600         ::GetModuleHandle(nullptr));
    601 
    602  if (argc == 2 && wcscmp(argv[1], kChildArg) == 0) {
    603    return ChildProcess::Main();
    604  }
    605 
    606  ChildProcess childProcess(argv[0], kChildArg);
    607  if (!childProcess) {
    608    return 1;
    609  }
    610 
    611  if (!TestAddString()) {
    612    return 1;
    613  }
    614 
    615  if (!TestDependentModules()) {
    616    return 1;
    617  }
    618 
    619  if (!TestDynamicBlocklist()) {
    620    return 1;
    621  }
    622 
    623  LauncherResult<HMODULE> remoteImageBase =
    624      nt::GetProcessExeModule(childProcess);
    625  if (remoteImageBase.isErr()) {
    626    PrintLauncherError(remoteImageBase, "nt::GetProcessExeModule failed");
    627    return 1;
    628  }
    629 
    630  nt::CrossExecTransferManager transferMgr(childProcess);
    631  if (!transferMgr) {
    632    printf(
    633        "TEST-FAILED | TestCrossProcessWin | "
    634        "CrossExecTransferManager instantiation failed.\n");
    635    return 1;
    636  }
    637 
    638  LauncherVoidResult result =
    639      transferMgr.Transfer(&ChildProcess::sExecutableImageBase,
    640                           &remoteImageBase.inspect(), sizeof(HMODULE));
    641  if (result.isErr()) {
    642    PrintLauncherError(result, "ChildProcess::WriteData(Imagebase) failed");
    643    return 1;
    644  }
    645 
    646  DWORD childPid = childProcess.GetProcessId();
    647 
    648  DWORD* readOnlyData = const_cast<DWORD*>(&ChildProcess::sReadOnlyProcessId);
    649  result = transferMgr.Transfer(readOnlyData, &childPid, sizeof(DWORD));
    650  if (result.isOk()) {
    651    printf(
    652        "TEST-UNEXPECTED | TestCrossProcessWin | "
    653        "A constant was located in a writable section.");
    654    return 1;
    655  }
    656 
    657  AutoVirtualProtect prot =
    658      transferMgr.Protect(readOnlyData, sizeof(uint32_t), PAGE_READWRITE);
    659  if (!prot) {
    660    printf(
    661        "TEST-FAILED | TestCrossProcessWin | "
    662        "VirtualProtect failed - %08lx\n",
    663        prot.GetError().AsHResult());
    664    return 1;
    665  }
    666 
    667  result = transferMgr.Transfer(readOnlyData, &childPid, sizeof(DWORD));
    668  if (result.isErr()) {
    669    PrintLauncherError(result, "ChildProcess::WriteData(PID) failed");
    670    return 1;
    671  }
    672 
    673  result = gSharedSection.Init();
    674  if (result.isErr()) {
    675    PrintLauncherError(result, "SharedSection::Init failed");
    676    return 1;
    677  }
    678 
    679  result = gSharedSection.SetBlocklist(gShortList, false);
    680  if (result.isErr()) {
    681    PrintLauncherError(result, "SetBlocklist(gShortList) failed");
    682    return false;
    683  }
    684 
    685  for (const char* testString : kTestDependentModulePaths) {
    686    // Test AllocatedUnicodeString(const char*) that is used
    687    // in IsDependentModule()
    688    nt::AllocatedUnicodeString depModule(testString);
    689    UNICODE_STRING depModuleLeafName;
    690    nt::GetLeafName(&depModuleLeafName, depModule);
    691    result = gSharedSection.AddDependentModule(&depModuleLeafName);
    692    if (result.isErr()) {
    693      PrintLauncherError(result, "SharedSection::AddDependentModule failed");
    694      return 1;
    695    }
    696  }
    697 
    698  result =
    699      gSharedSection.TransferHandle(transferMgr, GENERIC_READ | GENERIC_WRITE);
    700  if (result.isErr()) {
    701    PrintLauncherError(result, "SharedSection::TransferHandle failed");
    702    return 1;
    703  }
    704 
    705  // Close the section in the parent process before resuming the child process
    706  gSharedSection.Reset(nullptr);
    707 
    708  if (!childProcess.ResumeAndWaitUntilExit()) {
    709    return 1;
    710  }
    711 
    712  printf("TEST-PASS | TestCrossProcessWin | All checks passed\n");
    713  return 0;
    714 }