Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(181)

Side by Side Diff: util/win/exception_handler_server.cc

Issue 1301853002: win: Crash handler server (Closed) Base URL: https://chromium.googlesource.com/crashpad/crashpad@master
Patch Set: fixes and move some things around Created 5 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698