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 "handler/win/registration_server.h" | |
| 16 | |
| 17 #include <string.h> | |
| 18 #include <vector> | |
| 19 | |
| 20 #include "base/logging.h" | |
| 21 #include "base/strings/utf_string_conversions.h" | |
| 22 #include "client/registration_protocol_win.h" | |
| 23 #include "util/stdlib/pointer_container.h" | |
| 24 | |
| 25 namespace crashpad { | |
| 26 | |
| 27 namespace { | |
| 28 | |
| 29 // Invokes GetNamedPipeClientProcessId if available (Vista+). Returns true if | |
| 30 // the method is unavailable or completes successfully. Returns false if an | |
| 31 // error occurs. | |
| 32 // If the method is unavailable process_id will be set to 0. | |
| 33 bool TryGetNamedPipeClientProcessId(HANDLE pipe, DWORD* process_id) { | |
| 34 bool result = false; | |
| 35 | |
| 36 HMODULE kernel_dll = LoadLibrary(L"kernel32.dll"); | |
| 37 if (kernel_dll) { | |
| 38 decltype(GetNamedPipeClientProcessId)* proc = | |
| 39 reinterpret_cast<decltype(GetNamedPipeClientProcessId)*>( | |
| 40 GetProcAddress(kernel_dll, "GetNamedPipeClientProcessId")); | |
| 41 if (!proc) { | |
| 42 *process_id = 0; | |
| 43 result = true; | |
| 44 } else { | |
| 45 result = proc(pipe, process_id); | |
| 46 } | |
| 47 FreeLibrary(kernel_dll); | |
| 48 } | |
| 49 return result; | |
| 50 } | |
| 51 | |
| 52 // Implements the state and state transitions for a single pipe instance. | |
| 53 class PipeState { | |
| 54 public: | |
| 55 // Instantiates an instance that will operate on |pipe| and handle requests | |
| 56 // using |delegate|. The client must call Start() before clients may connect | |
| 57 // to this pipe. | |
| 58 PipeState(ScopedFileHANDLE pipe, RegistrationServer::Delegate* delegate); | |
| 59 ~PipeState(); | |
| 60 | |
| 61 // Places the pipe in the running state, ready to receive and process client | |
| 62 // connections. Returns true if successful, in which case the client must | |
| 63 // observe completion_event() and invoke OnCompletion() whenever it is | |
| 64 // signaled. | |
| 65 // Before destroying a running PipeState instance you must invoke Stop() and | |
| 66 // then wait for completion_event() to be signaled one last time. | |
| 67 bool Start(); | |
|
scottmg
2015/05/21 02:32:36
Consider if you prefer Initialize() for this funct
erikwright (departed)
2015/05/21 15:12:38
Done.
| |
| 68 | |
| 69 // Cancels any pending asynchronous operations. After invoking this method you | |
| 70 // must wait for completion_event() to be signaled before destroying the | |
| 71 // instance. | |
| 72 void Stop(); | |
| 73 | |
| 74 // Returns an event handle that will be signaled whenever an asynchronous | |
| 75 // operation associated with this instance completes. | |
| 76 HANDLE completion_event() { return event_.get(); } | |
| 77 | |
| 78 // Must be called by the client whenever completion_event() is signaled. | |
| 79 // Returns true if the pipe is still in the running state. Otherwise, a | |
| 80 // permanent failure has occurred and the instance may be immediately | |
| 81 // destroyed. | |
| 82 bool OnCompletion(); | |
| 83 | |
| 84 private: | |
| 85 typedef bool (PipeState::*AsyncCompletionHandler)(DWORD bytes_transferred); | |
| 86 | |
| 87 // State transition handlers. Return true if the pipe is still valid. | |
| 88 bool OnConnectComplete(DWORD /* bytes_transferred */); | |
| 89 bool OnReadComplete(DWORD bytes_transferred); | |
| 90 bool OnWriteComplete(DWORD bytes_transferred); | |
| 91 | |
| 92 // Pipe operations. Return true if the pipe is still valid. | |
| 93 bool IssueConnect(); | |
| 94 bool IssueRead(); | |
| 95 bool IssueWrite(); | |
| 96 bool HandleRequest(); | |
| 97 bool ResetConnection(); | |
| 98 | |
| 99 RegistrationRequest request_; | |
| 100 RegistrationResponse response_; | |
| 101 // The state transition handler to be invoked when the active asynchronous | |
| 102 // operation completes. | |
| 103 AsyncCompletionHandler completion_handler_; | |
| 104 OVERLAPPED overlapped_; | |
| 105 ScopedKernelHANDLE event_; | |
| 106 ScopedFileHANDLE pipe_; | |
| 107 RegistrationServer::Delegate* delegate_; | |
| 108 | |
| 109 DISALLOW_COPY_AND_ASSIGN(PipeState); | |
| 110 }; | |
| 111 | |
| 112 PipeState::PipeState(ScopedFileHANDLE pipe, | |
| 113 RegistrationServer::Delegate* delegate) | |
| 114 : request_(), | |
| 115 response_(), | |
| 116 completion_handler_(nullptr), | |
| 117 overlapped_(), | |
| 118 event_(), | |
| 119 pipe_(pipe.Pass()), | |
| 120 delegate_(delegate) { | |
| 121 } | |
| 122 | |
| 123 PipeState::~PipeState() { | |
| 124 } | |
| 125 | |
| 126 bool PipeState::Start() { | |
| 127 DCHECK(!event_.is_valid()); | |
| 128 DCHECK(pipe_.is_valid()); | |
| 129 | |
| 130 event_.reset(CreateEvent(nullptr, true, false, nullptr)); | |
| 131 | |
| 132 if (!event_.is_valid()) { | |
| 133 PLOG(ERROR); | |
| 134 } else { | |
| 135 overlapped_.hEvent = event_.get(); | |
| 136 if (IssueConnect()) | |
| 137 return true; | |
| 138 } | |
| 139 | |
| 140 overlapped_.hEvent = nullptr; | |
| 141 event_.reset(); | |
| 142 pipe_.reset(); | |
| 143 completion_handler_ = nullptr; | |
| 144 | |
| 145 return false; | |
| 146 } | |
| 147 | |
| 148 void PipeState::Stop() { | |
| 149 DCHECK(pipe_.is_valid()); | |
| 150 if (!CancelIo(pipe_.get())) | |
| 151 PLOG(FATAL); | |
| 152 } | |
| 153 | |
| 154 bool PipeState::OnCompletion() { | |
| 155 AsyncCompletionHandler completion_handler = completion_handler_; | |
| 156 completion_handler_ = nullptr; | |
| 157 | |
| 158 DWORD bytes_transferred = 0; | |
| 159 BOOL success = GetOverlappedResult(pipe_.get(), | |
| 160 &overlapped_, | |
| 161 &bytes_transferred, | |
| 162 false); // do not wait | |
|
scottmg
2015/05/21 02:32:36
"Do not wait."
erikwright (departed)
2015/05/21 15:12:37
Done.
| |
| 163 ResetEvent(event_.get()); | |
| 164 bool still_running = false; | |
| 165 if (!completion_handler) { | |
| 166 NOTREACHED(); | |
| 167 still_running = ResetConnection(); | |
| 168 } else if (!success) { | |
| 169 PLOG(ERROR); | |
| 170 still_running = ResetConnection(); | |
| 171 } else { | |
| 172 still_running = (this->*completion_handler)(bytes_transferred); | |
| 173 } | |
| 174 | |
| 175 if (!still_running) { | |
| 176 overlapped_.hEvent = nullptr; | |
| 177 event_.reset(); | |
| 178 pipe_.reset(); | |
| 179 completion_handler_ = nullptr; | |
| 180 } else { | |
| 181 DCHECK(completion_handler_); | |
| 182 } | |
| 183 | |
| 184 return still_running; | |
| 185 } | |
| 186 | |
| 187 bool PipeState::OnConnectComplete(DWORD /* bytes_transferred */) { | |
| 188 return IssueRead(); | |
| 189 } | |
| 190 | |
| 191 bool PipeState::OnReadComplete(DWORD bytes_transferred) { | |
| 192 if (bytes_transferred != sizeof(request_)) { | |
| 193 LOG(ERROR) << "Invalid message size: " << bytes_transferred; | |
| 194 return ResetConnection(); | |
| 195 } else { | |
| 196 return HandleRequest(); | |
| 197 } | |
| 198 } | |
| 199 | |
| 200 bool PipeState::OnWriteComplete(DWORD bytes_transferred) { | |
| 201 if (bytes_transferred != sizeof(response_)) { | |
| 202 LOG(ERROR) << "Incomplete write operation. Bytes written: " | |
| 203 << bytes_transferred; | |
| 204 } | |
| 205 return ResetConnection(); | |
| 206 } | |
| 207 | |
| 208 bool PipeState::IssueConnect() { | |
| 209 if (ConnectNamedPipe(pipe_.get(), &overlapped_)) { | |
| 210 return OnConnectComplete(0); // bytes_transferred (ignored) | |
| 211 } else { | |
| 212 DWORD result = GetLastError(); | |
| 213 if (result == ERROR_PIPE_CONNECTED) { | |
| 214 return OnConnectComplete(0); // bytes_transferred (ignored) | |
| 215 } else if (result == ERROR_IO_PENDING) { | |
| 216 completion_handler_ = &PipeState::OnConnectComplete; | |
| 217 return true; | |
| 218 } else { | |
| 219 PLOG(ERROR); | |
| 220 return false; | |
| 221 } | |
| 222 } | |
| 223 } | |
| 224 | |
| 225 bool PipeState::IssueRead() { | |
| 226 DWORD bytes_read = 0; | |
| 227 if (ReadFile(pipe_.get(), | |
| 228 &request_, | |
| 229 sizeof(request_), | |
| 230 &bytes_read, | |
| 231 &overlapped_)) { | |
| 232 return OnReadComplete(bytes_read); | |
| 233 } else if (GetLastError() == ERROR_IO_PENDING) { | |
| 234 completion_handler_ = &PipeState::OnReadComplete; | |
| 235 return true; | |
| 236 } else { | |
| 237 PLOG(ERROR); | |
| 238 return ResetConnection(); | |
| 239 } | |
| 240 } | |
| 241 | |
| 242 bool PipeState::IssueWrite() { | |
| 243 DWORD bytes_written = 0; | |
| 244 if (WriteFile(pipe_.get(), | |
| 245 &response_, | |
| 246 sizeof(response_), | |
| 247 &bytes_written, | |
| 248 &overlapped_)) { | |
| 249 return OnWriteComplete(bytes_written); | |
| 250 } else if (GetLastError() == ERROR_IO_PENDING) { | |
| 251 completion_handler_ = &PipeState::OnWriteComplete; | |
| 252 return true; | |
| 253 } else { | |
| 254 PLOG(ERROR); | |
| 255 return ResetConnection(); | |
| 256 } | |
| 257 } | |
| 258 | |
| 259 bool PipeState::HandleRequest() { | |
| 260 // On Vista+ we can verify that the client is who they claim to be, thus | |
| 261 // preventing arbitrary processes from having us duplicate handles into other | |
| 262 // processes. | |
| 263 DWORD real_client_process_id = 0; | |
| 264 if (TryGetNamedPipeClientProcessId(pipe_.get(), &real_client_process_id)) { | |
| 265 if (real_client_process_id != 0 && | |
| 266 real_client_process_id != request_.client_process_id) { | |
| 267 LOG(ERROR) << "Client process ID from request (" | |
| 268 << request_.client_process_id | |
| 269 << ") does not match pipe client process ID (" | |
| 270 << real_client_process_id << ")."; | |
| 271 return ResetConnection(); | |
| 272 } | |
| 273 } | |
| 274 | |
| 275 ScopedKernelHANDLE client_process( | |
| 276 OpenProcess(PROCESS_ALL_ACCESS, false, request_.client_process_id)); | |
| 277 if (!client_process.is_valid()) { | |
| 278 if (ImpersonateNamedPipeClient(pipe_.get())) { | |
| 279 client_process.reset( | |
| 280 OpenProcess(PROCESS_ALL_ACCESS, false, request_.client_process_id)); | |
| 281 RevertToSelf(); | |
| 282 } | |
| 283 } | |
| 284 | |
| 285 if (!client_process.is_valid()) { | |
| 286 LOG(ERROR) << "Failed to open client process."; | |
| 287 return ResetConnection(); | |
| 288 } | |
| 289 | |
| 290 memset(&response_, 0, sizeof(response_)); | |
| 291 | |
| 292 // Go through these temporaries to deal with the fact that | |
| 293 // RegistrationResponse members may not be aligned (which complicates passing | |
| 294 // pointers to them to RegisterClient). | |
|
scottmg
2015/05/21 02:32:36
Good thought. I'm not sure it's necessary in a win
erikwright (departed)
2015/05/21 15:12:37
I get compilation warnings if I pass the unaligned
scottmg
2015/05/21 16:17:35
Interesting, I didn't know that! What you have see
| |
| 295 HANDLE request_report_event = nullptr; | |
| 296 HANDLE report_complete_event = nullptr; | |
| 297 | |
| 298 if (!delegate_->RegisterClient(client_process.Pass(), | |
| 299 request_.crashpad_info_address, | |
| 300 &request_report_event, | |
| 301 &report_complete_event)) { | |
| 302 return ResetConnection(); | |
| 303 } | |
| 304 response_.request_report_event = request_report_event; | |
| 305 response_.report_complete_event = report_complete_event; | |
| 306 return IssueWrite(); | |
| 307 } | |
| 308 | |
| 309 bool PipeState::ResetConnection() { | |
| 310 memset(&request_, 0, sizeof(request_)); | |
| 311 | |
| 312 if (!DisconnectNamedPipe(pipe_.get())) { | |
| 313 PLOG(ERROR); | |
| 314 return false; | |
| 315 } else { | |
| 316 return IssueConnect(); | |
| 317 } | |
| 318 } | |
| 319 | |
| 320 } // namespace | |
| 321 | |
| 322 RegistrationServer::RegistrationServer() : stop_event_() { | |
| 323 stop_event_.reset(CreateEvent(nullptr, false, false, nullptr)); | |
| 324 DPCHECK(stop_event_.is_valid()); | |
| 325 } | |
| 326 | |
| 327 RegistrationServer::~RegistrationServer() { | |
| 328 } | |
| 329 | |
| 330 void RegistrationServer::Run(const base::string16& pipe_name, | |
| 331 Delegate* delegate) { | |
| 332 PointerVector<PipeState> pipes; | |
| 333 std::vector<HANDLE> handles; | |
| 334 | |
| 335 const int kNumPipes = 3; | |
| 336 | |
| 337 // Create the named pipes. | |
| 338 for (int i = 0; i < kNumPipes; ++i) { | |
| 339 DWORD open_mode = PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED; | |
| 340 if (pipes.size() == 0) | |
| 341 open_mode |= FILE_FLAG_FIRST_PIPE_INSTANCE; | |
| 342 ScopedFileHANDLE pipe( | |
| 343 CreateNamedPipe(pipe_name.c_str(), | |
| 344 open_mode, | |
| 345 PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, | |
| 346 kNumPipes, | |
| 347 512, // nOutBufferSize | |
| 348 512, // nInBufferSize | |
| 349 20, // nDefaultTimeOut | |
| 350 nullptr)); // lpSecurityAttributes | |
| 351 if (pipe.is_valid()) { | |
| 352 scoped_ptr<PipeState> pipe_state(new PipeState(pipe.Pass(), delegate)); | |
| 353 if (pipe_state->Start()) { | |
| 354 pipes.push_back(pipe_state.release()); | |
| 355 handles.push_back(pipes.back()->completion_event()); | |
| 356 } | |
| 357 } else { | |
| 358 PLOG(ERROR); | |
| 359 } | |
| 360 } | |
| 361 | |
| 362 if (pipes.size() == 0) | |
| 363 return; | |
| 364 | |
| 365 delegate->OnStarted(); | |
| 366 | |
| 367 // Add stop_event_ to the list of events we will observe. | |
| 368 handles.push_back(stop_event_.get()); | |
| 369 | |
| 370 // Run the main loop, dispatching completion event signals to the pipe | |
| 371 // instances. | |
| 372 while (true) { | |
| 373 DWORD wait_result = WaitForMultipleObjects( | |
| 374 static_cast<DWORD>(handles.size()), handles.data(), false, INFINITE); | |
| 375 if (wait_result >= WAIT_OBJECT_0 && | |
| 376 wait_result < WAIT_OBJECT_0 + pipes.size()) { | |
| 377 int index = wait_result - WAIT_OBJECT_0; | |
| 378 // Handle a completion event. | |
| 379 if (!pipes[index]->OnCompletion()) { | |
| 380 pipes.erase(pipes.begin() + index); | |
| 381 handles.erase(handles.begin() + index); | |
| 382 } | |
| 383 if (pipes.size()) | |
| 384 continue; | |
| 385 // Exit due to all pipes having failed. | |
| 386 } else if (wait_result == WAIT_OBJECT_0 + pipes.size()) { | |
| 387 // Exit due to stop_event_. | |
| 388 } else if (wait_result == WAIT_FAILED) { | |
| 389 // Exit due to error. | |
| 390 PLOG(ERROR); | |
| 391 } else { | |
| 392 // Exit due to unexpected return code. | |
| 393 NOTREACHED(); | |
| 394 } | |
| 395 break; | |
| 396 } | |
| 397 | |
| 398 // Remove |stop_event_| from the wait list. | |
| 399 handles.pop_back(); | |
| 400 | |
| 401 // Cancel any ongoing asynchronous operations. | |
| 402 for (auto& pipe : pipes) { | |
| 403 pipe->Stop(); | |
| 404 } | |
| 405 | |
| 406 // Wait until all of the pipe instances are ready to be destroyed. | |
| 407 DWORD wait_result = WaitForMultipleObjects( | |
| 408 static_cast<DWORD>(handles.size()), handles.data(), true, INFINITE); | |
| 409 PCHECK(wait_result != WAIT_FAILED); | |
| 410 DCHECK_GE(wait_result, WAIT_OBJECT_0); | |
| 411 DCHECK_LT(wait_result, WAIT_OBJECT_0 + handles.size()); | |
| 412 } | |
| 413 | |
| 414 void RegistrationServer::Stop() { | |
| 415 if (!SetEvent(stop_event_.get())) | |
| 416 PLOG(FATAL); | |
| 417 } | |
| 418 | |
| 419 } // namespace crashpad | |
| OLD | NEW |