diff options
Diffstat (limited to 'mozglue/tests/TestPEExportSection.cpp')
-rw-r--r-- | mozglue/tests/TestPEExportSection.cpp | 706 |
1 files changed, 706 insertions, 0 deletions
diff --git a/mozglue/tests/TestPEExportSection.cpp b/mozglue/tests/TestPEExportSection.cpp new file mode 100644 index 0000000000..cf160368d3 --- /dev/null +++ b/mozglue/tests/TestPEExportSection.cpp @@ -0,0 +1,706 @@ +/* -*- 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 + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +// This test makes sure mozilla::nt::PEExportSection can parse the export +// section of a local process, and a remote process even though it's +// modified by an external code. + +#include "mozilla/CmdLineAndEnvUtils.h" +#include "mozilla/NativeNt.h" +#include "nsWindowsDllInterceptor.h" + +#include <stdio.h> +#include <windows.h> + +#define EXPORT_FUNCTION_EQ(name, func) \ + (GetProcAddress(imageBase, name) == reinterpret_cast<void*>(func)) + +#define VERIFY_EXPORT_FUNCTION(tables, name, expected, errorMessage) \ + do { \ + if (tables.GetProcAddress(name) != reinterpret_cast<void*>(expected)) { \ + printf("TEST-FAILED | TestPEExportSection | %s", errorMessage); \ + return kTestFail; \ + } \ + } while (0) + +using namespace mozilla::nt; +using mozilla::interceptor::MMPolicyInProcess; +using mozilla::interceptor::MMPolicyOutOfProcess; +using LocalPEExportSection = PEExportSection<MMPolicyInProcess>; +using RemotePEExportSection = PEExportSection<MMPolicyOutOfProcess>; + +constexpr DWORD kEventTimeoutinMs = 5000; +const wchar_t kProcessControlEventName[] = + L"TestPEExportSection.Process.Control.Event"; + +enum TestResult : int { + kTestSuccess = 0, + kTestFail, + kTestSkip, +}; + +// These strings start with the same keyword to make sure we don't do substring +// match. Moreover, kSecretFunctionInvalid is purposely longer than the +// combination of the other two strings and located in between the other two +// strings to effectively test binary search. +const char kSecretFunction[] = "Secret"; +const char kSecretFunctionInvalid[] = "Secret invalid long name"; +const char kSecretFunctionWithSuffix[] = "Secret2"; + +const wchar_t* kNoModification = L"--NoModification"; +const wchar_t* kNoExport = L"--NoExport"; +const wchar_t* kModifyTableEntry = L"--ModifyTableEntry"; +const wchar_t* kModifyTable = L"--ModifyTable"; +const wchar_t* kModifyDirectoryEntry = L"--ModifyDirectoryEntry"; +const wchar_t* kExportByOrdinal = L"--ExportByOrdinal"; + +// Use the global variable to pass the child process's error status to the +// parent process. We don't use a process's exit code to keep the test simple. +int gChildProcessStatus = 0; + +// These functions are exported by linker or export section tampering at +// runtime. Each of function bodies needs to be different to avoid ICF. +extern "C" __declspec(dllexport) int Export1() { return 0; } +extern "C" __declspec(dllexport) int Export2() { return 1; } +int SecretFunction1() { return 100; } +int SecretFunction2() { return 101; } + +// This class allocates a writable region downstream of the mapped image +// and prepares it as a valid export section. +class ExportDirectoryPatcher final { + static constexpr int kRegionAllocationTryLimit = 100; + static constexpr int kNumOfTableEntries = 2; + // VirtualAlloc sometimes fails if a desired base address is too small. + // Define a minimum desired base to reduce the number of allocation tries. + static constexpr uintptr_t kMinimumAllocationPoint = 0x8000000; + + struct ExportDirectory { + IMAGE_EXPORT_DIRECTORY mDirectoryHeader; + DWORD mExportAddressTable[kNumOfTableEntries]; + DWORD mExportNameTable[kNumOfTableEntries]; + WORD mExportOrdinalTable[kNumOfTableEntries]; + char mNameBuffer1[sizeof(kSecretFunction)]; + char mNameBuffer2[sizeof(kSecretFunctionWithSuffix)]; + + template <typename T> + static DWORD PtrToRVA(T aPtr, uintptr_t aBase) { + return reinterpret_cast<uintptr_t>(aPtr) - aBase; + } + + explicit ExportDirectory(uintptr_t aImageBase) : mDirectoryHeader{} { + mDirectoryHeader.Base = 1; + mExportAddressTable[0] = PtrToRVA(SecretFunction1, aImageBase); + mExportAddressTable[1] = PtrToRVA(SecretFunction2, aImageBase); + mExportNameTable[0] = PtrToRVA(mNameBuffer1, aImageBase); + mExportNameTable[1] = PtrToRVA(mNameBuffer2, aImageBase); + mExportOrdinalTable[0] = 0; + mExportOrdinalTable[1] = 1; + strcpy(mNameBuffer1, kSecretFunction); + strcpy(mNameBuffer2, kSecretFunctionWithSuffix); + } + }; + + uintptr_t mImageBase; + ExportDirectory* mNewExportDirectory; + + DWORD PtrToRVA(const void* aPtr) const { + return reinterpret_cast<uintptr_t>(aPtr) - mImageBase; + } + + public: + explicit ExportDirectoryPatcher(HMODULE aModule) + : mImageBase(PEHeaders::HModuleToBaseAddr<uintptr_t>(aModule)), + mNewExportDirectory(nullptr) { + SYSTEM_INFO si = {}; + ::GetSystemInfo(&si); + + int numPagesRequired = ((sizeof(ExportDirectory) - 1) / si.dwPageSize) + 1; + + uintptr_t desiredBase = mImageBase + si.dwAllocationGranularity; + desiredBase = std::max(desiredBase, kMinimumAllocationPoint); + + for (int i = 0; i < kRegionAllocationTryLimit; ++i) { + void* allocated = + ::VirtualAlloc(reinterpret_cast<void*>(desiredBase), + numPagesRequired * si.dwPageSize, + MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + if (allocated) { + // Use the end of a allocated page as ExportDirectory in order to test + // the boundary between a commit page and a non-commited page. + allocated = reinterpret_cast<uint8_t*>(allocated) + + (numPagesRequired * si.dwPageSize) - + sizeof(ExportDirectory); + mNewExportDirectory = new (allocated) ExportDirectory(mImageBase); + return; + } + + desiredBase += si.dwAllocationGranularity; + } + + gChildProcessStatus = kTestSkip; + printf( + "TEST-SKIP | TestPEExportSection | " + "Giving up finding an allocatable space following the mapped image.\n"); + } + + ~ExportDirectoryPatcher() { + // Intentionally leave mNewExportDirectory leaked to keep a patched data + // available until the process is terminated. + } + + explicit operator bool() const { return !!mNewExportDirectory; } + + void PopulateDirectory(IMAGE_EXPORT_DIRECTORY& aOutput) const { + aOutput.NumberOfFunctions = aOutput.NumberOfNames = kNumOfTableEntries; + aOutput.AddressOfFunctions = + PtrToRVA(mNewExportDirectory->mExportAddressTable); + aOutput.AddressOfNames = PtrToRVA(mNewExportDirectory->mExportNameTable); + aOutput.AddressOfNameOrdinals = + PtrToRVA(mNewExportDirectory->mExportOrdinalTable); + } + + void PopulateDirectoryEntry(IMAGE_DATA_DIRECTORY& aOutput) const { + PopulateDirectory(mNewExportDirectory->mDirectoryHeader); + aOutput.VirtualAddress = PtrToRVA(&mNewExportDirectory->mDirectoryHeader); + aOutput.Size = sizeof(ExportDirectory); + } +}; + +// This exports SecretFunction1 as "Export1" by replacing an entry of the +// export address table. +void ModifyExportAddressTableEntry() { + MMPolicyInProcess policy; + HMODULE imageBase = ::GetModuleHandleW(nullptr); + auto ourExe = LocalPEExportSection::Get(imageBase, policy); + + auto addressTableEntry = + const_cast<DWORD*>(ourExe.FindExportAddressTableEntry("Export1")); + if (!addressTableEntry) { + gChildProcessStatus = kTestFail; + return; + } + + mozilla::AutoVirtualProtect protection( + addressTableEntry, sizeof(*addressTableEntry), PAGE_READWRITE); + if (!protection) { + gChildProcessStatus = kTestFail; + return; + } + + *addressTableEntry = reinterpret_cast<uintptr_t>(SecretFunction1) - + PEHeaders::HModuleToBaseAddr<uintptr_t>(imageBase); + + if (!EXPORT_FUNCTION_EQ("Export1", SecretFunction1) || + !EXPORT_FUNCTION_EQ("Export2", Export2)) { + gChildProcessStatus = kTestFail; + } +} + +// This switches the entire address table into one exporting SecretFunction1 +// and SecretFunction2. +void ModifyExportAddressTable() { + MMPolicyInProcess policy; + HMODULE imageBase = ::GetModuleHandleW(nullptr); + auto ourExe = LocalPEExportSection::Get(imageBase, policy); + + auto exportDirectory = ourExe.GetExportDirectory(); + if (!exportDirectory) { + gChildProcessStatus = kTestFail; + return; + } + + mozilla::AutoVirtualProtect protection( + exportDirectory, sizeof(*exportDirectory), PAGE_READWRITE); + if (!protection) { + gChildProcessStatus = kTestFail; + return; + } + + ExportDirectoryPatcher patcher(imageBase); + if (!patcher) { + return; + } + + patcher.PopulateDirectory(*exportDirectory); + + if (GetProcAddress(imageBase, "Export1") || + GetProcAddress(imageBase, "Export2") || + !EXPORT_FUNCTION_EQ(kSecretFunction, SecretFunction1) || + !EXPORT_FUNCTION_EQ(kSecretFunctionWithSuffix, SecretFunction2)) { + gChildProcessStatus = kTestFail; + } +} + +// This hides all export functions by setting the table size to 0. +void HideExportSection() { + HMODULE imageBase = ::GetModuleHandleW(nullptr); + PEHeaders ourExe(imageBase); + + auto sectionTable = + ourExe.GetImageDirectoryEntryPtr(IMAGE_DIRECTORY_ENTRY_EXPORT); + + mozilla::AutoVirtualProtect protection(sectionTable, sizeof(*sectionTable), + PAGE_READWRITE); + if (!protection) { + gChildProcessStatus = kTestFail; + return; + } + + sectionTable->VirtualAddress = sectionTable->Size = 0; + + if (GetProcAddress(imageBase, "Export1") || + GetProcAddress(imageBase, "Export2")) { + gChildProcessStatus = kTestFail; + } +} + +// This makes the export directory entry point to a new export section +// which exports SecretFunction1 and SecretFunction2. +void ModifyExportDirectoryEntry() { + HMODULE imageBase = ::GetModuleHandleW(nullptr); + PEHeaders ourExe(imageBase); + + auto sectionTable = + ourExe.GetImageDirectoryEntryPtr(IMAGE_DIRECTORY_ENTRY_EXPORT); + + mozilla::AutoVirtualProtect protection(sectionTable, sizeof(*sectionTable), + PAGE_READWRITE); + if (!protection) { + gChildProcessStatus = kTestFail; + return; + } + + ExportDirectoryPatcher patcher(imageBase); + if (!patcher) { + return; + } + + patcher.PopulateDirectoryEntry(*sectionTable); + + if (GetProcAddress(imageBase, "Export1") || + GetProcAddress(imageBase, "Export2") || + !EXPORT_FUNCTION_EQ(kSecretFunction, SecretFunction1) || + !EXPORT_FUNCTION_EQ(kSecretFunctionWithSuffix, SecretFunction2)) { + gChildProcessStatus = kTestFail; + } +} + +// This exports functions only by Ordinal by hiding the export name table. +void ExportByOrdinal() { + ModifyExportDirectoryEntry(); + if (gChildProcessStatus != kTestSuccess) { + return; + } + + MMPolicyInProcess policy; + HMODULE imageBase = ::GetModuleHandleW(nullptr); + auto ourExe = LocalPEExportSection::Get(imageBase, policy); + + auto exportDirectory = ourExe.GetExportDirectory(); + if (!exportDirectory) { + gChildProcessStatus = kTestFail; + return; + } + + exportDirectory->NumberOfNames = 0; + + if (GetProcAddress(imageBase, "Export1") || + GetProcAddress(imageBase, "Export2") || + GetProcAddress(imageBase, kSecretFunction) || + GetProcAddress(imageBase, kSecretFunctionWithSuffix) || + !EXPORT_FUNCTION_EQ(MAKEINTRESOURCE(1), SecretFunction1) || + !EXPORT_FUNCTION_EQ(MAKEINTRESOURCE(2), SecretFunction2)) { + gChildProcessStatus = kTestFail; + } +} + +class ChildProcess final { + nsAutoHandle mChildProcess; + nsAutoHandle mChildMainThread; + + public: + static int Main(const nsAutoHandle& aEvent, const wchar_t* aOption) { + if (wcscmp(aOption, kNoModification) == 0) { + ; + } else if (wcscmp(aOption, kNoExport) == 0) { + HideExportSection(); + } else if (wcscmp(aOption, kModifyTableEntry) == 0) { + ModifyExportAddressTableEntry(); + } else if (wcscmp(aOption, kModifyTable) == 0) { + ModifyExportAddressTable(); + } else if (wcscmp(aOption, kModifyDirectoryEntry) == 0) { + ModifyExportDirectoryEntry(); + } else if (wcscmp(aOption, kExportByOrdinal) == 0) { + ExportByOrdinal(); + } + + // Letting the parent process know the child process is ready. + ::SetEvent(aEvent); + + // The child process does not exit itself. It's force terminated by + // the parent process when all tests are done. + for (;;) { + ::Sleep(100); + } + } + + ChildProcess(const wchar_t* aExecutable, const wchar_t* aOption, + const nsAutoHandle& aEvent, const nsAutoHandle& aJob) { + const wchar_t* childArgv[] = {aExecutable, aOption}; + auto cmdLine( + mozilla::MakeCommandLine(mozilla::ArrayLength(childArgv), childArgv)); + + STARTUPINFOW si = {sizeof(si)}; + PROCESS_INFORMATION pi; + BOOL ok = ::CreateProcessW(aExecutable, cmdLine.get(), nullptr, nullptr, + FALSE, 0, nullptr, nullptr, &si, &pi); + if (!ok) { + printf( + "TEST-FAILED | TestPEExportSection | " + "CreateProcessW falied - %08lx.\n", + GetLastError()); + return; + } + + if (aJob && !::AssignProcessToJobObject(aJob, pi.hProcess)) { + printf( + "TEST-FAILED | TestPEExportSection | " + "AssignProcessToJobObject falied - %08lx.\n", + GetLastError()); + ::TerminateProcess(pi.hProcess, 1); + return; + } + + // Wait until requested modification is done in the child process. + if (::WaitForSingleObject(aEvent, kEventTimeoutinMs) != WAIT_OBJECT_0) { + printf( + "TEST-FAILED | TestPEExportSection | " + "Child process was not ready in time.\n"); + return; + } + + mChildProcess.own(pi.hProcess); + mChildMainThread.own(pi.hThread); + } + + ~ChildProcess() { ::TerminateProcess(mChildProcess, 0); } + + operator HANDLE() const { return mChildProcess; } + + TestResult GetStatus() const { + TestResult status = kTestSuccess; + if (!::ReadProcessMemory(mChildProcess, &gChildProcessStatus, &status, + sizeof(status), nullptr)) { + status = kTestFail; + printf( + "TEST-FAILED | TestPEExportSection | " + "ReadProcessMemory failed - %08lx\n", + GetLastError()); + } + return status; + } +}; + +template <typename MMPolicy> +TestResult BasicTest(const MMPolicy& aMMPolicy) { + const bool isAppHelpLoaded = ::GetModuleHandleW(L"apphelp.dll"); + + // Use ntdll.dll because it does not have any forwarder RVA. + HMODULE ntdllImageBase = ::GetModuleHandleW(L"ntdll.dll"); + auto ntdllExports = PEExportSection<MMPolicy>::Get(ntdllImageBase, aMMPolicy); + + auto exportDir = ntdllExports.GetExportDirectory(); + auto tableOfNames = + ntdllExports.template RVAToPtr<const PDWORD>(exportDir->AddressOfNames); + for (DWORD i = 0; i < exportDir->NumberOfNames; ++i) { + const auto name = + ntdllExports.template RVAToPtr<const char*>(tableOfNames[i]); + + if (isAppHelpLoaded && strcmp(name, "NtdllDefWindowProc_W") == 0) { + // In this case, GetProcAddress will return + // apphelp!DWM8AND16BitHook_DefWindowProcW. + continue; + } + + auto funcEntry = ntdllExports.FindExportAddressTableEntry(name); + if (ntdllExports.template RVAToPtr<const void*>(*funcEntry) != + ::GetProcAddress(ntdllImageBase, name)) { + printf( + "TEST-FAILED | TestPEExportSection | " + "FindExportAddressTableEntry did not resolve ntdll!%s.\n", + name); + return kTestFail; + } + } + + for (DWORD i = 0; i < 0x10000; i += 0x10) { + if (ntdllExports.GetProcAddress(MAKEINTRESOURCE(i)) != + ::GetProcAddress(ntdllImageBase, MAKEINTRESOURCE(i))) { + printf( + "TEST-FAILED | TestPEExportSection | " + "GetProcAddress did not resolve ntdll!Ordinal#%lu.\n", + i); + return kTestFail; + } + } + + // Test a known forwarder RVA. + auto k32Exports = PEExportSection<MMPolicy>::Get( + ::GetModuleHandleW(L"kernel32.dll"), aMMPolicy); + if (k32Exports.FindExportAddressTableEntry("HeapAlloc")) { + printf( + "TEST-FAILED | TestPEExportSection | " + "kernel32!HeapAlloc should be forwarded to ntdll!RtlAllocateHeap.\n"); + return kTestFail; + } + + // Test invalid names. + if (k32Exports.FindExportAddressTableEntry("Invalid name") || + k32Exports.FindExportAddressTableEntry("")) { + printf( + "TEST-FAILED | TestPEExportSection | " + "FindExportAddressTableEntry should return " + "nullptr for a non-existent name.\n"); + return kTestFail; + } + + return kTestSuccess; +} + +TestResult RunChildProcessTest( + const wchar_t* aExecutable, const wchar_t* aOption, + const nsAutoHandle& aEvent, const nsAutoHandle& aJob, + TestResult (*aTestCallback)(const RemotePEExportSection&)) { + ChildProcess childProcess(aExecutable, aOption, aEvent, aJob); + if (!childProcess) { + return kTestFail; + } + + auto result = childProcess.GetStatus(); + if (result != kTestSuccess) { + return result; + } + + MMPolicyOutOfProcess policy(childProcess); + + // One time is enough to run BasicTest in the child process. + static TestResult oneTimeResult = BasicTest<MMPolicyOutOfProcess>(policy); + if (oneTimeResult != kTestSuccess) { + return oneTimeResult; + } + + auto exportTableChild = + RemotePEExportSection::Get(::GetModuleHandleW(nullptr), policy); + return aTestCallback(exportTableChild); +} + +mozilla::LauncherResult<nsReturnRef<HANDLE>> CreateJobToLimitProcessLifetime() { + uint64_t version; + PEHeaders ntdllHeaders(::GetModuleHandleW(L"ntdll.dll")); + if (!ntdllHeaders.GetVersionInfo(version)) { + printf( + "TEST-FAILED | TestPEExportSection | " + "Unable to obtain version information from ntdll.dll\n"); + return LAUNCHER_ERROR_FROM_LAST(); + } + + constexpr uint64_t kWin8 = 0x60002ull << 32; + nsAutoHandle job; + + if (version < kWin8) { + // Since a process can be associated only with a single job in Win7 or + // older and this test program is already assigned with a job by + // infrastructure, we cannot use a job. + return job.out(); + } + + job.own(::CreateJobObject(nullptr, nullptr)); + if (!job) { + printf( + "TEST-FAILED | TestPEExportSection | " + "CreateJobObject falied - %08lx.\n", + GetLastError()); + return LAUNCHER_ERROR_FROM_LAST(); + } + + JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobInfo = {}; + jobInfo.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; + + if (!::SetInformationJobObject(job, JobObjectExtendedLimitInformation, + &jobInfo, sizeof(jobInfo))) { + printf( + "TEST-FAILED | TestPEExportSection | " + "SetInformationJobObject falied - %08lx.\n", + GetLastError()); + return LAUNCHER_ERROR_FROM_LAST(); + } + + return job.out(); +} + +extern "C" int wmain(int argc, wchar_t* argv[]) { + nsAutoHandle controlEvent( + ::CreateEventW(nullptr, FALSE, FALSE, kProcessControlEventName)); + + if (argc == 2) { + return ChildProcess::Main(controlEvent, argv[1]); + } + + if (argc != 1) { + printf( + "TEST-FAILED | TestPEExportSection | " + "Invalid arguments.\n"); + return kTestFail; + } + + MMPolicyInProcess policy; + if (BasicTest<MMPolicyInProcess>(policy)) { + return kTestFail; + } + + auto exportTableSelf = + LocalPEExportSection::Get(::GetModuleHandleW(nullptr), policy); + if (!exportTableSelf) { + printf( + "TEST-FAILED | TestPEExportSection | " + "LocalPEExportSection::Get failed.\n"); + return kTestFail; + } + + VERIFY_EXPORT_FUNCTION(exportTableSelf, "Export1", Export1, + "Local | Export1 was not exported.\n"); + VERIFY_EXPORT_FUNCTION(exportTableSelf, "Export2", Export2, + "Local | Export2 was not exported.\n"); + VERIFY_EXPORT_FUNCTION( + exportTableSelf, "Invalid name", 0, + "Local | GetProcAddress should return nullptr for an invalid name.\n"); + + // We'll add the child process to a job so that, in the event of a failure in + // this parent process, the child process will be automatically terminated. + auto probablyJob = CreateJobToLimitProcessLifetime(); + if (probablyJob.isErr()) { + return kTestFail; + } + + nsAutoHandle job(probablyJob.unwrap()); + + auto result = RunChildProcessTest( + argv[0], kNoModification, controlEvent, job, + [](const RemotePEExportSection& aTables) { + VERIFY_EXPORT_FUNCTION(aTables, "Export1", Export1, + "NoModification | Export1 was not exported.\n"); + VERIFY_EXPORT_FUNCTION(aTables, "Export2", Export2, + "NoModification | Export2 was not exported.\n"); + return kTestSuccess; + }); + if (result == kTestFail) { + return result; + } + + result = RunChildProcessTest( + argv[0], kNoExport, controlEvent, job, + [](const RemotePEExportSection& aTables) { + VERIFY_EXPORT_FUNCTION(aTables, "Export1", 0, + "NoExport | Export1 was exported.\n"); + VERIFY_EXPORT_FUNCTION(aTables, "Export2", 0, + "NoExport | Export2 was exported.\n"); + return kTestSuccess; + }); + if (result == kTestFail) { + return result; + } + + result = RunChildProcessTest( + argv[0], kModifyTableEntry, controlEvent, job, + [](const RemotePEExportSection& aTables) { + VERIFY_EXPORT_FUNCTION( + aTables, "Export1", SecretFunction1, + "ModifyTableEntry | SecretFunction1 was not exported.\n"); + VERIFY_EXPORT_FUNCTION( + aTables, "Export2", Export2, + "ModifyTableEntry | Export2 was not exported.\n"); + return kTestSuccess; + }); + if (result == kTestFail) { + return result; + } + + result = RunChildProcessTest( + argv[0], kModifyTable, controlEvent, job, + [](const RemotePEExportSection& aTables) { + VERIFY_EXPORT_FUNCTION(aTables, "Export1", 0, + "ModifyTable | Export1 was exported.\n"); + VERIFY_EXPORT_FUNCTION(aTables, "Export2", 0, + "ModifyTable | Export2 was exported.\n"); + VERIFY_EXPORT_FUNCTION( + aTables, kSecretFunction, SecretFunction1, + "ModifyTable | SecretFunction1 was not exported.\n"); + VERIFY_EXPORT_FUNCTION( + aTables, kSecretFunctionWithSuffix, SecretFunction2, + "ModifyTable | SecretFunction2 was not exported.\n"); + VERIFY_EXPORT_FUNCTION( + aTables, kSecretFunctionInvalid, 0, + "ModifyTable | kSecretFunctionInvalid was exported.\n"); + return kTestSuccess; + }); + if (result == kTestFail) { + return result; + } + + result = RunChildProcessTest( + argv[0], kModifyDirectoryEntry, controlEvent, job, + [](const RemotePEExportSection& aTables) { + VERIFY_EXPORT_FUNCTION( + aTables, "Export1", 0, + "ModifyDirectoryEntry | Export1 was exported.\n"); + VERIFY_EXPORT_FUNCTION( + aTables, "Export2", 0, + "ModifyDirectoryEntry | Export2 was exported.\n"); + VERIFY_EXPORT_FUNCTION( + aTables, kSecretFunction, SecretFunction1, + "ModifyDirectoryEntry | SecretFunction1 was not exported.\n"); + VERIFY_EXPORT_FUNCTION( + aTables, kSecretFunctionWithSuffix, SecretFunction2, + "ModifyDirectoryEntry | SecretFunction2 was not exported.\n"); + VERIFY_EXPORT_FUNCTION( + aTables, kSecretFunctionInvalid, 0, + "ModifyDirectoryEntry | kSecretFunctionInvalid was exported.\n"); + return kTestSuccess; + }); + if (result == kTestFail) { + return result; + } + + result = RunChildProcessTest( + argv[0], kExportByOrdinal, controlEvent, job, + [](const RemotePEExportSection& aTables) { + VERIFY_EXPORT_FUNCTION(aTables, "Export1", 0, + "ExportByOrdinal | Export1 was exported.\n"); + VERIFY_EXPORT_FUNCTION(aTables, "Export2", 0, + "ExportByOrdinal | Export2 was exported.\n"); + VERIFY_EXPORT_FUNCTION( + aTables, kSecretFunction, 0, + "ModifyDirectoryEntry | kSecretFunction was exported by name.\n"); + VERIFY_EXPORT_FUNCTION( + aTables, kSecretFunctionWithSuffix, 0, + "ModifyDirectoryEntry | " + "kSecretFunctionWithSuffix was exported by name.\n"); + VERIFY_EXPORT_FUNCTION( + aTables, MAKEINTRESOURCE(1), SecretFunction1, + "ModifyDirectoryEntry | " + "kSecretFunction was not exported by ordinal.\n"); + VERIFY_EXPORT_FUNCTION( + aTables, MAKEINTRESOURCE(2), SecretFunction2, + "ModifyDirectoryEntry | " + "kSecretFunctionWithSuffix was not exported by ordinal.\n"); + return kTestSuccess; + }); + if (result == kTestFail) { + return result; + } + + return kTestSuccess; +} |