tor-browser

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

TestSameBinary.cpp (8428B)


      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 #define MOZ_USE_LAUNCHER_ERROR
      8 
      9 #include <stdio.h>
     10 #include <stdlib.h>
     11 
     12 #include "SameBinary.h"
     13 #include "mozilla/Assertions.h"
     14 #include "mozilla/CmdLineAndEnvUtils.h"
     15 #include "mozilla/NativeNt.h"
     16 #include "mozilla/WinHeaderOnlyUtils.h"
     17 #include "nsWindowsHelpers.h"
     18 
     19 #define EXPECT_SAMEBINARY_IS(expected, option, message)                \
     20  do {                                                                 \
     21    mozilla::LauncherResult<bool> isSame =                             \
     22        mozilla::IsSameBinaryAsParentProcess(option);                  \
     23    if (isSame.isErr()) {                                              \
     24      PrintLauncherError(isSame,                                       \
     25                         "IsSameBinaryAsParentProcess returned error " \
     26                         "when we were expecting success.");           \
     27      return 1;                                                        \
     28    }                                                                  \
     29    if (isSame.unwrap() != expected) {                                 \
     30      PrintErrorMsg(message);                                          \
     31      return 1;                                                        \
     32    }                                                                  \
     33  } while (0)
     34 
     35 /**
     36 * This test involves three processes:
     37 *   1. The "Monitor" process, which is executed by |MonitorMain|. This process
     38 *      is responsible for integrating with the test harness, so it spawns the
     39 *      "Parent" process (2), and then waits for the other two processes to
     40 *      finish.
     41 *   2. The "Parent" process, which is executed by |ParentMain|. This process
     42 *      creates the "Child" process (3) and then waits indefinitely.
     43 *   3. The "Child" process, which is executed by |ChildMain| and carries out
     44 *      the actual test. It terminates the Parent process during its execution,
     45 *      using the Child PID as the Parent process's exit code. This serves as a
     46 *      hacky yet effective way to signal to the Monitor process which PID it
     47 *      should wait on to ensure that the Child process has exited.
     48 */
     49 
     50 static const char kMsgStart[] = "TEST-FAILED | SameBinary | ";
     51 
     52 inline void PrintErrorMsg(const char* aMsg) {
     53  printf("%s%s\n", kMsgStart, aMsg);
     54 }
     55 
     56 inline void PrintWinError(const char* aMsg) {
     57  mozilla::WindowsError err(mozilla::WindowsError::FromLastError());
     58  printf("%s%s: %S\n", kMsgStart, aMsg, err.AsString().get());
     59 }
     60 
     61 template <typename T>
     62 inline void PrintLauncherError(const mozilla::LauncherResult<T>& aResult,
     63                               const char* aMsg = nullptr) {
     64  const char* const kSep = aMsg ? ": " : "";
     65  const char* msg = aMsg ? aMsg : "";
     66  const mozilla::LauncherError& err = aResult.inspectErr();
     67  printf("%s%s%s%S (%s:%d)\n", kMsgStart, msg, kSep,
     68         err.mError.AsString().get(), err.mFile, err.mLine);
     69 }
     70 
     71 static int ChildMain(DWORD aExpectedParentPid) {
     72  mozilla::LauncherResult<DWORD> parentPid = mozilla::nt::GetParentProcessId();
     73  if (parentPid.isErr()) {
     74    PrintLauncherError(parentPid);
     75    return 1;
     76  }
     77 
     78  if (parentPid.inspect() != aExpectedParentPid) {
     79    PrintErrorMsg("Unexpected mismatch of parent PIDs");
     80    return 1;
     81  }
     82 
     83  const DWORD kAccess = PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_TERMINATE;
     84  nsAutoHandle parentProcess(
     85      ::OpenProcess(kAccess, FALSE, parentPid.inspect()));
     86  if (!parentProcess) {
     87    PrintWinError("Unexpectedly failed to call OpenProcess on parent");
     88    return 1;
     89  }
     90 
     91  EXPECT_SAMEBINARY_IS(
     92      true, mozilla::ImageFileCompareOption::Default,
     93      "IsSameBinaryAsParentProcess returned incorrect result for identical "
     94      "binaries");
     95  EXPECT_SAMEBINARY_IS(
     96      true, mozilla::ImageFileCompareOption::CompareNtPathsOnly,
     97      "IsSameBinaryAsParentProcess(CompareNtPathsOnly) returned incorrect "
     98      "result for identical binaries");
     99 
    100  // Total hack, but who cares? We'll set the parent's exit code as our PID
    101  // so that the monitor process knows who to wait for!
    102  if (!::TerminateProcess(parentProcess.get(), ::GetCurrentProcessId())) {
    103    PrintWinError("Unexpected failure in TerminateProcess");
    104    return 1;
    105  }
    106 
    107  // Close our handle to the parent process so that no references are held.
    108  ::CloseHandle(parentProcess.disown());
    109 
    110  // Querying a pid on a terminated process may still succeed some time after
    111  // that process has been terminated. For the purposes of this test, we'll poll
    112  // the OS until we cannot succesfully open the parentPid anymore.
    113  const uint32_t kMaxAttempts = 100;
    114  uint32_t curAttempt = 0;
    115  while (HANDLE p = ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE,
    116                                  parentPid.inspect())) {
    117    ::CloseHandle(p);
    118    ::Sleep(100);
    119    ++curAttempt;
    120    if (curAttempt >= kMaxAttempts) {
    121      PrintErrorMsg(
    122          "Exhausted retry attempts waiting for parent pid to become invalid");
    123      return 1;
    124    }
    125  }
    126 
    127  EXPECT_SAMEBINARY_IS(
    128      false, mozilla::ImageFileCompareOption::Default,
    129      "IsSameBinaryAsParentProcess returned incorrect result for dead parent "
    130      "process");
    131  EXPECT_SAMEBINARY_IS(
    132      false, mozilla::ImageFileCompareOption::CompareNtPathsOnly,
    133      "IsSameBinaryAsParentProcess(CompareNtPathsOnly) returned incorrect "
    134      "result for dead parent process");
    135 
    136  return 0;
    137 }
    138 
    139 static nsReturnRef<HANDLE> CreateSelfProcess(int argc, wchar_t* argv[]) {
    140  nsAutoHandle empty;
    141 
    142  DWORD myPid = ::GetCurrentProcessId();
    143 
    144  wchar_t strPid[11] = {};
    145 #if defined(__MINGW32__)
    146  _ultow(myPid, strPid, 16);
    147 #else
    148  if (_ultow_s(myPid, strPid, 16)) {
    149    PrintErrorMsg("_ultow_s failed");
    150    return empty.out();
    151  }
    152 #endif  // defined(__MINGW32__)
    153 
    154  wchar_t* extraArgs[] = {strPid};
    155 
    156  auto cmdLine =
    157      mozilla::MakeCommandLine(argc, argv, std::size(extraArgs), extraArgs);
    158  if (!cmdLine) {
    159    PrintErrorMsg("MakeCommandLine failed");
    160    return empty.out();
    161  }
    162 
    163  STARTUPINFOW si = {sizeof(si)};
    164  PROCESS_INFORMATION pi;
    165  BOOL ok =
    166      ::CreateProcessW(argv[0], cmdLine.get(), nullptr, nullptr, FALSE,
    167                       CREATE_UNICODE_ENVIRONMENT, nullptr, nullptr, &si, &pi);
    168  if (!ok) {
    169    PrintWinError("CreateProcess failed");
    170    return empty.out();
    171  }
    172 
    173  nsAutoHandle proc(pi.hProcess);
    174  nsAutoHandle thd(pi.hThread);
    175 
    176  return proc.out();
    177 }
    178 
    179 static int ParentMain(int argc, wchar_t* argv[]) {
    180  nsAutoHandle childProc(CreateSelfProcess(argc, argv));
    181  if (!childProc) {
    182    return 1;
    183  }
    184 
    185  if (::WaitForSingleObject(childProc.get(), INFINITE) != WAIT_OBJECT_0) {
    186    PrintWinError(
    187        "Unexpected result from WaitForSingleObject on child process");
    188    return 1;
    189  }
    190 
    191  MOZ_CRASH("This process should be terminated by now");
    192 }
    193 
    194 static int MonitorMain(int argc, wchar_t* argv[]) {
    195  // In this process, "parent" means the process that will be running
    196  // ParentMain, which is our child process (confusing, I know...)
    197  nsAutoHandle parentProc(CreateSelfProcess(argc, argv));
    198  if (!parentProc) {
    199    return 1;
    200  }
    201 
    202  if (::WaitForSingleObject(parentProc.get(), 60000) != WAIT_OBJECT_0) {
    203    PrintWinError("Unexpected result from WaitForSingleObject on parent");
    204    return 1;
    205  }
    206 
    207  DWORD childPid;
    208  if (!::GetExitCodeProcess(parentProc.get(), &childPid)) {
    209    PrintWinError("GetExitCodeProcess failed");
    210    return 1;
    211  }
    212 
    213  nsAutoHandle childProc(::OpenProcess(SYNCHRONIZE, FALSE, childPid));
    214  if (!childProc) {
    215    // Nothing to wait on anymore, which is OK.
    216    return 0;
    217  }
    218 
    219  // We want no more references to parentProc
    220  ::CloseHandle(parentProc.disown());
    221 
    222  if (::WaitForSingleObject(childProc.get(), 60000) != WAIT_OBJECT_0) {
    223    PrintWinError("Unexpected result from WaitForSingleObject on child");
    224    return 1;
    225  }
    226 
    227  return 0;
    228 }
    229 
    230 extern "C" int wmain(int argc, wchar_t* argv[]) {
    231  if (argc == 3) {
    232    return ChildMain(wcstoul(argv[2], nullptr, 16));
    233  }
    234 
    235  if (!mozilla::SetArgv0ToFullBinaryPath(argv)) {
    236    return 1;
    237  }
    238 
    239  if (argc == 1) {
    240    return MonitorMain(argc, argv);
    241  }
    242 
    243  if (argc == 2) {
    244    return ParentMain(argc, argv);
    245  }
    246 
    247  PrintErrorMsg("Unexpected argc");
    248  return 1;
    249 }