tor-browser

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

process_watcher_win.cc (8819B)


      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 // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
      4 // Use of this source code is governed by a BSD-style license that can be
      5 // found in the LICENSE file.
      6 
      7 #include "chrome/common/process_watcher.h"
      8 
      9 #include <algorithm>
     10 #include <processthreadsapi.h>
     11 #include <synchapi.h>
     12 #include "base/message_loop.h"
     13 #include "base/object_watcher.h"
     14 #include "prenv.h"
     15 
     16 // Maximum amount of time (in milliseconds) to wait for the process to exit.
     17 static constexpr int kWaitInterval = 2000;
     18 
     19 // This is somewhat arbitrary, but based on Try run results.  When
     20 // changing this, be aware of toolkit.asyncshutdown.crash_timeout
     21 // (currently 60s), after which the parent process will be killed.
     22 #ifdef MOZ_CODE_COVERAGE
     23 // Child processes seem to take longer to shut down on ccov builds, at
     24 // least in the wdspec tests; ~20s has been observed, and we'll spam
     25 // false positives unless this is increased.
     26 static constexpr DWORD kShutdownWaitMs = 80000;
     27 #elif defined(MOZ_ASAN) || defined(MOZ_TSAN)
     28 // Sanitizers also slow things down in some cases; see bug 1806224.
     29 static constexpr DWORD kShutdownWaitMs = 40000;
     30 #else
     31 static constexpr DWORD kShutdownWaitMs = 20000;
     32 #endif
     33 
     34 namespace {
     35 
     36 static bool IsProcessDead(base::ProcessHandle process) {
     37  return WaitForSingleObject(process, 0) == WAIT_OBJECT_0;
     38 }
     39 
     40 class ChildReaper : public mozilla::Runnable,
     41                    public base::ObjectWatcher::Delegate,
     42                    public MessageLoop::DestructionObserver {
     43 public:
     44  explicit ChildReaper(base::ProcessHandle process, bool force)
     45      : mozilla::Runnable("ChildReaper"), process_(process), force_(force) {
     46    watcher_.StartWatching(process_, this);
     47  }
     48 
     49  virtual ~ChildReaper() {
     50    if (process_) {
     51      KillProcess();
     52      DCHECK(!process_) << "Make sure to close the handle.";
     53    }
     54  }
     55 
     56  // MessageLoop::DestructionObserver -----------------------------------------
     57 
     58  virtual void WillDestroyCurrentMessageLoop() {
     59    MOZ_ASSERT(!force_);
     60    if (process_) {
     61      // Exception for the fake hang tests in ipc/glue/test/browser
     62      if (!PR_GetEnv("MOZ_TEST_CHILD_EXIT_HANG")) {
     63        CrashProcessIfHanging();
     64      }
     65      WaitForSingleObject(process_, INFINITE);
     66      base::CloseProcessHandle(process_);
     67      process_ = 0;
     68 
     69      MessageLoop::current()->RemoveDestructionObserver(this);
     70      delete this;
     71    }
     72  }
     73 
     74  // Task ---------------------------------------------------------------------
     75 
     76  NS_IMETHOD Run() override {
     77    MOZ_ASSERT(force_);
     78    if (process_) {
     79      KillProcess();
     80    }
     81    return NS_OK;
     82  }
     83 
     84  // MessageLoop::Watcher -----------------------------------------------------
     85 
     86  virtual void OnObjectSignaled(HANDLE object) {
     87    // When we're called from KillProcess, the ObjectWatcher may still be
     88    // watching.  the process handle, so make sure it has stopped.
     89    watcher_.StopWatching();
     90 
     91    base::CloseProcessHandle(process_);
     92    process_ = 0;
     93 
     94    if (!force_) {
     95      MessageLoop::current()->RemoveDestructionObserver(this);
     96      delete this;
     97    }
     98  }
     99 
    100 private:
    101  void KillProcess() {
    102    MOZ_ASSERT(force_);
    103 
    104    // OK, time to get frisky.  We don't actually care when the process
    105    // terminates.  We just care that it eventually terminates, and that's what
    106    // TerminateProcess should do for us. Don't check for the result code since
    107    // it fails quite often. This should be investigated eventually.
    108    TerminateProcess(process_, base::PROCESS_END_PROCESS_WAS_HUNG);
    109 
    110    // Now, just cleanup as if the process exited normally.
    111    OnObjectSignaled(process_);
    112  }
    113 
    114  void CrashProcessIfHanging() {
    115    if (IsProcessDead(process_)) {
    116      return;
    117    }
    118    DWORD pid = GetProcessId(process_);
    119    DCHECK(pid != 0);
    120 
    121    // If child processes seems to be hanging on shutdown, wait for a
    122    // reasonable time.  The wait is global instead of per-process
    123    // because the child processes should be shutting down in
    124    // parallel, and also we're potentially racing global timeouts
    125    // like nsTerminator.  (The counter doesn't need to be atomic;
    126    // this is always called on the I/O thread.)
    127    static DWORD sWaitMs = kShutdownWaitMs;
    128    if (sWaitMs > 0) {
    129      CHROMIUM_LOG(WARNING)
    130          << "Process " << pid
    131          << " may be hanging at shutdown; will wait for up to " << sWaitMs
    132          << "ms";
    133    }
    134    const auto beforeWait = mozilla::TimeStamp::NowLoRes();
    135    const DWORD waitStatus = WaitForSingleObject(process_, sWaitMs);
    136 
    137    const double elapsed =
    138        (mozilla::TimeStamp::NowLoRes() - beforeWait).ToMilliseconds();
    139    sWaitMs -= static_cast<DWORD>(
    140        std::clamp(elapsed, 0.0, static_cast<double>(sWaitMs)));
    141 
    142    switch (waitStatus) {
    143      case WAIT_TIMEOUT:
    144        // The process is still running.
    145        break;
    146      case WAIT_OBJECT_0:
    147        // The process exited.
    148        return;
    149      case WAIT_FAILED:
    150        CHROMIUM_LOG(ERROR) << "Waiting for process " << pid
    151                            << " failed; error " << GetLastError();
    152        DCHECK(false) << "WaitForSingleObject failed";
    153        // Process status unclear; assume it's gone.
    154        return;
    155      default:
    156        DCHECK(false) << "WaitForSingleObject returned " << waitStatus;
    157        // Again, not clear what's happening so avoid touching the process
    158        return;
    159    }
    160 
    161    // We want TreeHerder to flag this log line as an error, so that
    162    // this is more obviously a deliberate crash; "fatal error" is one
    163    // of the strings it looks for.
    164    CHROMIUM_LOG(ERROR)
    165        << "Process " << pid
    166        << " hanging at shutdown; attempting crash report (fatal error)";
    167 
    168    // We're going to use CreateRemoteThread to call DbgBreakPoint in
    169    // the target process; it's in a "known DLL" so it should be at
    170    // the same address in all processes.  (Normal libraries, like
    171    // xul.dll, are usually at the same address but can be relocated
    172    // in case of conflict.)
    173    //
    174    // DbgBreakPoint doesn't take an argument, so we can give it an
    175    // arbitrary value to make it easier to identify these crash
    176    // reports.  (reinterpret_cast isn't constexpr, so this is
    177    // declared as an integer and cast to the required type later.)
    178    // The primary use case for all of this is in CI, where we'll also
    179    // have log messages, but if these crashes end up in Socorro in
    180    // significant numbers then we'll be able to look for this value.
    181    static constexpr uint64_t kIpcMagic = 0x43504900435049;
    182 
    183    const HMODULE ntdll = GetModuleHandleW(L"ntdll.dll");
    184    if (!ntdll) {
    185      CHROMIUM_LOG(ERROR) << "couldn't find ntdll.dll: error "
    186                          << GetLastError();
    187      return;
    188    }
    189    const auto dbgBreak = reinterpret_cast<LPTHREAD_START_ROUTINE>(
    190        GetProcAddress(ntdll, "DbgBreakPoint"));
    191    if (!dbgBreak) {
    192      CHROMIUM_LOG(ERROR) << "couldn't find DbgBreakPoint: error "
    193                          << GetLastError();
    194      return;
    195    }
    196 
    197    const DWORD rights = PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION |
    198                         PROCESS_VM_OPERATION | PROCESS_VM_WRITE |
    199                         PROCESS_VM_READ;
    200    HANDLE process_priv = nullptr;
    201    if (!DuplicateHandle(GetCurrentProcess(), process_, GetCurrentProcess(),
    202                         &process_priv, rights, /* inherit */ FALSE,
    203                         /* options */ 0)) {
    204      const auto error = GetLastError();
    205      CHROMIUM_LOG(ERROR) << "OpenProcess: error " << error;
    206    } else {
    207      DCHECK(process_priv);
    208      HANDLE thread =
    209          CreateRemoteThread(process_priv, /* sec attr */ nullptr,
    210                             /* stack */ 0, dbgBreak, (LPVOID)kIpcMagic,
    211                             /* flags */ 0, nullptr);
    212      if (!thread) {
    213        const auto error = GetLastError();
    214        CHROMIUM_LOG(ERROR) << "CreateRemoteThread: error " << error;
    215      } else {
    216        CloseHandle(thread);
    217      }
    218      CloseHandle(process_priv);
    219    }
    220  }
    221 
    222  // The process that we are watching.
    223  base::ProcessHandle process_;
    224 
    225  base::ObjectWatcher watcher_;
    226 
    227  bool force_;
    228 
    229  DISALLOW_EVIL_CONSTRUCTORS(ChildReaper);
    230 };
    231 
    232 }  // namespace
    233 
    234 // static
    235 void ProcessWatcher::EnsureProcessTerminated(base::ProcessHandle process,
    236                                             bool force) {
    237  DCHECK(process != GetCurrentProcess());
    238 
    239  // If already signaled, then we are done!
    240  if (IsProcessDead(process)) {
    241    base::CloseProcessHandle(process);
    242    return;
    243  }
    244 
    245  MessageLoopForIO* loop = MessageLoopForIO::current();
    246  if (force) {
    247    RefPtr<mozilla::Runnable> task = new ChildReaper(process, force);
    248    loop->PostDelayedTask(task.forget(), kWaitInterval);
    249  } else {
    250    loop->AddDestructionObserver(new ChildReaper(process, force));
    251  }
    252 }