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