tor-browser

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

TestPEExportSection.cpp (24771B)


      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 // This test makes sure mozilla::nt::PEExportSection can parse the export
      8 // section of a local process, and a remote process even though it's
      9 // modified by an external code.
     10 
     11 #include "mozilla/CmdLineAndEnvUtils.h"
     12 #include "mozilla/NativeNt.h"
     13 #include "nsWindowsDllInterceptor.h"
     14 
     15 #include <stdio.h>
     16 #include <windows.h>
     17 
     18 #define EXPORT_FUNCTION_EQ(name, func) \
     19  (GetProcAddress(imageBase, name) == reinterpret_cast<void*>(func))
     20 
     21 #define VERIFY_EXPORT_FUNCTION(tables, name, expected, errorMessage)        \
     22  do {                                                                      \
     23    if (tables.GetProcAddress(name) != reinterpret_cast<void*>(expected)) { \
     24      printf("TEST-FAILED | TestPEExportSection | %s", errorMessage);       \
     25      return kTestFail;                                                     \
     26    }                                                                       \
     27  } while (0)
     28 
     29 using namespace mozilla::nt;
     30 using mozilla::interceptor::MMPolicyInProcess;
     31 using mozilla::interceptor::MMPolicyOutOfProcess;
     32 using LocalPEExportSection = PEExportSection<MMPolicyInProcess>;
     33 using RemotePEExportSection = PEExportSection<MMPolicyOutOfProcess>;
     34 
     35 constexpr DWORD kEventTimeoutinMs = 5000;
     36 const wchar_t kProcessControlEventName[] =
     37    L"TestPEExportSection.Process.Control.Event";
     38 
     39 enum TestResult : int {
     40  kTestSuccess = 0,
     41  kTestFail,
     42  kTestSkip,
     43 };
     44 
     45 // These strings start with the same keyword to make sure we don't do substring
     46 // match.  Moreover, kSecretFunctionInvalid is purposely longer than the
     47 // combination of the other two strings and located in between the other two
     48 // strings to effectively test binary search.
     49 const char kSecretFunction[] = "Secret";
     50 const char kSecretFunctionInvalid[] = "Secret invalid long name";
     51 const char kSecretFunctionWithSuffix[] = "Secret2";
     52 
     53 const wchar_t* kNoModification = L"--NoModification";
     54 const wchar_t* kNoExport = L"--NoExport";
     55 const wchar_t* kModifyTableEntry = L"--ModifyTableEntry";
     56 const wchar_t* kModifyTable = L"--ModifyTable";
     57 const wchar_t* kModifyDirectoryEntry = L"--ModifyDirectoryEntry";
     58 const wchar_t* kExportByOrdinal = L"--ExportByOrdinal";
     59 
     60 // Use the global variable to pass the child process's error status to the
     61 // parent process.  We don't use a process's exit code to keep the test simple.
     62 int gChildProcessStatus = 0;
     63 
     64 // These functions are exported by linker or export section tampering at
     65 // runtime.  Each of function bodies needs to be different to avoid ICF.
     66 extern "C" __declspec(dllexport) int Export1() { return 0; }
     67 extern "C" __declspec(dllexport) int Export2() { return 1; }
     68 int SecretFunction1() { return 100; }
     69 int SecretFunction2() { return 101; }
     70 
     71 // This class allocates a writable region downstream of the mapped image
     72 // and prepares it as a valid export section.
     73 class ExportDirectoryPatcher final {
     74  static constexpr int kRegionAllocationTryLimit = 100;
     75  static constexpr int kNumOfTableEntries = 2;
     76  // VirtualAlloc sometimes fails if a desired base address is too small.
     77  // Define a minimum desired base to reduce the number of allocation tries.
     78  static constexpr uintptr_t kMinimumAllocationPoint = 0x8000000;
     79 
     80  struct ExportDirectory {
     81    IMAGE_EXPORT_DIRECTORY mDirectoryHeader;
     82    DWORD mExportAddressTable[kNumOfTableEntries];
     83    DWORD mExportNameTable[kNumOfTableEntries];
     84    WORD mExportOrdinalTable[kNumOfTableEntries];
     85    char mNameBuffer1[sizeof(kSecretFunction)];
     86    char mNameBuffer2[sizeof(kSecretFunctionWithSuffix)];
     87 
     88    template <typename T>
     89    static DWORD PtrToRVA(T aPtr, uintptr_t aBase) {
     90      return reinterpret_cast<uintptr_t>(aPtr) - aBase;
     91    }
     92 
     93    explicit ExportDirectory(uintptr_t aImageBase) : mDirectoryHeader{} {
     94      mDirectoryHeader.Base = 1;
     95      mExportAddressTable[0] = PtrToRVA(SecretFunction1, aImageBase);
     96      mExportAddressTable[1] = PtrToRVA(SecretFunction2, aImageBase);
     97      mExportNameTable[0] = PtrToRVA(mNameBuffer1, aImageBase);
     98      mExportNameTable[1] = PtrToRVA(mNameBuffer2, aImageBase);
     99      mExportOrdinalTable[0] = 0;
    100      mExportOrdinalTable[1] = 1;
    101      strcpy(mNameBuffer1, kSecretFunction);
    102      strcpy(mNameBuffer2, kSecretFunctionWithSuffix);
    103    }
    104  };
    105 
    106  uintptr_t mImageBase;
    107  ExportDirectory* mNewExportDirectory;
    108 
    109  DWORD PtrToRVA(const void* aPtr) const {
    110    return reinterpret_cast<uintptr_t>(aPtr) - mImageBase;
    111  }
    112 
    113 public:
    114  explicit ExportDirectoryPatcher(HMODULE aModule)
    115      : mImageBase(PEHeaders::HModuleToBaseAddr<uintptr_t>(aModule)),
    116        mNewExportDirectory(nullptr) {
    117    SYSTEM_INFO si = {};
    118    ::GetSystemInfo(&si);
    119 
    120    int numPagesRequired = ((sizeof(ExportDirectory) - 1) / si.dwPageSize) + 1;
    121 
    122    uintptr_t desiredBase = mImageBase + si.dwAllocationGranularity;
    123    desiredBase = std::max(desiredBase, kMinimumAllocationPoint);
    124 
    125    for (int i = 0; i < kRegionAllocationTryLimit; ++i) {
    126      void* allocated =
    127          ::VirtualAlloc(reinterpret_cast<void*>(desiredBase),
    128                         numPagesRequired * si.dwPageSize,
    129                         MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
    130      if (allocated) {
    131        // Use the end of a allocated page as ExportDirectory in order to test
    132        // the boundary between a commit page and a non-commited page.
    133        allocated = reinterpret_cast<uint8_t*>(allocated) +
    134                    (numPagesRequired * si.dwPageSize) -
    135                    sizeof(ExportDirectory);
    136        mNewExportDirectory = new (allocated) ExportDirectory(mImageBase);
    137        return;
    138      }
    139 
    140      desiredBase += si.dwAllocationGranularity;
    141    }
    142 
    143    gChildProcessStatus = kTestSkip;
    144    printf(
    145        "TEST-SKIP | TestPEExportSection | "
    146        "Giving up finding an allocatable space following the mapped image.\n");
    147  }
    148 
    149  ~ExportDirectoryPatcher() {
    150    // Intentionally leave mNewExportDirectory leaked to keep a patched data
    151    // available until the process is terminated.
    152  }
    153 
    154  explicit operator bool() const { return !!mNewExportDirectory; }
    155 
    156  void PopulateDirectory(IMAGE_EXPORT_DIRECTORY& aOutput) const {
    157    aOutput.NumberOfFunctions = aOutput.NumberOfNames = kNumOfTableEntries;
    158    aOutput.AddressOfFunctions =
    159        PtrToRVA(mNewExportDirectory->mExportAddressTable);
    160    aOutput.AddressOfNames = PtrToRVA(mNewExportDirectory->mExportNameTable);
    161    aOutput.AddressOfNameOrdinals =
    162        PtrToRVA(mNewExportDirectory->mExportOrdinalTable);
    163  }
    164 
    165  void PopulateDirectoryEntry(IMAGE_DATA_DIRECTORY& aOutput) const {
    166    PopulateDirectory(mNewExportDirectory->mDirectoryHeader);
    167    aOutput.VirtualAddress = PtrToRVA(&mNewExportDirectory->mDirectoryHeader);
    168    aOutput.Size = sizeof(ExportDirectory);
    169  }
    170 };
    171 
    172 // This exports SecretFunction1 as "Export1" by replacing an entry of the
    173 // export address table.
    174 void ModifyExportAddressTableEntry() {
    175  MMPolicyInProcess policy;
    176  HMODULE imageBase = ::GetModuleHandleW(nullptr);
    177  auto ourExe = LocalPEExportSection::Get(imageBase, policy);
    178 
    179  auto addressTableEntry =
    180      const_cast<DWORD*>(ourExe.FindExportAddressTableEntry("Export1"));
    181  if (!addressTableEntry) {
    182    gChildProcessStatus = kTestFail;
    183    return;
    184  }
    185 
    186  mozilla::AutoVirtualProtect protection(
    187      addressTableEntry, sizeof(*addressTableEntry), PAGE_READWRITE);
    188  if (!protection) {
    189    gChildProcessStatus = kTestFail;
    190    return;
    191  }
    192 
    193  *addressTableEntry = reinterpret_cast<uintptr_t>(SecretFunction1) -
    194                       PEHeaders::HModuleToBaseAddr<uintptr_t>(imageBase);
    195 
    196  if (!EXPORT_FUNCTION_EQ("Export1", SecretFunction1) ||
    197      !EXPORT_FUNCTION_EQ("Export2", Export2)) {
    198    gChildProcessStatus = kTestFail;
    199  }
    200 }
    201 
    202 // This switches the entire address table into one exporting SecretFunction1
    203 // and SecretFunction2.
    204 void ModifyExportAddressTable() {
    205  MMPolicyInProcess policy;
    206  HMODULE imageBase = ::GetModuleHandleW(nullptr);
    207  auto ourExe = LocalPEExportSection::Get(imageBase, policy);
    208 
    209  auto exportDirectory = ourExe.GetExportDirectory();
    210  if (!exportDirectory) {
    211    gChildProcessStatus = kTestFail;
    212    return;
    213  }
    214 
    215  mozilla::AutoVirtualProtect protection(
    216      exportDirectory, sizeof(*exportDirectory), PAGE_READWRITE);
    217  if (!protection) {
    218    gChildProcessStatus = kTestFail;
    219    return;
    220  }
    221 
    222  ExportDirectoryPatcher patcher(imageBase);
    223  if (!patcher) {
    224    return;
    225  }
    226 
    227  patcher.PopulateDirectory(*exportDirectory);
    228 
    229  if (GetProcAddress(imageBase, "Export1") ||
    230      GetProcAddress(imageBase, "Export2") ||
    231      !EXPORT_FUNCTION_EQ(kSecretFunction, SecretFunction1) ||
    232      !EXPORT_FUNCTION_EQ(kSecretFunctionWithSuffix, SecretFunction2)) {
    233    gChildProcessStatus = kTestFail;
    234  }
    235 }
    236 
    237 // This hides all export functions by setting the table size to 0.
    238 void HideExportSection() {
    239  HMODULE imageBase = ::GetModuleHandleW(nullptr);
    240  PEHeaders ourExe(imageBase);
    241 
    242  auto sectionTable =
    243      ourExe.GetImageDirectoryEntryPtr(IMAGE_DIRECTORY_ENTRY_EXPORT);
    244 
    245  mozilla::AutoVirtualProtect protection(sectionTable, sizeof(*sectionTable),
    246                                         PAGE_READWRITE);
    247  if (!protection) {
    248    gChildProcessStatus = kTestFail;
    249    return;
    250  }
    251 
    252  sectionTable->VirtualAddress = sectionTable->Size = 0;
    253 
    254  if (GetProcAddress(imageBase, "Export1") ||
    255      GetProcAddress(imageBase, "Export2")) {
    256    gChildProcessStatus = kTestFail;
    257  }
    258 }
    259 
    260 // This makes the export directory entry point to a new export section
    261 // which exports SecretFunction1 and SecretFunction2.
    262 void ModifyExportDirectoryEntry() {
    263  HMODULE imageBase = ::GetModuleHandleW(nullptr);
    264  PEHeaders ourExe(imageBase);
    265 
    266  auto sectionTable =
    267      ourExe.GetImageDirectoryEntryPtr(IMAGE_DIRECTORY_ENTRY_EXPORT);
    268 
    269  mozilla::AutoVirtualProtect protection(sectionTable, sizeof(*sectionTable),
    270                                         PAGE_READWRITE);
    271  if (!protection) {
    272    gChildProcessStatus = kTestFail;
    273    return;
    274  }
    275 
    276  ExportDirectoryPatcher patcher(imageBase);
    277  if (!patcher) {
    278    return;
    279  }
    280 
    281  patcher.PopulateDirectoryEntry(*sectionTable);
    282 
    283  if (GetProcAddress(imageBase, "Export1") ||
    284      GetProcAddress(imageBase, "Export2") ||
    285      !EXPORT_FUNCTION_EQ(kSecretFunction, SecretFunction1) ||
    286      !EXPORT_FUNCTION_EQ(kSecretFunctionWithSuffix, SecretFunction2)) {
    287    gChildProcessStatus = kTestFail;
    288  }
    289 }
    290 
    291 // This exports functions only by Ordinal by hiding the export name table.
    292 void ExportByOrdinal() {
    293  ModifyExportDirectoryEntry();
    294  if (gChildProcessStatus != kTestSuccess) {
    295    return;
    296  }
    297 
    298  MMPolicyInProcess policy;
    299  HMODULE imageBase = ::GetModuleHandleW(nullptr);
    300  auto ourExe = LocalPEExportSection::Get(imageBase, policy);
    301 
    302  auto exportDirectory = ourExe.GetExportDirectory();
    303  if (!exportDirectory) {
    304    gChildProcessStatus = kTestFail;
    305    return;
    306  }
    307 
    308  exportDirectory->NumberOfNames = 0;
    309 
    310  if (GetProcAddress(imageBase, "Export1") ||
    311      GetProcAddress(imageBase, "Export2") ||
    312      GetProcAddress(imageBase, kSecretFunction) ||
    313      GetProcAddress(imageBase, kSecretFunctionWithSuffix) ||
    314      !EXPORT_FUNCTION_EQ(MAKEINTRESOURCE(1), SecretFunction1) ||
    315      !EXPORT_FUNCTION_EQ(MAKEINTRESOURCE(2), SecretFunction2)) {
    316    gChildProcessStatus = kTestFail;
    317  }
    318 }
    319 
    320 class ChildProcess final {
    321  nsAutoHandle mChildProcess;
    322  nsAutoHandle mChildMainThread;
    323 
    324 public:
    325  static int Main(const nsAutoHandle& aEvent, const wchar_t* aOption) {
    326    if (wcscmp(aOption, kNoModification) == 0) {
    327      ;
    328    } else if (wcscmp(aOption, kNoExport) == 0) {
    329      HideExportSection();
    330    } else if (wcscmp(aOption, kModifyTableEntry) == 0) {
    331      ModifyExportAddressTableEntry();
    332    } else if (wcscmp(aOption, kModifyTable) == 0) {
    333      ModifyExportAddressTable();
    334    } else if (wcscmp(aOption, kModifyDirectoryEntry) == 0) {
    335      ModifyExportDirectoryEntry();
    336    } else if (wcscmp(aOption, kExportByOrdinal) == 0) {
    337      ExportByOrdinal();
    338    }
    339 
    340    // Letting the parent process know the child process is ready.
    341    ::SetEvent(aEvent);
    342 
    343    // The child process does not exit itself.  It's force terminated by
    344    // the parent process when all tests are done.
    345    for (;;) {
    346      ::Sleep(100);
    347    }
    348  }
    349 
    350  ChildProcess(const wchar_t* aExecutable, const wchar_t* aOption,
    351               const nsAutoHandle& aEvent, const nsAutoHandle& aJob) {
    352    const wchar_t* childArgv[] = {aExecutable, aOption};
    353    auto cmdLine(mozilla::MakeCommandLine(std::size(childArgv), childArgv));
    354 
    355    STARTUPINFOW si = {sizeof(si)};
    356    PROCESS_INFORMATION pi;
    357    BOOL ok = ::CreateProcessW(aExecutable, cmdLine.get(), nullptr, nullptr,
    358                               FALSE, 0, nullptr, nullptr, &si, &pi);
    359    if (!ok) {
    360      printf(
    361          "TEST-FAILED | TestPEExportSection | "
    362          "CreateProcessW falied - %08lx.\n",
    363          GetLastError());
    364      return;
    365    }
    366 
    367    if (aJob && !::AssignProcessToJobObject(aJob, pi.hProcess)) {
    368      printf(
    369          "TEST-FAILED | TestPEExportSection | "
    370          "AssignProcessToJobObject falied - %08lx.\n",
    371          GetLastError());
    372      ::TerminateProcess(pi.hProcess, 1);
    373      return;
    374    }
    375 
    376    // Wait until requested modification is done in the child process.
    377    if (::WaitForSingleObject(aEvent, kEventTimeoutinMs) != WAIT_OBJECT_0) {
    378      printf(
    379          "TEST-FAILED | TestPEExportSection | "
    380          "Child process was not ready in time.\n");
    381      return;
    382    }
    383 
    384    mChildProcess.own(pi.hProcess);
    385    mChildMainThread.own(pi.hThread);
    386  }
    387 
    388  ~ChildProcess() { ::TerminateProcess(mChildProcess, 0); }
    389 
    390  operator HANDLE() const { return mChildProcess; }
    391 
    392  TestResult GetStatus() const {
    393    TestResult status = kTestSuccess;
    394    if (!::ReadProcessMemory(mChildProcess, &gChildProcessStatus, &status,
    395                             sizeof(status), nullptr)) {
    396      status = kTestFail;
    397      printf(
    398          "TEST-FAILED | TestPEExportSection | "
    399          "ReadProcessMemory failed - %08lx\n",
    400          GetLastError());
    401    }
    402    return status;
    403  }
    404 };
    405 
    406 template <typename MMPolicy>
    407 TestResult BasicTest(const MMPolicy& aMMPolicy) {
    408  const bool isAppHelpLoaded = ::GetModuleHandleW(L"apphelp.dll");
    409 
    410  // Use ntdll.dll because it does not have any forwarder RVA.
    411  HMODULE ntdllImageBase = ::GetModuleHandleW(L"ntdll.dll");
    412  auto ntdllExports = PEExportSection<MMPolicy>::Get(ntdllImageBase, aMMPolicy);
    413 
    414  auto exportDir = ntdllExports.GetExportDirectory();
    415  auto tableOfNames =
    416      ntdllExports.template RVAToPtr<const PDWORD>(exportDir->AddressOfNames);
    417  for (DWORD i = 0; i < exportDir->NumberOfNames; ++i) {
    418    const auto name =
    419        ntdllExports.template RVAToPtr<const char*>(tableOfNames[i]);
    420 
    421    if (isAppHelpLoaded && strcmp(name, "NtdllDefWindowProc_W") == 0) {
    422      // In this case, GetProcAddress will return
    423      // apphelp!DWM8AND16BitHook_DefWindowProcW.
    424      continue;
    425    }
    426 
    427    auto funcEntry = ntdllExports.FindExportAddressTableEntry(name);
    428    if (ntdllExports.template RVAToPtr<const void*>(*funcEntry) !=
    429        ::GetProcAddress(ntdllImageBase, name)) {
    430      printf(
    431          "TEST-FAILED | TestPEExportSection | "
    432          "FindExportAddressTableEntry did not resolve ntdll!%s.\n",
    433          name);
    434      return kTestFail;
    435    }
    436  }
    437 
    438  for (DWORD i = 0; i < 0x10000; i += 0x10) {
    439    if (ntdllExports.GetProcAddress(MAKEINTRESOURCE(i)) !=
    440        ::GetProcAddress(ntdllImageBase, MAKEINTRESOURCE(i))) {
    441      printf(
    442          "TEST-FAILED | TestPEExportSection | "
    443          "GetProcAddress did not resolve ntdll!Ordinal#%lu.\n",
    444          i);
    445      return kTestFail;
    446    }
    447  }
    448 
    449  // Test a known forwarder RVA.
    450  auto k32Exports = PEExportSection<MMPolicy>::Get(
    451      ::GetModuleHandleW(L"kernel32.dll"), aMMPolicy);
    452  if (k32Exports.FindExportAddressTableEntry("HeapAlloc")) {
    453    printf(
    454        "TEST-FAILED | TestPEExportSection | "
    455        "kernel32!HeapAlloc should be forwarded to ntdll!RtlAllocateHeap.\n");
    456    return kTestFail;
    457  }
    458 
    459  // Test invalid names.
    460  if (k32Exports.FindExportAddressTableEntry("Invalid name") ||
    461      k32Exports.FindExportAddressTableEntry("")) {
    462    printf(
    463        "TEST-FAILED | TestPEExportSection | "
    464        "FindExportAddressTableEntry should return "
    465        "nullptr for a non-existent name.\n");
    466    return kTestFail;
    467  }
    468 
    469  return kTestSuccess;
    470 }
    471 
    472 TestResult RunChildProcessTest(
    473    const wchar_t* aExecutable, const wchar_t* aOption,
    474    const nsAutoHandle& aEvent, const nsAutoHandle& aJob,
    475    TestResult (*aTestCallback)(const RemotePEExportSection&)) {
    476  ChildProcess childProcess(aExecutable, aOption, aEvent, aJob);
    477  if (!childProcess) {
    478    return kTestFail;
    479  }
    480 
    481  auto result = childProcess.GetStatus();
    482  if (result != kTestSuccess) {
    483    return result;
    484  }
    485 
    486  MMPolicyOutOfProcess policy(childProcess);
    487 
    488  // One time is enough to run BasicTest in the child process.
    489  static TestResult oneTimeResult = BasicTest<MMPolicyOutOfProcess>(policy);
    490  if (oneTimeResult != kTestSuccess) {
    491    return oneTimeResult;
    492  }
    493 
    494  auto exportTableChild =
    495      RemotePEExportSection::Get(::GetModuleHandleW(nullptr), policy);
    496  return aTestCallback(exportTableChild);
    497 }
    498 
    499 mozilla::LauncherResult<nsReturnRef<HANDLE>> CreateJobToLimitProcessLifetime() {
    500  uint64_t version;
    501  PEHeaders ntdllHeaders(::GetModuleHandleW(L"ntdll.dll"));
    502  if (!ntdllHeaders.GetVersionInfo(version)) {
    503    printf(
    504        "TEST-FAILED | TestPEExportSection | "
    505        "Unable to obtain version information from ntdll.dll\n");
    506    return LAUNCHER_ERROR_FROM_LAST();
    507  }
    508 
    509  constexpr uint64_t kWin8 = 0x60002ull << 32;
    510  nsAutoHandle job;
    511 
    512  if (version < kWin8) {
    513    // Since a process can be associated only with a single job in Win7 or
    514    // older and this test program is already assigned with a job by
    515    // infrastructure, we cannot use a job.
    516    return job.out();
    517  }
    518 
    519  job.own(::CreateJobObject(nullptr, nullptr));
    520  if (!job) {
    521    printf(
    522        "TEST-FAILED | TestPEExportSection | "
    523        "CreateJobObject falied - %08lx.\n",
    524        GetLastError());
    525    return LAUNCHER_ERROR_FROM_LAST();
    526  }
    527 
    528  JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobInfo = {};
    529  jobInfo.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
    530 
    531  if (!::SetInformationJobObject(job, JobObjectExtendedLimitInformation,
    532                                 &jobInfo, sizeof(jobInfo))) {
    533    printf(
    534        "TEST-FAILED | TestPEExportSection | "
    535        "SetInformationJobObject falied - %08lx.\n",
    536        GetLastError());
    537    return LAUNCHER_ERROR_FROM_LAST();
    538  }
    539 
    540  return job.out();
    541 }
    542 
    543 extern "C" int wmain(int argc, wchar_t* argv[]) {
    544  nsAutoHandle controlEvent(
    545      ::CreateEventW(nullptr, FALSE, FALSE, kProcessControlEventName));
    546 
    547  if (argc == 2) {
    548    return ChildProcess::Main(controlEvent, argv[1]);
    549  }
    550 
    551  if (argc != 1) {
    552    printf(
    553        "TEST-FAILED | TestPEExportSection | "
    554        "Invalid arguments.\n");
    555    return kTestFail;
    556  }
    557 
    558  MMPolicyInProcess policy;
    559  if (BasicTest<MMPolicyInProcess>(policy)) {
    560    return kTestFail;
    561  }
    562 
    563  auto exportTableSelf =
    564      LocalPEExportSection::Get(::GetModuleHandleW(nullptr), policy);
    565  if (!exportTableSelf) {
    566    printf(
    567        "TEST-FAILED | TestPEExportSection | "
    568        "LocalPEExportSection::Get failed.\n");
    569    return kTestFail;
    570  }
    571 
    572  VERIFY_EXPORT_FUNCTION(exportTableSelf, "Export1", Export1,
    573                         "Local | Export1 was not exported.\n");
    574  VERIFY_EXPORT_FUNCTION(exportTableSelf, "Export2", Export2,
    575                         "Local | Export2 was not exported.\n");
    576  VERIFY_EXPORT_FUNCTION(
    577      exportTableSelf, "Invalid name", 0,
    578      "Local | GetProcAddress should return nullptr for an invalid name.\n");
    579 
    580  // We'll add the child process to a job so that, in the event of a failure in
    581  // this parent process, the child process will be automatically terminated.
    582  auto probablyJob = CreateJobToLimitProcessLifetime();
    583  if (probablyJob.isErr()) {
    584    return kTestFail;
    585  }
    586 
    587  nsAutoHandle job(probablyJob.unwrap());
    588 
    589  auto result = RunChildProcessTest(
    590      argv[0], kNoModification, controlEvent, job,
    591      [](const RemotePEExportSection& aTables) {
    592        VERIFY_EXPORT_FUNCTION(aTables, "Export1", Export1,
    593                               "NoModification | Export1 was not exported.\n");
    594        VERIFY_EXPORT_FUNCTION(aTables, "Export2", Export2,
    595                               "NoModification | Export2 was not exported.\n");
    596        return kTestSuccess;
    597      });
    598  if (result == kTestFail) {
    599    return result;
    600  }
    601 
    602  result = RunChildProcessTest(
    603      argv[0], kNoExport, controlEvent, job,
    604      [](const RemotePEExportSection& aTables) {
    605        VERIFY_EXPORT_FUNCTION(aTables, "Export1", 0,
    606                               "NoExport | Export1 was exported.\n");
    607        VERIFY_EXPORT_FUNCTION(aTables, "Export2", 0,
    608                               "NoExport | Export2 was exported.\n");
    609        return kTestSuccess;
    610      });
    611  if (result == kTestFail) {
    612    return result;
    613  }
    614 
    615  result = RunChildProcessTest(
    616      argv[0], kModifyTableEntry, controlEvent, job,
    617      [](const RemotePEExportSection& aTables) {
    618        VERIFY_EXPORT_FUNCTION(
    619            aTables, "Export1", SecretFunction1,
    620            "ModifyTableEntry | SecretFunction1 was not exported.\n");
    621        VERIFY_EXPORT_FUNCTION(
    622            aTables, "Export2", Export2,
    623            "ModifyTableEntry | Export2 was not exported.\n");
    624        return kTestSuccess;
    625      });
    626  if (result == kTestFail) {
    627    return result;
    628  }
    629 
    630  result = RunChildProcessTest(
    631      argv[0], kModifyTable, controlEvent, job,
    632      [](const RemotePEExportSection& aTables) {
    633        VERIFY_EXPORT_FUNCTION(aTables, "Export1", 0,
    634                               "ModifyTable | Export1 was exported.\n");
    635        VERIFY_EXPORT_FUNCTION(aTables, "Export2", 0,
    636                               "ModifyTable | Export2 was exported.\n");
    637        VERIFY_EXPORT_FUNCTION(
    638            aTables, kSecretFunction, SecretFunction1,
    639            "ModifyTable | SecretFunction1 was not exported.\n");
    640        VERIFY_EXPORT_FUNCTION(
    641            aTables, kSecretFunctionWithSuffix, SecretFunction2,
    642            "ModifyTable | SecretFunction2 was not exported.\n");
    643        VERIFY_EXPORT_FUNCTION(
    644            aTables, kSecretFunctionInvalid, 0,
    645            "ModifyTable | kSecretFunctionInvalid was exported.\n");
    646        return kTestSuccess;
    647      });
    648  if (result == kTestFail) {
    649    return result;
    650  }
    651 
    652  result = RunChildProcessTest(
    653      argv[0], kModifyDirectoryEntry, controlEvent, job,
    654      [](const RemotePEExportSection& aTables) {
    655        VERIFY_EXPORT_FUNCTION(
    656            aTables, "Export1", 0,
    657            "ModifyDirectoryEntry | Export1 was exported.\n");
    658        VERIFY_EXPORT_FUNCTION(
    659            aTables, "Export2", 0,
    660            "ModifyDirectoryEntry | Export2 was exported.\n");
    661        VERIFY_EXPORT_FUNCTION(
    662            aTables, kSecretFunction, SecretFunction1,
    663            "ModifyDirectoryEntry | SecretFunction1 was not exported.\n");
    664        VERIFY_EXPORT_FUNCTION(
    665            aTables, kSecretFunctionWithSuffix, SecretFunction2,
    666            "ModifyDirectoryEntry | SecretFunction2 was not exported.\n");
    667        VERIFY_EXPORT_FUNCTION(
    668            aTables, kSecretFunctionInvalid, 0,
    669            "ModifyDirectoryEntry | kSecretFunctionInvalid was exported.\n");
    670        return kTestSuccess;
    671      });
    672  if (result == kTestFail) {
    673    return result;
    674  }
    675 
    676  result = RunChildProcessTest(
    677      argv[0], kExportByOrdinal, controlEvent, job,
    678      [](const RemotePEExportSection& aTables) {
    679        VERIFY_EXPORT_FUNCTION(aTables, "Export1", 0,
    680                               "ExportByOrdinal | Export1 was exported.\n");
    681        VERIFY_EXPORT_FUNCTION(aTables, "Export2", 0,
    682                               "ExportByOrdinal | Export2 was exported.\n");
    683        VERIFY_EXPORT_FUNCTION(
    684            aTables, kSecretFunction, 0,
    685            "ModifyDirectoryEntry | kSecretFunction was exported by name.\n");
    686        VERIFY_EXPORT_FUNCTION(
    687            aTables, kSecretFunctionWithSuffix, 0,
    688            "ModifyDirectoryEntry | "
    689            "kSecretFunctionWithSuffix was exported by name.\n");
    690        VERIFY_EXPORT_FUNCTION(
    691            aTables, MAKEINTRESOURCE(1), SecretFunction1,
    692            "ModifyDirectoryEntry | "
    693            "kSecretFunction was not exported by ordinal.\n");
    694        VERIFY_EXPORT_FUNCTION(
    695            aTables, MAKEINTRESOURCE(2), SecretFunction2,
    696            "ModifyDirectoryEntry | "
    697            "kSecretFunctionWithSuffix was not exported by ordinal.\n");
    698        return kTestSuccess;
    699      });
    700  if (result == kTestFail) {
    701    return result;
    702  }
    703 
    704  return kTestSuccess;
    705 }