OLD | NEW |
---|---|
(Empty) | |
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 "base/logging.h" | |
18 #include "base/strings/stringprintf.h" | |
19 #include "base/strings/utf_string_conversions.h" | |
20 #include "minidump/minidump_file_writer.h" | |
21 #include "snapshot/crashpad_info_client_options.h" | |
22 #include "snapshot/win/process_snapshot_win.h" | |
23 #include "util/file/file_writer.h" | |
24 #include "util/misc/tri_state.h" | |
25 #include "util/misc/uuid.h" | |
26 #include "util/win/registration_protocol_win.h" | |
27 | |
28 namespace crashpad { | |
29 | |
30 namespace { | |
31 | |
32 //! \brief Context information for the named pipe handler threads. | |
33 class PipeServiceContext { | |
34 public: | |
35 PipeServiceContext(HANDLE port, | |
36 HANDLE pipe, | |
37 ExceptionHandlerServer::Delegate* delegate, | |
38 base::Lock* clients_lock, | |
39 std::set<internal::ClientData*>* clients) | |
40 : port_(port), | |
41 pipe_(pipe), | |
42 delegate_(delegate), | |
43 clients_lock_(clients_lock), | |
44 clients_(clients) {} | |
45 | |
46 HANDLE port() { return port_; } | |
47 HANDLE pipe() { return pipe_.get(); } | |
48 ExceptionHandlerServer::Delegate* delegate() { return delegate_; } | |
49 base::Lock* clients_lock() { return clients_lock_; } | |
50 std::set<internal::ClientData*>* clients() { return clients_; } | |
51 | |
52 private: | |
53 HANDLE port_; // weak | |
54 ScopedKernelHANDLE pipe_; | |
55 ExceptionHandlerServer::Delegate* delegate_; // weak | |
56 base::Lock* clients_lock_; // weak | |
57 std::set<internal::ClientData*>* clients_; // weak | |
58 | |
59 DISALLOW_COPY_AND_ASSIGN(PipeServiceContext); | |
60 }; | |
61 | |
62 decltype(GetNamedPipeClientProcessId)* GetNamedPipeClientProcessIdFunction() { | |
63 static decltype(GetNamedPipeClientProcessId)* func = | |
64 reinterpret_cast<decltype(GetNamedPipeClientProcessId)*>(GetProcAddress( | |
65 GetModuleHandle(L"kernel32.dll"), "GetNamedPipeClientProcessId")); | |
66 return func; | |
67 } | |
68 | |
69 HANDLE DuplicateEvent(HANDLE process, HANDLE event) { | |
70 HANDLE handle; | |
71 if (DuplicateHandle(GetCurrentProcess(), | |
72 event, | |
73 process, | |
74 &handle, | |
75 SYNCHRONIZE | EVENT_MODIFY_STATE, | |
76 false, | |
77 0)) { | |
78 return handle; | |
79 } | |
80 return nullptr; | |
81 } | |
82 | |
83 } // namespace | |
84 | |
85 namespace internal { | |
86 | |
87 //! \brief The context data for registered threadpool waits. | |
88 //! | |
89 //! This object must be created and destroyed on the main thread. Access must be | |
90 //! guarded by use of the lock() with the exception of the threadpool wait | |
91 //! variables which are accessed only by the main thread. | |
92 class ClientData { | |
93 public: | |
94 ClientData(HANDLE port, | |
95 ExceptionHandlerServer::Delegate* delegate, | |
96 ScopedKernelHANDLE process, | |
97 WinVMAddress exception_information_address, | |
98 WAITORTIMERCALLBACK dump_request_callback, | |
99 WAITORTIMERCALLBACK process_end_callback) | |
100 : dump_request_thread_pool_wait_(INVALID_HANDLE_VALUE), | |
101 process_end_thread_pool_wait_(INVALID_HANDLE_VALUE), | |
102 lock_(), | |
103 port_(port), | |
104 delegate_(delegate), | |
105 dump_requested_event_( | |
106 CreateEvent(nullptr, false /* auto reset */, false, nullptr)), | |
107 process_(process.Pass()), | |
108 exception_information_address_(exception_information_address) { | |
109 RegisterThreadPoolWaits(dump_request_callback, process_end_callback); | |
110 } | |
111 | |
112 ~ClientData() { | |
113 // It is important that this only access the threadpool waits (it's called | |
114 // from the main thread) until the waits are unregistered, to ensure that | |
115 // any outstanding callbacks are complete. | |
116 UnregisterThreadPoolWaits(); | |
117 } | |
118 | |
119 base::Lock* lock() { return &lock_; } | |
120 HANDLE port() { return port_; } | |
121 ExceptionHandlerServer::Delegate* delegate() { return delegate_; } | |
122 HANDLE dump_requested_event() { return dump_requested_event_.get(); } | |
123 WinVMAddress exception_information_address() const { | |
124 return exception_information_address_; | |
125 } | |
126 HANDLE process() { return process_.get(); } | |
127 | |
128 private: | |
129 void RegisterThreadPoolWaits(WAITORTIMERCALLBACK dump_request_callback, | |
130 WAITORTIMERCALLBACK process_end_callback) { | |
131 if (!RegisterWaitForSingleObject(&dump_request_thread_pool_wait_, | |
132 dump_requested_event_.get(), | |
133 dump_request_callback, | |
134 this, | |
135 INFINITE, | |
136 WT_EXECUTEDEFAULT)) { | |
137 LOG(ERROR) << "RegisterWaitForSingleObject dump requested"; | |
138 } | |
139 | |
140 if (!RegisterWaitForSingleObject(&process_end_thread_pool_wait_, | |
141 process_.get(), | |
142 process_end_callback, | |
143 this, | |
144 INFINITE, | |
145 WT_EXECUTEONLYONCE)) { | |
146 LOG(ERROR) << "RegisterWaitForSingleObject process end"; | |
147 } | |
148 } | |
149 | |
150 // This blocks until outstanding calls complete so that we know it's safe to | |
151 // delete this object. Because of this, it must be executed on the main | |
152 // thread, not a threadpool thread. | |
153 void UnregisterThreadPoolWaits() { | |
154 UnregisterWaitEx(dump_request_thread_pool_wait_, INVALID_HANDLE_VALUE); | |
155 dump_request_thread_pool_wait_ = INVALID_HANDLE_VALUE; | |
156 UnregisterWaitEx(process_end_thread_pool_wait_, INVALID_HANDLE_VALUE); | |
157 process_end_thread_pool_wait_ = INVALID_HANDLE_VALUE; | |
158 } | |
159 | |
160 // These are only accessed on the main thread. | |
161 HANDLE dump_request_thread_pool_wait_; | |
162 HANDLE process_end_thread_pool_wait_; | |
163 | |
164 base::Lock lock_; | |
165 // Access to these fields must be guarded by lock_. | |
166 HANDLE port_; // weak | |
167 ExceptionHandlerServer::Delegate* delegate_; // weak | |
168 ScopedKernelHANDLE dump_requested_event_; | |
169 ScopedKernelHANDLE process_; | |
170 WinVMAddress exception_information_address_; | |
171 | |
172 DISALLOW_COPY_AND_ASSIGN(ClientData); | |
173 }; | |
174 | |
175 } // namespace internal | |
176 | |
177 ExceptionHandlerServer::Delegate::~Delegate() { | |
178 } | |
179 | |
180 ExceptionHandlerServer::ExceptionHandlerServer(Delegate* delegate) | |
181 : delegate_(delegate), | |
182 port_(CreateIoCompletionPort(INVALID_HANDLE_VALUE, nullptr, 0, 1)), | |
183 clients_lock_(), | |
184 clients_() { | |
185 } | |
186 | |
187 ExceptionHandlerServer::~ExceptionHandlerServer() { | |
188 } | |
189 | |
190 void ExceptionHandlerServer::Run(const std::string& pipe_name) { | |
191 // We create two pipe instances, so that there's one listening while the | |
192 // PipeServiceProc is processing a registration. | |
193 const int kPipeInstances = 2; | |
194 ScopedKernelHANDLE thread_handles[kPipeInstances]; | |
195 for (int i = 0; i < kPipeInstances; ++i) { | |
196 HANDLE pipe = | |
197 CreateNamedPipe(base::UTF8ToUTF16(pipe_name).c_str(), | |
Mark Mentovai
2015/08/26 21:40:28
UTF8ToUTF16 outside of the loop?
scottmg
2015/08/27 01:04:38
Done.
| |
198 PIPE_ACCESS_DUPLEX, | |
199 PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, | |
200 kPipeInstances, | |
201 512, | |
202 512, | |
203 0, | |
204 nullptr); | |
205 | |
206 // Ownership of this object (and the pipe instance) is given to the new | |
207 // thread. We close the thread handles at the end of the scope, but need not | |
208 // wait to join them. They clean up the context object and the pipe | |
Mark Mentovai
2015/08/26 21:40:28
“Need not wait to join them”—really? Doesn’t that
scottmg
2015/08/27 01:04:38
Done.
| |
209 // instance on termination. | |
210 PipeServiceContext* context = new PipeServiceContext( | |
211 port_.get(), pipe, delegate_, &clients_lock_, &clients_); | |
212 thread_handles[i].reset( | |
213 CreateThread(nullptr, 0, &PipeServiceProc, context, 0, nullptr)); | |
214 } | |
215 | |
216 delegate_->OnStarted(); | |
217 | |
218 // This is the main loop of the server. Most work is done on the threadpool, | |
219 // other than process end handing which is posted back to this main thread, as | |
Mark Mentovai
2015/08/26 21:40:28
process end handing?
scottmg
2015/08/27 01:04:38
Done.
| |
220 // we must unregister the threadpool waits here. | |
221 for (;;) { | |
222 OVERLAPPED* ov = nullptr; | |
223 ULONG_PTR key = 0; | |
224 DWORD bytes = 0; | |
225 GetQueuedCompletionStatus(port_.get(), &bytes, &key, &ov, INFINITE); | |
226 if (!key) { | |
227 // Shutting down. | |
228 break; | |
229 } | |
230 | |
231 // Otherwise, this is a request to unregister and destroy the given client. | |
232 // delete'ing the ClientData blocks in UnregisterWaitEx to ensure all | |
233 // outstanding threadpool waits are complete. This is important because the | |
234 // process handle can be signalled *before* the dump request is signalled. | |
235 internal::ClientData* client = reinterpret_cast<internal::ClientData*>(key); | |
236 { | |
237 base::AutoLock lock(clients_lock_); | |
238 clients_.erase(client); | |
239 } | |
240 delete client; | |
241 } | |
242 | |
243 // Signal to the named pipe instances that they should terminate. | |
244 for (int i = 0; i < kPipeInstances; ++i) { | |
245 RegistrationRequest shutdown_request = {0}; | |
246 shutdown_request.client_process_id = GetCurrentProcessId(); | |
247 RegistrationResponse response; | |
248 internal::RegisterWithCrashHandlerServer( | |
249 base::UTF8ToUTF16(pipe_name), shutdown_request, &response); | |
250 } | |
251 | |
252 // Deleting ClientData does a blocking wait until the threadpool executions | |
253 // have terminated when unregistering them. | |
254 { | |
255 base::AutoLock lock(clients_lock_); | |
256 for (auto* client : clients_) | |
257 delete client; | |
258 clients_.clear(); | |
259 } | |
260 | |
261 delegate_->OnShutdown(); | |
Mark Mentovai
2015/08/26 21:40:28
Since this happens as the last thing in Run(), is
scottmg
2015/08/27 01:04:37
Remove OnShutdown. Started is useful to know that
| |
262 } | |
263 | |
264 void ExceptionHandlerServer::Stop() { | |
265 // Post a null key (third argument) to trigger shutdown. | |
266 PostQueuedCompletionStatus(port_.get(), 0, 0, nullptr); | |
267 } | |
268 | |
269 // static | |
270 DWORD __stdcall ExceptionHandlerServer::PipeServiceProc(void* ctx) { | |
271 PipeServiceContext* service_context = | |
272 reinterpret_cast<PipeServiceContext*>(ctx); | |
273 | |
274 for (;;) { | |
275 // Connect can false if the client connects before we do. Continue and | |
Mark Mentovai
2015/08/26 21:40:28
What’s this comment mean?
scottmg
2015/08/27 01:04:37
Removed, and made it check for the condition the c
| |
276 // attempt a read anyway. | |
277 ConnectNamedPipe(service_context->pipe(), nullptr); | |
278 RegistrationRequest request; | |
279 if (!LoggingReadFile(service_context->pipe(), &request, sizeof(request))) | |
280 goto AbortAndResumeListening; | |
Mark Mentovai
2015/08/26 21:40:28
Can you restructure all of this to avoid “goto”? P
scottmg
2015/08/27 01:04:38
Done.
| |
281 | |
282 // Shutdown request. | |
283 if (request.client_process_id == GetCurrentProcessId()) { | |
Mark Mentovai
2015/08/26 21:40:28
This lets any evil client crash the party for ever
scottmg
2015/08/27 01:04:37
Not that I know of (a better way). AFAIK, we'd hav
scottmg
2015/08/27 04:18:00
WRT this first part, added a shared token so the r
| |
284 RegistrationResponse shutdown_response = {0}; | |
285 LoggingWriteFile(service_context->pipe(), | |
286 &shutdown_response, | |
287 sizeof(shutdown_response)); | |
288 break; | |
289 } | |
290 | |
291 decltype(GetNamedPipeClientProcessId)* get_named_pipe_client_process_id = | |
292 GetNamedPipeClientProcessIdFunction(); | |
293 if (get_named_pipe_client_process_id) { | |
294 // GetNamedPipeClientProcessId is only available on Vista+. | |
295 DWORD real_pid = 0; | |
296 if (get_named_pipe_client_process_id(service_context->pipe(), | |
297 &real_pid) && | |
298 request.client_process_id != real_pid) { | |
299 LOG(ERROR) << "forged client pid"; | |
Mark Mentovai
2015/08/26 21:40:28
Log the evil real pid too?
scottmg
2015/08/27 01:04:38
Done.
| |
300 goto AbortAndResumeListening; | |
301 } | |
302 } | |
303 | |
304 // We attempt to open the process as us. This is the main case that should | |
305 // almost always succeed as the server will generally be more privileged. If | |
306 // we're running as a different user, it may be that we will fail to open | |
307 // the process, but the client will be able to, so we make a second attempt | |
308 // having impersonated the client. | |
Mark Mentovai
2015/08/26 21:40:28
Can we have the client pass its own process handle
scottmg
2015/08/27 01:04:37
I have to defer to the Windows handle people again
| |
309 HANDLE client_process = | |
310 OpenProcess(PROCESS_ALL_ACCESS, false, request.client_process_id); | |
311 if (!client_process) { | |
312 if (!ImpersonateNamedPipeClient(service_context->pipe())) { | |
313 PLOG(ERROR) << "ImpersonateNamedPipeClient"; | |
314 goto AbortAndResumeListening; | |
315 } | |
316 HANDLE client_process = | |
317 OpenProcess(PROCESS_ALL_ACCESS, false, request.client_process_id); | |
318 RevertToSelf(); | |
Mark Mentovai
2015/08/26 21:40:28
On the fence about a scoper to ensure this happens
scottmg
2015/08/27 01:04:38
PCHECKd.
| |
319 if (!client_process) { | |
320 LOG(ERROR) << "failed to open " << request.client_process_id; | |
Mark Mentovai
2015/08/26 21:40:28
I’d say PLOG, but that intervening RevertToSelf()
scottmg
2015/08/27 01:04:37
Right.
| |
321 goto AbortAndResumeListening; | |
322 } | |
323 } | |
324 | |
325 internal::ClientData* client; | |
326 { | |
327 base::AutoLock lock(*service_context->clients_lock()); | |
328 client = new internal::ClientData(service_context->port(), | |
329 service_context->delegate(), | |
330 ScopedKernelHANDLE(client_process), | |
331 request.exception_information, | |
332 &OnDumpEvent, | |
333 &OnProcessEnd); | |
334 service_context->clients()->insert(client); | |
335 } | |
336 | |
337 // Duplicate the events back to the client so they can request a dump. | |
338 RegistrationResponse response; | |
339 response.request_report_event = reinterpret_cast<uint32_t>( | |
340 DuplicateEvent(client->process(), client->dump_requested_event())); | |
341 | |
342 if (!LoggingWriteFile(service_context->pipe(), &response, sizeof(response))) | |
343 goto AbortAndResumeListening; | |
344 | |
345 AbortAndResumeListening: | |
346 DisconnectNamedPipe(service_context->pipe()); | |
347 } | |
348 | |
349 delete service_context; | |
350 | |
351 return 0; | |
352 } | |
353 | |
354 // static | |
355 void __stdcall ExceptionHandlerServer::OnDumpEvent(void* ctx, BOOLEAN) { | |
356 // This function is executed on the thread pool. | |
357 internal::ClientData* client = reinterpret_cast<internal::ClientData*>(ctx); | |
358 base::AutoLock lock(*client->lock()); | |
359 | |
360 // Capture the exception. | |
361 client->delegate()->OnException(client->process(), | |
362 client->exception_information_address()); | |
363 | |
364 // Terminate the client with a known exit code. | |
Mark Mentovai
2015/08/26 21:40:28
Is this what the system would do in the absence of
scottmg
2015/08/27 01:04:38
Yes, you're right. I was sort of thinking that thi
| |
365 const UINT kCrashExitCode = 0xffff7002; | |
366 TerminateProcess(client->process(), kCrashExitCode); | |
367 } | |
368 | |
369 // static | |
370 void __stdcall ExceptionHandlerServer::OnProcessEnd(void* ctx, BOOLEAN) { | |
371 // This function is executed on the thread pool. | |
372 internal::ClientData* client = reinterpret_cast<internal::ClientData*>(ctx); | |
373 base::AutoLock lock(*client->lock()); | |
374 | |
375 // Post back to the main thread to have it delete this client record. | |
376 PostQueuedCompletionStatus(client->port(), 0, ULONG_PTR(client), nullptr); | |
377 } | |
378 | |
379 } // namespace crashpad | |
OLD | NEW |