ipc_channel_win.cc (28247B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 // Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. 4 // Use of this source code is governed by a BSD-style license that can be 5 // found in the LICENSE file. 6 7 #include "chrome/common/ipc_channel_win.h" 8 9 #include <windows.h> 10 #include <winternl.h> 11 #include <ntstatus.h> 12 #include <sstream> 13 14 #include "base/command_line.h" 15 #include "base/logging.h" 16 #include "base/process.h" 17 #include "base/process_util.h" 18 #include "base/rand_util.h" 19 #include "base/string_util.h" 20 #include "base/win_util.h" 21 #include "chrome/common/chrome_switches.h" 22 #include "chrome/common/ipc_channel_utils.h" 23 #include "chrome/common/ipc_message_utils.h" 24 #include "mozilla/ipc/ProtocolUtils.h" 25 #include "mozilla/LateWriteChecks.h" 26 #include "mozilla/RandomNum.h" 27 #include "nsThreadUtils.h" 28 29 using namespace mozilla::ipc; 30 31 namespace { 32 33 // This logic is borrowed from Chromium's `base/win/win_util.h`. It allows us 34 // to distinguish pseudo-handle values, such as returned by GetCurrentProcess() 35 // (-1), GetCurrentThread() (-2), and potentially more. The code there claims 36 // that fuzzers have found issues up until -12 with DuplicateHandle. 37 // 38 // https://source.chromium.org/chromium/chromium/src/+/36dbbf38697dd1e23ef8944bb9e57f6e0b3d41ec:base/win/win_util.h 39 inline bool IsPseudoHandle(HANDLE handle) { 40 auto handleValue = static_cast<int32_t>(reinterpret_cast<uintptr_t>(handle)); 41 return -12 <= handleValue && handleValue < 0; 42 } 43 44 // A real handle is a handle that is not a pseudo-handle. Always preferably use 45 // this variant over ::DuplicateHandle. Only use stock ::DuplicateHandle if you 46 // explicitly need the ability to duplicate a pseudo-handle. 47 inline bool DuplicateRealHandle(HANDLE source_process, HANDLE source_handle, 48 HANDLE target_process, LPHANDLE target_handle, 49 DWORD desired_access, BOOL inherit_handle, 50 DWORD options) { 51 MOZ_RELEASE_ASSERT(!IsPseudoHandle(source_handle)); 52 return static_cast<bool>(::DuplicateHandle( 53 source_process, source_handle, target_process, target_handle, 54 desired_access, inherit_handle, options)); 55 } 56 57 } // namespace 58 59 namespace IPC { 60 //------------------------------------------------------------------------------ 61 62 ChannelWin::State::State(ChannelWin* channel) { 63 memset(&context.overlapped, 0, sizeof(context.overlapped)); 64 context.handler = channel; 65 } 66 67 ChannelWin::State::~State() { 68 COMPILE_ASSERT(!offsetof(ChannelWin::State, context), starts_with_io_context); 69 } 70 71 //------------------------------------------------------------------------------ 72 73 const Channel::ChannelKind ChannelWin::sKind{ 74 .create_raw_pipe = &ChannelWin::CreateRawPipe, 75 .num_relayed_attachments = &ChannelWin::NumRelayedAttachments, 76 .is_valid_handle = &ChannelWin::IsValidHandle, 77 }; 78 79 ChannelWin::ChannelWin(mozilla::UniqueFileHandle pipe, Mode mode, 80 base::ProcessId other_pid) 81 : input_state_(this), output_state_(this), other_pid_(other_pid) { 82 Init(mode); 83 MaybeOpenProcessHandle(); 84 85 if (!pipe) { 86 return; 87 } 88 89 pipe_ = pipe.release(); 90 EnqueueHelloMessage(); 91 } 92 93 void ChannelWin::Init(Mode mode) { 94 // Verify that we fit in a "quantum-spaced" jemalloc bucket. 95 static_assert(sizeof(*this) <= 512, "Exceeded expected size class"); 96 97 chan_cap_.NoteExclusiveAccess(); 98 99 mode_ = mode; 100 pipe_ = INVALID_HANDLE_VALUE; 101 waiting_connect_ = true; 102 processing_incoming_ = false; 103 input_buf_offset_ = 0; 104 input_buf_ = mozilla::MakeUnique<char[]>(Channel::kReadBufferSize); 105 other_process_ = INVALID_HANDLE_VALUE; 106 } 107 108 void ChannelWin::OutputQueuePush(mozilla::UniquePtr<Message> msg) { 109 chan_cap_.NoteLockHeld(); 110 111 mozilla::LogIPCMessage::LogDispatchWithPid(msg.get(), other_pid_); 112 113 output_queue_.Push(std::move(msg)); 114 } 115 116 void ChannelWin::OutputQueuePop() { 117 mozilla::UniquePtr<Message> message = output_queue_.Pop(); 118 } 119 120 void ChannelWin::Close() { 121 IOThread().AssertOnCurrentThread(); 122 mozilla::MutexAutoLock lock(SendMutex()); 123 CloseLocked(); 124 } 125 126 void ChannelWin::CloseLocked() { 127 chan_cap_.NoteExclusiveAccess(); 128 129 // If we still have pending I/O, cancel it. The references inside 130 // `input_state_` and `output_state_` will keep the buffers alive until they 131 // complete. 132 if (input_state_.is_pending || output_state_.is_pending) { 133 CancelIo(pipe_); 134 } 135 136 // Closing the handle at this point prevents us from issuing more requests 137 // form OnIOCompleted(). 138 if (pipe_ != INVALID_HANDLE_VALUE) { 139 CloseHandle(pipe_); 140 pipe_ = INVALID_HANDLE_VALUE; 141 } 142 143 // If we have a connection to the other process, close the handle. 144 if (other_process_ != INVALID_HANDLE_VALUE) { 145 CloseHandle(other_process_); 146 other_process_ = INVALID_HANDLE_VALUE; 147 } 148 149 // Don't return from `CloseLocked()` until the IO has been completed, 150 // otherwise the IO thread may exit with outstanding IO, leaking the 151 // ChannelWin. 152 // 153 // It's OK to unlock here, as calls to `Send` from other threads will be 154 // rejected, due to `pipe_` having been cleared. 155 while (input_state_.is_pending || output_state_.is_pending) { 156 mozilla::MutexAutoUnlock unlock(SendMutex()); 157 MessageLoopForIO::current()->WaitForIOCompletion(INFINITE, this); 158 } 159 160 while (!output_queue_.IsEmpty()) { 161 OutputQueuePop(); 162 } 163 } 164 165 bool ChannelWin::Send(mozilla::UniquePtr<Message> message) { 166 mozilla::MutexAutoLock lock(SendMutex()); 167 chan_cap_.NoteLockHeld(); 168 169 #ifdef IPC_MESSAGE_DEBUG_EXTRA 170 DLOG(INFO) << "sending message @" << message.get() << " on channel @" << this 171 << " with type " << message->type() << " (" 172 << output_queue_.Count() << " in queue)"; 173 #endif 174 175 if (pipe_ == INVALID_HANDLE_VALUE) { 176 if (mozilla::ipc::LoggingEnabled()) { 177 fprintf(stderr, 178 "Can't send message %s, because this channel is closed.\n", 179 message->name()); 180 } 181 return false; 182 } 183 184 OutputQueuePush(std::move(message)); 185 // ensure waiting to write 186 if (!waiting_connect_) { 187 if (!output_state_.is_pending) { 188 if (!ProcessOutgoingMessages(NULL, 0, false)) { 189 return false; 190 } 191 } 192 } 193 194 return true; 195 } 196 197 bool ChannelWin::EnqueueHelloMessage() { 198 chan_cap_.NoteExclusiveAccess(); 199 200 auto m = mozilla::MakeUnique<Message>(MSG_ROUTING_NONE, HELLO_MESSAGE_TYPE); 201 202 // Also, don't send if the value is zero (for IPC backwards compatability). 203 if (!m->WriteInt(GetCurrentProcessId())) { 204 CloseHandle(pipe_); 205 pipe_ = INVALID_HANDLE_VALUE; 206 return false; 207 } 208 209 OutputQueuePush(std::move(m)); 210 return true; 211 } 212 213 bool ChannelWin::Connect(Listener* listener) { 214 IOThread().AssertOnCurrentThread(); 215 mozilla::MutexAutoLock lock(SendMutex()); 216 chan_cap_.NoteExclusiveAccess(); 217 218 if (pipe_ == INVALID_HANDLE_VALUE) return false; 219 220 listener_ = listener; 221 222 MessageLoopForIO::current()->RegisterIOHandler(pipe_, this); 223 waiting_connect_ = false; 224 225 DCHECK(!input_state_.is_pending); 226 227 // Complete setup asynchronously. By not setting input_state_.is_pending 228 // to `this`, we indicate to OnIOCompleted that this is the special 229 // initialization signal, while keeping a reference through the 230 // `RunnableMethod`. 231 IOThread().Dispatch( 232 mozilla::NewRunnableMethod<MessageLoopForIO::IOContext*, DWORD, DWORD>( 233 "ContinueConnect", this, &ChannelWin::OnIOCompleted, 234 &input_state_.context, 0, 0)); 235 236 DCHECK(!output_state_.is_pending); 237 ProcessOutgoingMessages(NULL, 0, false); 238 return true; 239 } 240 241 void ChannelWin::SetOtherPid(base::ProcessId other_pid) { 242 IOThread().AssertOnCurrentThread(); 243 mozilla::MutexAutoLock lock(SendMutex()); 244 chan_cap_.NoteExclusiveAccess(); 245 MOZ_RELEASE_ASSERT( 246 other_pid_ == base::kInvalidProcessId || other_pid_ == other_pid, 247 "Multiple sources of SetOtherPid disagree!"); 248 other_pid_ = other_pid; 249 250 MaybeOpenProcessHandle(); 251 } 252 253 void ChannelWin::MaybeOpenProcessHandle() { 254 chan_cap_.NoteExclusiveAccess(); 255 256 // If we know the remote pid and are a broker server, open a privileged handle 257 // to the child process to transfer handles to/from it. 258 if (mode_ == MODE_BROKER_SERVER && other_process_ == INVALID_HANDLE_VALUE && 259 other_pid_ != base::kInvalidProcessId) { 260 other_process_ = OpenProcess(PROCESS_DUP_HANDLE, false, other_pid_); 261 if (!other_process_) { 262 other_process_ = INVALID_HANDLE_VALUE; 263 CHROMIUM_LOG(ERROR) << "Failed to acquire privileged handle to " 264 << other_pid_ << ", cannot accept handles"; 265 } 266 } 267 } 268 269 bool ChannelWin::ProcessIncomingMessages(MessageLoopForIO::IOContext* context, 270 DWORD bytes_read, bool was_pending) { 271 chan_cap_.NoteOnTarget(); 272 273 DCHECK(!input_state_.is_pending); 274 275 if (was_pending) { 276 DCHECK(context); 277 278 if (!context || !bytes_read) return false; 279 } else { 280 // This happens at channel initialization. 281 DCHECK(!bytes_read && context == &input_state_.context); 282 } 283 284 for (;;) { 285 if (bytes_read == 0) { 286 if (INVALID_HANDLE_VALUE == pipe_) return false; 287 288 // Read from pipe... 289 BOOL ok = ReadFile(pipe_, input_buf_.get() + input_buf_offset_, 290 Channel::kReadBufferSize - input_buf_offset_, 291 &bytes_read, &input_state_.context.overlapped); 292 if (!ok) { 293 DWORD err = GetLastError(); 294 if (err == ERROR_IO_PENDING) { 295 input_state_.is_pending = this; 296 return true; 297 } 298 if (err != ERROR_BROKEN_PIPE && err != ERROR_NO_DATA) { 299 CHROMIUM_LOG(ERROR) 300 << "pipe error in connection to " << other_pid_ << ": " << err; 301 } 302 return false; 303 } 304 input_state_.is_pending = this; 305 return true; 306 } 307 DCHECK(bytes_read); 308 309 // Process messages from input buffer. 310 311 const char* p = input_buf_.get(); 312 const char* end = input_buf_.get() + input_buf_offset_ + bytes_read; 313 314 // NOTE: We re-check `pipe_` after each message to make sure we weren't 315 // closed while calling `OnMessageReceived` or `OnChannelConnected`. 316 while (p < end && INVALID_HANDLE_VALUE != pipe_) { 317 // Try to figure out how big the message is. Size is 0 if we haven't read 318 // enough of the header to know the size. 319 uint32_t message_length = 0; 320 if (incoming_message_) { 321 message_length = incoming_message_->size(); 322 } else { 323 message_length = Message::MessageSize(p, end); 324 } 325 326 if (!message_length) { 327 // We haven't seen the full message header. 328 MOZ_ASSERT(!incoming_message_); 329 330 // Move everything we have to the start of the buffer. We'll finish 331 // reading this message when we get more data. For now we leave it in 332 // input_buf_. 333 memmove(input_buf_.get(), p, end - p); 334 input_buf_offset_ = end - p; 335 336 break; 337 } 338 339 input_buf_offset_ = 0; 340 341 bool partial; 342 if (incoming_message_) { 343 // We already have some data for this message stored in 344 // incoming_message_. We want to append the new data there. 345 Message& m = *incoming_message_; 346 347 // How much data from this message remains to be added to 348 // incoming_message_? 349 MOZ_ASSERT(message_length > m.CurrentSize()); 350 uint32_t remaining = message_length - m.CurrentSize(); 351 352 // How much data from this message is stored in input_buf_? 353 uint32_t in_buf = std::min(remaining, uint32_t(end - p)); 354 355 m.InputBytes(p, in_buf); 356 p += in_buf; 357 358 // Are we done reading this message? 359 partial = in_buf != remaining; 360 } else { 361 // How much data from this message is stored in input_buf_? 362 uint32_t in_buf = std::min(message_length, uint32_t(end - p)); 363 364 incoming_message_ = mozilla::MakeUnique<Message>(p, in_buf); 365 p += in_buf; 366 367 // Are we done reading this message? 368 partial = in_buf != message_length; 369 } 370 371 if (partial) { 372 break; 373 } 374 375 Message& m = *incoming_message_; 376 377 // Note: We set other_pid_ below when we receive a Hello message (which 378 // has no routing ID), but we only emit a profiler marker for messages 379 // with a routing ID, so there's no conflict here. 380 AddIPCProfilerMarker(m, other_pid_, MessageDirection::eReceiving, 381 MessagePhase::TransferEnd); 382 383 #ifdef IPC_MESSAGE_DEBUG_EXTRA 384 DLOG(INFO) << "received message on channel @" << this << " with type " 385 << m.type(); 386 #endif 387 if (m.routing_id() == MSG_ROUTING_NONE && 388 m.type() == HELLO_MESSAGE_TYPE) { 389 // The Hello message contains the process id and must include the 390 // shared secret, if we are waiting for it. 391 MessageIterator it = MessageIterator(m); 392 int32_t other_pid = it.NextInt(); 393 SetOtherPid(other_pid); 394 395 listener_->OnChannelConnected(other_pid); 396 } else { 397 mozilla::LogIPCMessage::Run run(&m); 398 if (!AcceptHandles(m)) { 399 return false; 400 } 401 listener_->OnMessageReceived(std::move(incoming_message_)); 402 } 403 404 incoming_message_ = nullptr; 405 } 406 407 bytes_read = 0; // Get more data. 408 } 409 } 410 411 bool ChannelWin::ProcessOutgoingMessages(MessageLoopForIO::IOContext* context, 412 DWORD bytes_written, 413 bool was_pending) { 414 chan_cap_.NoteLockHeld(); 415 416 DCHECK(!output_state_.is_pending); 417 DCHECK(!waiting_connect_); // Why are we trying to send messages if there's 418 // no connection? 419 if (was_pending) { 420 DCHECK(context); 421 if (!context || bytes_written == 0) { 422 DWORD err = GetLastError(); 423 if (err != ERROR_BROKEN_PIPE && err != ERROR_NO_DATA) { 424 CHROMIUM_LOG(ERROR) 425 << "pipe error in connection to " << other_pid_ << ": " << err; 426 } 427 return false; 428 } 429 // Message was sent. 430 DCHECK(!output_queue_.IsEmpty()); 431 Message* m = output_queue_.FirstElement().get(); 432 433 MOZ_RELEASE_ASSERT(partial_write_iter_.isSome()); 434 Pickle::BufferList::IterImpl& iter = partial_write_iter_.ref(); 435 iter.Advance(m->Buffers(), bytes_written); 436 if (iter.Done()) { 437 AddIPCProfilerMarker(*m, other_pid_, MessageDirection::eSending, 438 MessagePhase::TransferEnd); 439 440 partial_write_iter_.reset(); 441 OutputQueuePop(); 442 // m has been destroyed, so clear the dangling reference. 443 m = nullptr; 444 } 445 } 446 447 if (output_queue_.IsEmpty()) return true; 448 449 if (INVALID_HANDLE_VALUE == pipe_) return false; 450 451 // Write to pipe... 452 Message* m = output_queue_.FirstElement().get(); 453 454 if (partial_write_iter_.isNothing()) { 455 AddIPCProfilerMarker(*m, other_pid_, MessageDirection::eSending, 456 MessagePhase::TransferStart); 457 if (!TransferHandles(*m)) { 458 return false; 459 } 460 Pickle::BufferList::IterImpl iter(m->Buffers()); 461 partial_write_iter_.emplace(iter); 462 } 463 464 Pickle::BufferList::IterImpl& iter = partial_write_iter_.ref(); 465 466 // Don't count this write for the purposes of late write checking. If this 467 // message results in a legitimate file write, that will show up when it 468 // happens. 469 mozilla::PushSuspendLateWriteChecks(); 470 BOOL ok = WriteFile(pipe_, iter.Data(), iter.RemainingInSegment(), 471 &bytes_written, &output_state_.context.overlapped); 472 mozilla::PopSuspendLateWriteChecks(); 473 474 if (!ok) { 475 DWORD err = GetLastError(); 476 if (err == ERROR_IO_PENDING) { 477 output_state_.is_pending = this; 478 479 #ifdef IPC_MESSAGE_DEBUG_EXTRA 480 DLOG(INFO) << "sent pending message @" << m << " on channel @" << this 481 << " with type " << m->type(); 482 #endif 483 484 return true; 485 } 486 if (err != ERROR_BROKEN_PIPE && err != ERROR_NO_DATA) { 487 CHROMIUM_LOG(ERROR) << "pipe error in connection to " << other_pid_ 488 << ": " << err; 489 } 490 return false; 491 } 492 493 #ifdef IPC_MESSAGE_DEBUG_EXTRA 494 DLOG(INFO) << "sent message @" << m << " on channel @" << this 495 << " with type " << m->type(); 496 #endif 497 498 output_state_.is_pending = this; 499 return true; 500 } 501 502 void ChannelWin::OnIOCompleted(MessageLoopForIO::IOContext* context, 503 DWORD bytes_transfered, DWORD error) { 504 // NOTE: In case the pending reference was the last reference, release it 505 // outside of the lock. 506 RefPtr<ChannelWin> was_pending; 507 508 IOThread().AssertOnCurrentThread(); 509 chan_cap_.NoteOnTarget(); 510 511 bool ok; 512 if (context == &input_state_.context) { 513 was_pending = input_state_.is_pending.forget(); 514 // we don't support recursion through OnMessageReceived yet! 515 DCHECK(!processing_incoming_); 516 processing_incoming_ = true; 517 ok = ProcessIncomingMessages(context, bytes_transfered, was_pending); 518 processing_incoming_ = false; 519 } else { 520 mozilla::MutexAutoLock lock(SendMutex()); 521 DCHECK(context == &output_state_.context); 522 was_pending = output_state_.is_pending.forget(); 523 ok = ProcessOutgoingMessages(context, bytes_transfered, was_pending); 524 } 525 if (!ok && INVALID_HANDLE_VALUE != pipe_) { 526 // We don't want to re-enter Close(). 527 Close(); 528 listener_->OnChannelError(); 529 } 530 } 531 532 // This logic is borrowed from Chromium's `base/win/nt_status.cc`, and is used 533 // to detect and silence DuplicateHandle errors caused due to the other process 534 // exiting. 535 // 536 // https://source.chromium.org/chromium/chromium/src/+/main:base/win/nt_status.cc;drc=e4622aaeccea84652488d1822c28c78b7115684f 537 static NTSTATUS GetLastNtStatus() { 538 using GetLastNtStatusFn = NTSTATUS NTAPI (*)(); 539 540 static constexpr const wchar_t kNtDllName[] = L"ntdll.dll"; 541 static constexpr const char kLastStatusFnName[] = "RtlGetLastNtStatus"; 542 543 // This is equivalent to calling NtCurrentTeb() and extracting 544 // LastStatusValue from the returned _TEB structure, except that the public 545 // _TEB struct definition does not actually specify the location of the 546 // LastStatusValue field. We avoid depending on such a definition by 547 // internally using RtlGetLastNtStatus() from ntdll.dll instead. 548 static auto* get_last_nt_status = reinterpret_cast<GetLastNtStatusFn>( 549 ::GetProcAddress(::GetModuleHandle(kNtDllName), kLastStatusFnName)); 550 return get_last_nt_status(); 551 } 552 553 // ERROR_ACCESS_DENIED may indicate that the remote process (which could be 554 // either the source or destination process here) is already terminated or has 555 // begun termination and therefore no longer has a handle table. We don't want 556 // these cases to crash because we know they happen in practice and are 557 // largely unavoidable. 558 // 559 // https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:mojo/core/platform_handle_in_transit.cc;l=47-53;drc=fdfd85f836e0e59c79ed9bf6d527a2b8f7fdeb6e 560 static bool WasOtherProcessExitingError(DWORD error) { 561 return error == ERROR_ACCESS_DENIED && 562 GetLastNtStatus() == STATUS_PROCESS_IS_TERMINATING; 563 } 564 565 static uint32_t HandleToUint32(HANDLE h) { 566 // Cast through uintptr_t and then unsigned int to make the truncation to 567 // 32 bits explicit. Handles are size of-pointer but are always 32-bit values. 568 // https://docs.microsoft.com/en-ca/windows/win32/winprog64/interprocess-communication 569 // says: 64-bit versions of Windows use 32-bit handles for interoperability. 570 return static_cast<uint32_t>(reinterpret_cast<uintptr_t>(h)); 571 } 572 573 static HANDLE Uint32ToHandle(uint32_t h) { 574 return reinterpret_cast<HANDLE>( 575 static_cast<uintptr_t>(static_cast<int32_t>(h))); 576 } 577 578 bool ChannelWin::AcceptHandles(Message& msg) { 579 chan_cap_.NoteOnTarget(); 580 581 MOZ_ASSERT(msg.num_handles() == 0); 582 583 uint32_t num_handles = msg.header()->num_handles; 584 if (num_handles == 0) { 585 return true; 586 } 587 588 // Read in the payload from the footer, truncating the message. 589 nsTArray<uint32_t> payload; 590 payload.AppendElements(num_handles); 591 if (!msg.ReadFooter(payload.Elements(), num_handles * sizeof(uint32_t), 592 /* truncate */ true)) { 593 CHROMIUM_LOG(ERROR) << "failed to read handle payload from message"; 594 return false; 595 } 596 msg.header()->num_handles = 0; 597 598 // Read in the handles themselves, transferring ownership as required. 599 nsTArray<mozilla::UniqueFileHandle> handles(num_handles); 600 for (uint32_t handleValue : payload) { 601 HANDLE ipc_handle = Uint32ToHandle(handleValue); 602 if (!ipc_handle || IsPseudoHandle(ipc_handle)) { 603 CHROMIUM_LOG(ERROR) 604 << "Attempt to accept invalid or null handle from process " 605 << other_pid_ << " for message " << msg.name() << " in AcceptHandles"; 606 return false; 607 } 608 609 // If we're the privileged process, the remote process will have leaked 610 // the sent handles in its local address space, and be relying on us to 611 // duplicate them, otherwise the remote privileged side will have 612 // transferred the handles to us already. 613 mozilla::UniqueFileHandle local_handle; 614 switch (mode_) { 615 case MODE_BROKER_SERVER: 616 MOZ_ASSERT(other_process_, "other_process_ cannot be null"); 617 if (other_process_ == INVALID_HANDLE_VALUE) { 618 CHROMIUM_LOG(ERROR) << "other_process_ is invalid in AcceptHandles"; 619 return false; 620 } 621 if (!DuplicateRealHandle( 622 other_process_, ipc_handle, GetCurrentProcess(), 623 mozilla::getter_Transfers(local_handle), 0, FALSE, 624 DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE)) { 625 DWORD err = GetLastError(); 626 // Don't log out a scary looking error if this failed due to the 627 // target process terminating. 628 if (!WasOtherProcessExitingError(err)) { 629 CHROMIUM_LOG(ERROR) 630 << "DuplicateHandle failed for handle " << ipc_handle 631 << " from process " << other_pid_ << " for message " 632 << msg.name() << " in AcceptHandles with error: " << err; 633 } 634 return false; 635 } 636 break; 637 case MODE_BROKER_CLIENT: 638 local_handle.reset(ipc_handle); 639 break; 640 default: 641 CHROMIUM_LOG(ERROR) << "invalid message: " << msg.name() 642 << ". channel is not configured to accept handles"; 643 return false; 644 } 645 646 MOZ_DIAGNOSTIC_ASSERT( 647 local_handle, "Accepting invalid or null handle from another process"); 648 649 // The handle is directly owned by this process now, and can be added to 650 // our `handles` array. 651 handles.AppendElement(std::move(local_handle)); 652 } 653 654 // We're done with the handle footer, truncate the message at that point. 655 msg.SetAttachedFileHandles(std::move(handles)); 656 MOZ_ASSERT(msg.num_handles() == num_handles); 657 return true; 658 } 659 660 bool ChannelWin::TransferHandles(Message& msg) { 661 chan_cap_.NoteLockHeld(); 662 663 MOZ_ASSERT(msg.header()->num_handles == 0); 664 665 uint32_t num_handles = msg.num_handles(); 666 if (num_handles == 0) { 667 return true; 668 } 669 670 #ifdef DEBUG 671 uint32_t handles_offset = msg.header()->payload_size; 672 #endif 673 674 nsTArray<uint32_t> payload(num_handles); 675 for (uint32_t i = 0; i < num_handles; ++i) { 676 // Take ownership of the handle. 677 mozilla::UniqueFileHandle local_handle = 678 std::move(msg.attached_handles_[i]); 679 if (!local_handle) { 680 CHROMIUM_LOG(ERROR) 681 << "Attempt to transfer invalid or null handle to process " 682 << other_pid_ << " for message " << msg.name() 683 << " in TransferHandles"; 684 return false; 685 } 686 687 // If we're the privileged process, transfer the HANDLE to our remote before 688 // sending the message. Otherwise, the remote privileged process will 689 // transfer the handle for us, so leak it. 690 HANDLE ipc_handle = NULL; 691 switch (mode_) { 692 case MODE_BROKER_SERVER: 693 MOZ_ASSERT(other_process_, "other_process_ cannot be null"); 694 if (other_process_ == INVALID_HANDLE_VALUE) { 695 CHROMIUM_LOG(ERROR) << "other_process_ is invalid in TransferHandles"; 696 return false; 697 } 698 if (!DuplicateRealHandle(GetCurrentProcess(), local_handle.get(), 699 other_process_, &ipc_handle, 0, FALSE, 700 DUPLICATE_SAME_ACCESS)) { 701 DWORD err = GetLastError(); 702 // Don't log out a scary looking error if this failed due to the 703 // target process terminating. 704 if (!WasOtherProcessExitingError(err)) { 705 CHROMIUM_LOG(ERROR) << "DuplicateHandle failed for handle " 706 << (HANDLE)local_handle.get() << " to process " 707 << other_pid_ << " for message " << msg.name() 708 << " in TransferHandles with error: " << err; 709 } 710 return false; 711 } 712 break; 713 case MODE_BROKER_CLIENT: 714 // Release ownership of the handle. It'll be closed when the parent 715 // process transfers it with DuplicateHandle in the remote privileged 716 // process. 717 ipc_handle = local_handle.release(); 718 break; 719 default: 720 CHROMIUM_LOG(ERROR) << "cannot send message: " << msg.name() 721 << ". channel is not configured to accept handles"; 722 return false; 723 } 724 725 MOZ_DIAGNOSTIC_ASSERT( 726 ipc_handle && ipc_handle != INVALID_HANDLE_VALUE, 727 "Transferring invalid or null handle to another process"); 728 729 payload.AppendElement(HandleToUint32(ipc_handle)); 730 } 731 msg.attached_handles_.Clear(); 732 733 msg.WriteFooter(payload.Elements(), payload.Length() * sizeof(uint32_t)); 734 msg.header()->num_handles = num_handles; 735 736 MOZ_ASSERT(msg.header()->payload_size == 737 handles_offset + (sizeof(uint32_t) * num_handles), 738 "Unexpected number of bytes written for handles footer?"); 739 return true; 740 } 741 742 // static 743 bool ChannelWin::CreateRawPipe(ChannelHandle* server, ChannelHandle* client) { 744 std::wstring pipe_name = 745 StringPrintf(L"\\\\.\\pipe\\gecko.%lu.%lu.%I64u", ::GetCurrentProcessId(), 746 ::GetCurrentThreadId(), mozilla::RandomUint64OrDie()); 747 const DWORD kOpenMode = 748 PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE; 749 const DWORD kPipeMode = PIPE_TYPE_BYTE | PIPE_READMODE_BYTE; 750 auto& serverHandle = server->emplace<mozilla::UniqueFileHandle>( 751 ::CreateNamedPipeW(pipe_name.c_str(), kOpenMode, kPipeMode, 752 1, // Max instances. 753 Channel::kReadBufferSize, // Output buffer size. 754 Channel::kReadBufferSize, // Input buffer size. 755 5000, // Timeout in ms. 756 nullptr)); // Default security descriptor. 757 if (!serverHandle) { 758 NS_WARNING( 759 nsPrintfCString("CreateNamedPipeW Failed %lu", ::GetLastError()).get()); 760 return false; 761 } 762 763 const DWORD kDesiredAccess = GENERIC_READ | GENERIC_WRITE; 764 // The SECURITY_ANONYMOUS flag means that the server side cannot impersonate 765 // the client, which is useful as both server & client may be unprivileged. 766 const DWORD kFlags = 767 SECURITY_SQOS_PRESENT | SECURITY_ANONYMOUS | FILE_FLAG_OVERLAPPED; 768 auto& clientHandle = client->emplace<mozilla::UniqueFileHandle>( 769 ::CreateFileW(pipe_name.c_str(), kDesiredAccess, 0, nullptr, 770 OPEN_EXISTING, kFlags, nullptr)); 771 if (!clientHandle) { 772 NS_WARNING( 773 nsPrintfCString("CreateFileW Failed %lu", ::GetLastError()).get()); 774 return false; 775 } 776 777 // Since a client has connected, ConnectNamedPipe() should return zero and 778 // GetLastError() should return ERROR_PIPE_CONNECTED. 779 if (::ConnectNamedPipe(serverHandle.get(), nullptr) || 780 ::GetLastError() != ERROR_PIPE_CONNECTED) { 781 NS_WARNING( 782 nsPrintfCString("ConnectNamedPipe Failed %lu", ::GetLastError()).get()); 783 return false; 784 } 785 return true; 786 } 787 788 // static 789 uint32_t ChannelWin::NumRelayedAttachments(const Message& message) { 790 return message.num_handles(); 791 } 792 793 // static 794 bool ChannelWin::IsValidHandle(const ChannelHandle& handle) { 795 const auto* fileHandle = std::get_if<mozilla::UniqueFileHandle>(&handle); 796 return fileHandle && *fileHandle; 797 } 798 799 } // namespace IPC