1 // Copyright 2015 The Crashpad Authors. All rights reserved.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include "util/win/exception_handler_server.h"
16 
17 #include <stdint.h>
18 #include <string.h>
19 #include <sys/types.h>
20 
21 #include <utility>
22 
23 #include "base/logging.h"
24 #include "base/numerics/safe_conversions.h"
25 #include "base/rand_util.h"
26 #include "base/stl_util.h"
27 #include "base/strings/stringprintf.h"
28 #include "base/strings/utf_string_conversions.h"
29 #include "minidump/minidump_file_writer.h"
30 #include "snapshot/crashpad_info_client_options.h"
31 #include "snapshot/win/process_snapshot_win.h"
32 #include "util/file/file_writer.h"
33 #include "util/misc/tri_state.h"
34 #include "util/misc/uuid.h"
35 #include "util/win/get_function.h"
36 #include "util/win/handle.h"
37 #include "util/win/registration_protocol_win.h"
38 #include "util/win/safe_terminate_process.h"
39 #include "util/win/xp_compat.h"
40 
41 namespace crashpad {
42 
43 namespace {
44 
GetNamedPipeClientProcessIdFunction()45 decltype(GetNamedPipeClientProcessId)* GetNamedPipeClientProcessIdFunction() {
46   static const auto get_named_pipe_client_process_id =
47       GET_FUNCTION(L"kernel32.dll", ::GetNamedPipeClientProcessId);
48   return get_named_pipe_client_process_id;
49 }
50 
DuplicateEvent(HANDLE process,HANDLE event)51 HANDLE DuplicateEvent(HANDLE process, HANDLE event) {
52   HANDLE handle;
53   if (DuplicateHandle(GetCurrentProcess(),
54                       event,
55                       process,
56                       &handle,
57                       SYNCHRONIZE | EVENT_MODIFY_STATE,
58                       false,
59                       0)) {
60     return handle;
61   }
62   return nullptr;
63 }
64 
65 }  // namespace
66 
67 namespace internal {
68 
69 //! \brief Context information for the named pipe handler threads.
70 class PipeServiceContext {
71  public:
PipeServiceContext(HANDLE port,HANDLE pipe,ExceptionHandlerServer::Delegate * delegate,base::Lock * clients_lock,std::set<internal::ClientData * > * clients,uint64_t shutdown_token)72   PipeServiceContext(HANDLE port,
73                      HANDLE pipe,
74                      ExceptionHandlerServer::Delegate* delegate,
75                      base::Lock* clients_lock,
76                      std::set<internal::ClientData*>* clients,
77                      uint64_t shutdown_token)
78       : port_(port),
79         pipe_(pipe),
80         delegate_(delegate),
81         clients_lock_(clients_lock),
82         clients_(clients),
83         shutdown_token_(shutdown_token) {}
84 
port() const85   HANDLE port() const { return port_; }
pipe() const86   HANDLE pipe() const { return pipe_.get(); }
delegate() const87   ExceptionHandlerServer::Delegate* delegate() const { return delegate_; }
clients_lock() const88   base::Lock* clients_lock() const { return clients_lock_; }
clients() const89   std::set<internal::ClientData*>* clients() const { return clients_; }
shutdown_token() const90   uint64_t shutdown_token() const { return shutdown_token_; }
91 
92  private:
93   HANDLE port_;  // weak
94   ScopedKernelHANDLE pipe_;
95   ExceptionHandlerServer::Delegate* delegate_;  // weak
96   base::Lock* clients_lock_;  // weak
97   std::set<internal::ClientData*>* clients_;  // weak
98   uint64_t shutdown_token_;
99 
100   DISALLOW_COPY_AND_ASSIGN(PipeServiceContext);
101 };
102 
103 //! \brief The context data for registered threadpool waits.
104 //!
105 //! This object must be created and destroyed on the main thread. Access must be
106 //! guarded by use of the lock() with the exception of the threadpool wait
107 //! variables which are accessed only by the main thread.
108 class ClientData {
109  public:
ClientData(HANDLE port,ExceptionHandlerServer::Delegate * delegate,ScopedKernelHANDLE process,ScopedKernelHANDLE crash_dump_requested_event,ScopedKernelHANDLE non_crash_dump_requested_event,ScopedKernelHANDLE non_crash_dump_completed_event,WinVMAddress crash_exception_information_address,WinVMAddress non_crash_exception_information_address,WinVMAddress debug_critical_section_address,WAITORTIMERCALLBACK crash_dump_request_callback,WAITORTIMERCALLBACK non_crash_dump_request_callback,WAITORTIMERCALLBACK process_end_callback)110   ClientData(HANDLE port,
111              ExceptionHandlerServer::Delegate* delegate,
112              ScopedKernelHANDLE process,
113              ScopedKernelHANDLE crash_dump_requested_event,
114              ScopedKernelHANDLE non_crash_dump_requested_event,
115              ScopedKernelHANDLE non_crash_dump_completed_event,
116              WinVMAddress crash_exception_information_address,
117              WinVMAddress non_crash_exception_information_address,
118              WinVMAddress debug_critical_section_address,
119              WAITORTIMERCALLBACK crash_dump_request_callback,
120              WAITORTIMERCALLBACK non_crash_dump_request_callback,
121              WAITORTIMERCALLBACK process_end_callback)
122       : crash_dump_request_thread_pool_wait_(INVALID_HANDLE_VALUE),
123         non_crash_dump_request_thread_pool_wait_(INVALID_HANDLE_VALUE),
124         process_end_thread_pool_wait_(INVALID_HANDLE_VALUE),
125         lock_(),
126         port_(port),
127         delegate_(delegate),
128         crash_dump_requested_event_(std::move(crash_dump_requested_event)),
129         non_crash_dump_requested_event_(
130             std::move(non_crash_dump_requested_event)),
131         non_crash_dump_completed_event_(
132             std::move(non_crash_dump_completed_event)),
133         process_(std::move(process)),
134         crash_exception_information_address_(
135             crash_exception_information_address),
136         non_crash_exception_information_address_(
137             non_crash_exception_information_address),
138         debug_critical_section_address_(debug_critical_section_address) {
139     RegisterThreadPoolWaits(crash_dump_request_callback,
140                             non_crash_dump_request_callback,
141                             process_end_callback);
142   }
143 
~ClientData()144   ~ClientData() {
145     // It is important that this only access the threadpool waits (it's called
146     // from the main thread) until the waits are unregistered, to ensure that
147     // any outstanding callbacks are complete.
148     UnregisterThreadPoolWaits();
149   }
150 
lock()151   base::Lock* lock() { return &lock_; }
port() const152   HANDLE port() const { return port_; }
delegate() const153   ExceptionHandlerServer::Delegate* delegate() const { return delegate_; }
crash_dump_requested_event() const154   HANDLE crash_dump_requested_event() const {
155     return crash_dump_requested_event_.get();
156   }
non_crash_dump_requested_event() const157   HANDLE non_crash_dump_requested_event() const {
158     return non_crash_dump_requested_event_.get();
159   }
non_crash_dump_completed_event() const160   HANDLE non_crash_dump_completed_event() const {
161     return non_crash_dump_completed_event_.get();
162   }
crash_exception_information_address() const163   WinVMAddress crash_exception_information_address() const {
164     return crash_exception_information_address_;
165   }
non_crash_exception_information_address() const166   WinVMAddress non_crash_exception_information_address() const {
167     return non_crash_exception_information_address_;
168   }
debug_critical_section_address() const169   WinVMAddress debug_critical_section_address() const {
170     return debug_critical_section_address_;
171   }
process() const172   HANDLE process() const { return process_.get(); }
173 
174  private:
RegisterThreadPoolWaits(WAITORTIMERCALLBACK crash_dump_request_callback,WAITORTIMERCALLBACK non_crash_dump_request_callback,WAITORTIMERCALLBACK process_end_callback)175   void RegisterThreadPoolWaits(
176       WAITORTIMERCALLBACK crash_dump_request_callback,
177       WAITORTIMERCALLBACK non_crash_dump_request_callback,
178       WAITORTIMERCALLBACK process_end_callback) {
179     if (!RegisterWaitForSingleObject(&crash_dump_request_thread_pool_wait_,
180                                      crash_dump_requested_event_.get(),
181                                      crash_dump_request_callback,
182                                      this,
183                                      INFINITE,
184                                      WT_EXECUTEDEFAULT)) {
185       LOG(ERROR) << "RegisterWaitForSingleObject crash dump requested";
186     }
187 
188     if (!RegisterWaitForSingleObject(&non_crash_dump_request_thread_pool_wait_,
189                                      non_crash_dump_requested_event_.get(),
190                                      non_crash_dump_request_callback,
191                                      this,
192                                      INFINITE,
193                                      WT_EXECUTEDEFAULT)) {
194       LOG(ERROR) << "RegisterWaitForSingleObject non-crash dump requested";
195     }
196 
197     if (!RegisterWaitForSingleObject(&process_end_thread_pool_wait_,
198                                      process_.get(),
199                                      process_end_callback,
200                                      this,
201                                      INFINITE,
202                                      WT_EXECUTEONLYONCE)) {
203       LOG(ERROR) << "RegisterWaitForSingleObject process end";
204     }
205   }
206 
207   // This blocks until outstanding calls complete so that we know it's safe to
208   // delete this object. Because of this, it must be executed on the main
209   // thread, not a threadpool thread.
UnregisterThreadPoolWaits()210   void UnregisterThreadPoolWaits() {
211     UnregisterWaitEx(crash_dump_request_thread_pool_wait_,
212                      INVALID_HANDLE_VALUE);
213     crash_dump_request_thread_pool_wait_ = INVALID_HANDLE_VALUE;
214     UnregisterWaitEx(non_crash_dump_request_thread_pool_wait_,
215                      INVALID_HANDLE_VALUE);
216     non_crash_dump_request_thread_pool_wait_ = INVALID_HANDLE_VALUE;
217     UnregisterWaitEx(process_end_thread_pool_wait_, INVALID_HANDLE_VALUE);
218     process_end_thread_pool_wait_ = INVALID_HANDLE_VALUE;
219   }
220 
221   // These are only accessed on the main thread.
222   HANDLE crash_dump_request_thread_pool_wait_;
223   HANDLE non_crash_dump_request_thread_pool_wait_;
224   HANDLE process_end_thread_pool_wait_;
225 
226   base::Lock lock_;
227   // Access to these fields must be guarded by lock_.
228   HANDLE port_;  // weak
229   ExceptionHandlerServer::Delegate* delegate_;  // weak
230   ScopedKernelHANDLE crash_dump_requested_event_;
231   ScopedKernelHANDLE non_crash_dump_requested_event_;
232   ScopedKernelHANDLE non_crash_dump_completed_event_;
233   ScopedKernelHANDLE process_;
234   WinVMAddress crash_exception_information_address_;
235   WinVMAddress non_crash_exception_information_address_;
236   WinVMAddress debug_critical_section_address_;
237 
238   DISALLOW_COPY_AND_ASSIGN(ClientData);
239 };
240 
241 }  // namespace internal
242 
~Delegate()243 ExceptionHandlerServer::Delegate::~Delegate() {
244 }
245 
ExceptionHandlerServer(bool persistent)246 ExceptionHandlerServer::ExceptionHandlerServer(bool persistent)
247     : pipe_name_(),
248       port_(CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, 0, 1)),
249       first_pipe_instance_(),
250       clients_lock_(),
251       clients_(),
252       persistent_(persistent) {
253 }
254 
~ExceptionHandlerServer()255 ExceptionHandlerServer::~ExceptionHandlerServer() {
256 }
257 
SetPipeName(const std::wstring & pipe_name)258 void ExceptionHandlerServer::SetPipeName(const std::wstring& pipe_name) {
259   DCHECK(pipe_name_.empty());
260   DCHECK(!pipe_name.empty());
261 
262   pipe_name_ = pipe_name;
263 }
264 
InitializeWithInheritedDataForInitialClient(const InitialClientData & initial_client_data,Delegate * delegate)265 void ExceptionHandlerServer::InitializeWithInheritedDataForInitialClient(
266     const InitialClientData& initial_client_data,
267     Delegate* delegate) {
268   DCHECK(pipe_name_.empty());
269   DCHECK(!first_pipe_instance_.is_valid());
270 
271   first_pipe_instance_.reset(initial_client_data.first_pipe_instance());
272 
273   // TODO(scottmg): Vista+. Might need to pass through or possibly find an Nt*.
274   size_t bytes = sizeof(wchar_t) * _MAX_PATH + sizeof(FILE_NAME_INFO);
275   std::unique_ptr<uint8_t[]> data(new uint8_t[bytes]);
276   if (!GetFileInformationByHandleEx(first_pipe_instance_.get(),
277                                     FileNameInfo,
278                                     data.get(),
279                                     static_cast<DWORD>(bytes))) {
280     PLOG(FATAL) << "GetFileInformationByHandleEx";
281   }
282   FILE_NAME_INFO* file_name_info =
283       reinterpret_cast<FILE_NAME_INFO*>(data.get());
284   pipe_name_ =
285       L"\\\\.\\pipe" + std::wstring(file_name_info->FileName,
286                                     file_name_info->FileNameLength /
287                                         sizeof(file_name_info->FileName[0]));
288 
289   {
290     base::AutoLock lock(clients_lock_);
291     internal::ClientData* client = new internal::ClientData(
292         port_.get(),
293         delegate,
294         ScopedKernelHANDLE(initial_client_data.client_process()),
295         ScopedKernelHANDLE(initial_client_data.request_crash_dump()),
296         ScopedKernelHANDLE(initial_client_data.request_non_crash_dump()),
297         ScopedKernelHANDLE(initial_client_data.non_crash_dump_completed()),
298         initial_client_data.crash_exception_information(),
299         initial_client_data.non_crash_exception_information(),
300         initial_client_data.debug_critical_section_address(),
301         &OnCrashDumpEvent,
302         &OnNonCrashDumpEvent,
303         &OnProcessEnd);
304     clients_.insert(client);
305   }
306 }
307 
Run(Delegate * delegate)308 void ExceptionHandlerServer::Run(Delegate* delegate) {
309   uint64_t shutdown_token = base::RandUint64();
310   ScopedKernelHANDLE thread_handles[kPipeInstances];
311   for (size_t i = 0; i < base::size(thread_handles); ++i) {
312     HANDLE pipe;
313     if (first_pipe_instance_.is_valid()) {
314       pipe = first_pipe_instance_.release();
315     } else {
316       pipe = CreateNamedPipeInstance(pipe_name_, i == 0);
317       PCHECK(pipe != INVALID_HANDLE_VALUE) << "CreateNamedPipe";
318     }
319 
320     // Ownership of this object (and the pipe instance) is given to the new
321     // thread. We close the thread handles at the end of the scope. They clean
322     // up the context object and the pipe instance on termination.
323     internal::PipeServiceContext* context =
324         new internal::PipeServiceContext(port_.get(),
325                                          pipe,
326                                          delegate,
327                                          &clients_lock_,
328                                          &clients_,
329                                          shutdown_token);
330     thread_handles[i].reset(
331         CreateThread(nullptr, 0, &PipeServiceProc, context, 0, nullptr));
332     PCHECK(thread_handles[i].is_valid()) << "CreateThread";
333   }
334 
335   delegate->ExceptionHandlerServerStarted();
336 
337   // This is the main loop of the server. Most work is done on the threadpool,
338   // other than process end handling which is posted back to this main thread,
339   // as we must unregister the threadpool waits here.
340   for (;;) {
341     OVERLAPPED* ov = nullptr;
342     ULONG_PTR key = 0;
343     DWORD bytes = 0;
344     GetQueuedCompletionStatus(port_.get(), &bytes, &key, &ov, INFINITE);
345     if (!key) {
346       // Shutting down.
347       break;
348     }
349 
350     // Otherwise, this is a request to unregister and destroy the given client.
351     // delete'ing the ClientData blocks in UnregisterWaitEx to ensure all
352     // outstanding threadpool waits are complete. This is important because the
353     // process handle can be signalled *before* the dump request is signalled.
354     internal::ClientData* client = reinterpret_cast<internal::ClientData*>(key);
355     base::AutoLock lock(clients_lock_);
356     clients_.erase(client);
357     delete client;
358     if (!persistent_ && clients_.empty())
359       break;
360   }
361 
362   // Signal to the named pipe instances that they should terminate.
363   for (size_t i = 0; i < base::size(thread_handles); ++i) {
364     ClientToServerMessage message;
365     memset(&message, 0, sizeof(message));
366     message.type = ClientToServerMessage::kShutdown;
367     message.shutdown.token = shutdown_token;
368     ServerToClientMessage response;
369     SendToCrashHandlerServer(pipe_name_,
370                              reinterpret_cast<ClientToServerMessage&>(message),
371                              &response);
372   }
373 
374   for (auto& handle : thread_handles)
375     WaitForSingleObject(handle.get(), INFINITE);
376 
377   // Deleting ClientData does a blocking wait until the threadpool executions
378   // have terminated when unregistering them.
379   {
380     base::AutoLock lock(clients_lock_);
381     for (auto* client : clients_)
382       delete client;
383     clients_.clear();
384   }
385 }
386 
Stop()387 void ExceptionHandlerServer::Stop() {
388   // Post a null key (third argument) to trigger shutdown.
389   PostQueuedCompletionStatus(port_.get(), 0, 0, nullptr);
390 }
391 
392 // This function must be called with service_context.pipe() already connected to
393 // a client pipe. It exchanges data with the client and adds a ClientData record
394 // to service_context->clients().
395 //
396 // static
ServiceClientConnection(const internal::PipeServiceContext & service_context)397 bool ExceptionHandlerServer::ServiceClientConnection(
398     const internal::PipeServiceContext& service_context) {
399   ClientToServerMessage message;
400 
401   if (!LoggingReadFileExactly(
402           service_context.pipe(), &message, sizeof(message)))
403     return false;
404 
405   switch (message.type) {
406     case ClientToServerMessage::kShutdown: {
407       if (message.shutdown.token != service_context.shutdown_token()) {
408         LOG(ERROR) << "forged shutdown request, got: "
409                    << message.shutdown.token;
410         return false;
411       }
412       ServerToClientMessage shutdown_response = {};
413       LoggingWriteFile(service_context.pipe(),
414                        &shutdown_response,
415                        sizeof(shutdown_response));
416       return true;
417     }
418 
419     case ClientToServerMessage::kPing: {
420       // No action required, the fact that the message was processed is
421       // sufficient.
422       ServerToClientMessage shutdown_response = {};
423       LoggingWriteFile(service_context.pipe(),
424                        &shutdown_response,
425                        sizeof(shutdown_response));
426       return false;
427     }
428 
429     case ClientToServerMessage::kRegister:
430       // Handled below.
431       break;
432 
433     default:
434       LOG(ERROR) << "unhandled message type: " << message.type;
435       return false;
436   }
437 
438   if (message.registration.version != RegistrationRequest::kMessageVersion) {
439     LOG(ERROR) << "unexpected version. got: " << message.registration.version
440                << " expecting: " << RegistrationRequest::kMessageVersion;
441     return false;
442   }
443 
444   decltype(GetNamedPipeClientProcessId)* get_named_pipe_client_process_id =
445       GetNamedPipeClientProcessIdFunction();
446   if (get_named_pipe_client_process_id) {
447     // GetNamedPipeClientProcessId is only available on Vista+.
448     DWORD real_pid = 0;
449     if (get_named_pipe_client_process_id(service_context.pipe(), &real_pid) &&
450         message.registration.client_process_id != real_pid) {
451       LOG(ERROR) << "forged client pid, real pid: " << real_pid
452                  << ", got: " << message.registration.client_process_id;
453       return false;
454     }
455   }
456 
457   // We attempt to open the process as us. This is the main case that should
458   // almost always succeed as the server will generally be more privileged. If
459   // we're running as a different user, it may be that we will fail to open
460   // the process, but the client will be able to, so we make a second attempt
461   // having impersonated the client.
462   HANDLE client_process = OpenProcess(
463       kXPProcessAllAccess, false, message.registration.client_process_id);
464   if (!client_process) {
465     if (!ImpersonateNamedPipeClient(service_context.pipe())) {
466       PLOG(ERROR) << "ImpersonateNamedPipeClient";
467       return false;
468     }
469     client_process = OpenProcess(
470         kXPProcessAllAccess, false, message.registration.client_process_id);
471     PCHECK(RevertToSelf());
472     if (!client_process) {
473       LOG(ERROR) << "failed to open " << message.registration.client_process_id;
474       return false;
475     }
476   }
477 
478   internal::ClientData* client;
479   {
480     base::AutoLock lock(*service_context.clients_lock());
481     client = new internal::ClientData(
482         service_context.port(),
483         service_context.delegate(),
484         ScopedKernelHANDLE(client_process),
485         ScopedKernelHANDLE(
486             CreateEvent(nullptr, false /* auto reset */, false, nullptr)),
487         ScopedKernelHANDLE(
488             CreateEvent(nullptr, false /* auto reset */, false, nullptr)),
489         ScopedKernelHANDLE(
490             CreateEvent(nullptr, false /* auto reset */, false, nullptr)),
491         message.registration.crash_exception_information,
492         message.registration.non_crash_exception_information,
493         message.registration.critical_section_address,
494         &OnCrashDumpEvent,
495         &OnNonCrashDumpEvent,
496         &OnProcessEnd);
497     service_context.clients()->insert(client);
498   }
499 
500   // Duplicate the events back to the client so they can request a dump.
501   ServerToClientMessage response;
502   response.registration.request_crash_dump_event =
503       HandleToInt(DuplicateEvent(
504           client->process(), client->crash_dump_requested_event()));
505   response.registration.request_non_crash_dump_event =
506       HandleToInt(DuplicateEvent(
507           client->process(), client->non_crash_dump_requested_event()));
508   response.registration.non_crash_dump_completed_event =
509       HandleToInt(DuplicateEvent(
510           client->process(), client->non_crash_dump_completed_event()));
511 
512   if (!LoggingWriteFile(service_context.pipe(), &response, sizeof(response)))
513     return false;
514 
515   return false;
516 }
517 
518 // static
PipeServiceProc(void * ctx)519 DWORD __stdcall ExceptionHandlerServer::PipeServiceProc(void* ctx) {
520   internal::PipeServiceContext* service_context =
521       reinterpret_cast<internal::PipeServiceContext*>(ctx);
522   DCHECK(service_context);
523 
524   for (;;) {
525     bool ret = !!ConnectNamedPipe(service_context->pipe(), nullptr);
526     if (!ret && GetLastError() != ERROR_PIPE_CONNECTED) {
527       PLOG(ERROR) << "ConnectNamedPipe";
528     } else if (ServiceClientConnection(*service_context)) {
529       break;
530     }
531     DisconnectNamedPipe(service_context->pipe());
532   }
533 
534   delete service_context;
535 
536   return 0;
537 }
538 
539 // static
OnCrashDumpEvent(void * ctx,BOOLEAN)540 void __stdcall ExceptionHandlerServer::OnCrashDumpEvent(void* ctx, BOOLEAN) {
541   // This function is executed on the thread pool.
542   internal::ClientData* client = reinterpret_cast<internal::ClientData*>(ctx);
543   base::AutoLock lock(*client->lock());
544 
545   // Capture the exception.
546   unsigned int exit_code = client->delegate()->ExceptionHandlerServerException(
547       client->process(),
548       client->crash_exception_information_address(),
549       client->debug_critical_section_address());
550 
551   SafeTerminateProcess(client->process(), exit_code);
552 }
553 
554 // static
OnNonCrashDumpEvent(void * ctx,BOOLEAN)555 void __stdcall ExceptionHandlerServer::OnNonCrashDumpEvent(void* ctx, BOOLEAN) {
556   // This function is executed on the thread pool.
557   internal::ClientData* client = reinterpret_cast<internal::ClientData*>(ctx);
558   base::AutoLock lock(*client->lock());
559 
560   // Capture the exception.
561   client->delegate()->ExceptionHandlerServerException(
562       client->process(),
563       client->non_crash_exception_information_address(),
564       client->debug_critical_section_address());
565 
566   bool result = !!SetEvent(client->non_crash_dump_completed_event());
567   PLOG_IF(ERROR, !result) << "SetEvent";
568 }
569 
570 // static
OnProcessEnd(void * ctx,BOOLEAN)571 void __stdcall ExceptionHandlerServer::OnProcessEnd(void* ctx, BOOLEAN) {
572   // This function is executed on the thread pool.
573   internal::ClientData* client = reinterpret_cast<internal::ClientData*>(ctx);
574   base::AutoLock lock(*client->lock());
575 
576   // Post back to the main thread to have it delete this client record.
577   PostQueuedCompletionStatus(client->port(), 0, ULONG_PTR(client), nullptr);
578 }
579 
580 }  // namespace crashpad
581