Chromium Code Reviews| 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 |