From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- ipc/chromium/src/chrome/common/child_process.cc | 29 + ipc/chromium/src/chrome/common/child_process.h | 45 + ipc/chromium/src/chrome/common/child_thread.cc | 48 + ipc/chromium/src/chrome/common/child_thread.h | 56 + ipc/chromium/src/chrome/common/chrome_switches.cc | 18 + ipc/chromium/src/chrome/common/chrome_switches.h | 22 + ipc/chromium/src/chrome/common/ipc_channel.h | 189 +++ .../src/chrome/common/ipc_channel_capability.h | 61 + .../src/chrome/common/ipc_channel_posix.cc | 1256 ++++++++++++++++++++ ipc/chromium/src/chrome/common/ipc_channel_posix.h | 203 ++++ .../src/chrome/common/ipc_channel_utils.cc | 58 + ipc/chromium/src/chrome/common/ipc_channel_utils.h | 21 + ipc/chromium/src/chrome/common/ipc_channel_win.cc | 814 +++++++++++++ ipc/chromium/src/chrome/common/ipc_channel_win.h | 167 +++ ipc/chromium/src/chrome/common/ipc_message.cc | 217 ++++ ipc/chromium/src/chrome/common/ipc_message.h | 424 +++++++ .../src/chrome/common/ipc_message_utils.cc | 105 ++ ipc/chromium/src/chrome/common/ipc_message_utils.h | 1092 +++++++++++++++++ ipc/chromium/src/chrome/common/mach_ipc_mac.cc | 93 ++ ipc/chromium/src/chrome/common/mach_ipc_mac.h | 40 + .../src/chrome/common/mach_message_source_mac.cc | 61 + .../src/chrome/common/mach_message_source_mac.h | 60 + ipc/chromium/src/chrome/common/process_watcher.h | 38 + .../chrome/common/process_watcher_posix_sigchld.cc | 257 ++++ .../src/chrome/common/process_watcher_win.cc | 252 ++++ 25 files changed, 5626 insertions(+) create mode 100644 ipc/chromium/src/chrome/common/child_process.cc create mode 100644 ipc/chromium/src/chrome/common/child_process.h create mode 100644 ipc/chromium/src/chrome/common/child_thread.cc create mode 100644 ipc/chromium/src/chrome/common/child_thread.h create mode 100644 ipc/chromium/src/chrome/common/chrome_switches.cc create mode 100644 ipc/chromium/src/chrome/common/chrome_switches.h create mode 100644 ipc/chromium/src/chrome/common/ipc_channel.h create mode 100644 ipc/chromium/src/chrome/common/ipc_channel_capability.h create mode 100644 ipc/chromium/src/chrome/common/ipc_channel_posix.cc create mode 100644 ipc/chromium/src/chrome/common/ipc_channel_posix.h create mode 100644 ipc/chromium/src/chrome/common/ipc_channel_utils.cc create mode 100644 ipc/chromium/src/chrome/common/ipc_channel_utils.h create mode 100644 ipc/chromium/src/chrome/common/ipc_channel_win.cc create mode 100644 ipc/chromium/src/chrome/common/ipc_channel_win.h create mode 100644 ipc/chromium/src/chrome/common/ipc_message.cc create mode 100644 ipc/chromium/src/chrome/common/ipc_message.h create mode 100644 ipc/chromium/src/chrome/common/ipc_message_utils.cc create mode 100644 ipc/chromium/src/chrome/common/ipc_message_utils.h create mode 100644 ipc/chromium/src/chrome/common/mach_ipc_mac.cc create mode 100644 ipc/chromium/src/chrome/common/mach_ipc_mac.h create mode 100644 ipc/chromium/src/chrome/common/mach_message_source_mac.cc create mode 100644 ipc/chromium/src/chrome/common/mach_message_source_mac.h create mode 100644 ipc/chromium/src/chrome/common/process_watcher.h create mode 100644 ipc/chromium/src/chrome/common/process_watcher_posix_sigchld.cc create mode 100644 ipc/chromium/src/chrome/common/process_watcher_win.cc (limited to 'ipc/chromium/src/chrome') diff --git a/ipc/chromium/src/chrome/common/child_process.cc b/ipc/chromium/src/chrome/common/child_process.cc new file mode 100644 index 0000000000..637564c30c --- /dev/null +++ b/ipc/chromium/src/chrome/common/child_process.cc @@ -0,0 +1,29 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/common/child_process.h" + +#include "base/basictypes.h" +#include "base/string_util.h" +#include "chrome/common/child_thread.h" + +ChildProcess* ChildProcess::child_process_; + +ChildProcess::ChildProcess(ChildThread* child_thread) + : child_thread_(child_thread) { + DCHECK(!child_process_); + child_process_ = this; + if (child_thread_.get()) // null in unittests. + child_thread_->Run(); +} + +ChildProcess::~ChildProcess() { + DCHECK(child_process_ == this); + + if (child_thread_.get()) child_thread_->Stop(); + + child_process_ = NULL; +} diff --git a/ipc/chromium/src/chrome/common/child_process.h b/ipc/chromium/src/chrome/common/child_process.h new file mode 100644 index 0000000000..7ce4cffa81 --- /dev/null +++ b/ipc/chromium/src/chrome/common/child_process.h @@ -0,0 +1,45 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_COMMON_CHILD_PROCESS_H__ +#define CHROME_COMMON_CHILD_PROCESS_H__ + +#include +#include +#include "base/basictypes.h" +#include "base/message_loop.h" +#include "base/waitable_event.h" +#include "mozilla/UniquePtr.h" + +class ChildThread; + +// Base class for child processes of the browser process (i.e. renderer and +// plugin host). This is a singleton object for each child process. +class ChildProcess { + public: + // Child processes should have an object that derives from this class. The + // constructor will return once ChildThread has started. + explicit ChildProcess(ChildThread* child_thread); + virtual ~ChildProcess(); + + // Getter for this process' main thread. + ChildThread* child_thread() { return child_thread_.get(); } + + // Getter for the one ChildProcess object for this process. + static ChildProcess* current() { return child_process_; } + + private: + // NOTE: make sure that child_thread_ is listed before shutdown_event_, since + // it depends on it (indirectly through IPC::SyncChannel). + mozilla::UniquePtr child_thread_; + + // The singleton instance for this process. + static ChildProcess* child_process_; + + DISALLOW_EVIL_CONSTRUCTORS(ChildProcess); +}; + +#endif // CHROME_COMMON_CHILD_PROCESS_H__ diff --git a/ipc/chromium/src/chrome/common/child_thread.cc b/ipc/chromium/src/chrome/common/child_thread.cc new file mode 100644 index 0000000000..3363d5729b --- /dev/null +++ b/ipc/chromium/src/chrome/common/child_thread.cc @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/common/child_thread.h" + +#include "chrome/common/child_process.h" +#include "mozilla/ipc/NodeController.h" + +ChildThread::ChildThread(Thread::Options options, base::ProcessId parent_pid) + : Thread("IPC I/O Child"), + owner_loop_(MessageLoop::current()), + options_(options), + parent_pid_(parent_pid) { + DCHECK(owner_loop_); +} + +ChildThread::~ChildThread() = default; + +bool ChildThread::Run() { + bool r = StartWithOptions(options_); + return r; +} + +ChildThread* ChildThread::current() { + return ChildProcess::current()->child_thread(); +} + +void ChildThread::Init() { + // Take ownership of the client channel handle which we inherited, and use it + // to start the initial IPC connection to the parent process. + IPC::Channel::ChannelHandle client_handle( + IPC::Channel::GetClientChannelHandle()); + auto channel = mozilla::MakeUnique( + std::move(client_handle), IPC::Channel::MODE_CLIENT, parent_pid_); +#if defined(XP_WIN) + channel->StartAcceptingHandles(IPC::Channel::MODE_CLIENT); +#elif defined(XP_DARWIN) + channel->StartAcceptingMachPorts(IPC::Channel::MODE_CLIENT); +#endif + + initial_port_ = mozilla::ipc::NodeController::InitChildProcess( + std::move(channel), parent_pid_); +} + +void ChildThread::CleanUp() { mozilla::ipc::NodeController::CleanUp(); } diff --git a/ipc/chromium/src/chrome/common/child_thread.h b/ipc/chromium/src/chrome/common/child_thread.h new file mode 100644 index 0000000000..847a995cae --- /dev/null +++ b/ipc/chromium/src/chrome/common/child_thread.h @@ -0,0 +1,56 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_COMMON_CHILD_THREAD_H_ +#define CHROME_COMMON_CHILD_THREAD_H_ + +#include "base/thread.h" +#include "chrome/common/ipc_channel.h" +#include "mojo/core/ports/port_ref.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/ipc/ScopedPort.h" + +class ResourceDispatcher; + +// Child processes's background thread should derive from this class. +class ChildThread : public base::Thread { + public: + // Creates the thread. + ChildThread(Thread::Options options, base::ProcessId parent_pid); + virtual ~ChildThread(); + + mozilla::ipc::ScopedPort TakeInitialPort() { + return std::move(initial_port_); + } + + protected: + friend class ChildProcess; + + // Starts the thread. + bool Run(); + + protected: + // Returns the one child thread. + static ChildThread* current(); + + // Thread implementation. + virtual void Init() override; + virtual void CleanUp() override; + + private: + // The message loop used to run tasks on the thread that started this thread. + MessageLoop* owner_loop_; + + mozilla::ipc::ScopedPort initial_port_; + + Thread::Options options_; + + base::ProcessId parent_pid_; + + DISALLOW_EVIL_CONSTRUCTORS(ChildThread); +}; + +#endif // CHROME_COMMON_CHILD_THREAD_H_ diff --git a/ipc/chromium/src/chrome/common/chrome_switches.cc b/ipc/chromium/src/chrome/common/chrome_switches.cc new file mode 100644 index 0000000000..185934c87d --- /dev/null +++ b/ipc/chromium/src/chrome/common/chrome_switches.cc @@ -0,0 +1,18 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/common/chrome_switches.h" + +namespace switches { + +// Can't find the switch you are looking for? try looking in +// base/base_switches.cc instead. + +// The value of this switch tells the child process which +// IPC channel the browser expects to use to communicate with it. +const wchar_t kProcessChannelID[] = L"channel"; + +} // namespace switches diff --git a/ipc/chromium/src/chrome/common/chrome_switches.h b/ipc/chromium/src/chrome/common/chrome_switches.h new file mode 100644 index 0000000000..b13a726980 --- /dev/null +++ b/ipc/chromium/src/chrome/common/chrome_switches.h @@ -0,0 +1,22 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// Defines all the command-line switches used by Chrome. + +#ifndef CHROME_COMMON_CHROME_SWITCHES_H__ +#define CHROME_COMMON_CHROME_SWITCHES_H__ + +#if defined(_MSC_VER) +# include +#endif + +namespace switches { + +extern const wchar_t kProcessChannelID[]; + +} // namespace switches + +#endif // CHROME_COMMON_CHROME_SWITCHES_H__ diff --git a/ipc/chromium/src/chrome/common/ipc_channel.h b/ipc/chromium/src/chrome/common/ipc_channel.h new file mode 100644 index 0000000000..5d342d7252 --- /dev/null +++ b/ipc/chromium/src/chrome/common/ipc_channel.h @@ -0,0 +1,189 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_COMMON_IPC_CHANNEL_H_ +#define CHROME_COMMON_IPC_CHANNEL_H_ + +#include +#include +#include "base/basictypes.h" +#include "base/process.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/UniquePtrExtensions.h" +#include "mozilla/WeakPtr.h" +#include "chrome/common/ipc_message.h" + +#ifdef XP_WIN +# include +#endif + +namespace IPC { + +class Message; +class MessageReader; +class MessageWriter; + +//------------------------------------------------------------------------------ + +class Channel { + // Security tests need access to the pipe handle. + friend class ChannelTest; + + public: + // For channels which are created after initialization, handles to the pipe + // endpoints may be passed around directly using IPC messages. + using ChannelHandle = mozilla::UniqueFileHandle; + + // Implemented by consumers of a Channel to receive messages. + // + // All listeners will only be called on the IO thread, and must be destroyed + // on the IO thread. + class Listener { + public: + virtual ~Listener() = default; + + // Called when a message is received. + virtual void OnMessageReceived(mozilla::UniquePtr message) = 0; + + // Called when the channel is connected and we have received the internal + // Hello message from the peer. + virtual void OnChannelConnected(base::ProcessId peer_pid) {} + + // Called when an error is detected that causes the channel to close. + // This method is not called when a channel is closed normally. + virtual void OnChannelError() {} + }; + + enum Mode { MODE_SERVER, MODE_CLIENT }; + + enum { + + // The maximum message size in bytes. Attempting to receive a + // message of this size or bigger results in a channel error. + // This is larger in fuzzing builds to allow the fuzzing of passing + // large data structures into DOM methods without crashing. +#ifndef FUZZING + kMaximumMessageSize = 256 * 1024 * 1024, +#else + kMaximumMessageSize = 1792 * 1024 * 1024, // 1.75GB +#endif + + // Amount of data to read at once from the pipe. + kReadBufferSize = 4 * 1024, + }; + + // Initialize a Channel. + // + // |pipe| identifies the pipe which will be used. It should have been created + // using CreateRawPipe(). + // |mode| specifies whether this channel is operating in server mode or client + // mode. One side of the connection should be the client, and the other should + // be the server. + // |other_pid| specifies the pid of the other side of this channel. This will + // be used for logging, and for transferring HANDLEs from a privileged process + // on Windows (if enabled). + // + // The Channel must be created and destroyed on the IO thread, and all + // methods, unless otherwise noted, are only safe to call on the I/O thread. + // + Channel(ChannelHandle pipe, Mode mode, base::ProcessId other_pid); + + ~Channel(); + + // Connect the pipe. On the server side, this will initiate + // waiting for connections. On the client, it attempts to + // connect to a pre-existing pipe. Note, calling Connect() + // will not block the calling thread and may complete + // asynchronously. + // + // |listener| will receive a callback on the current thread for each newly + // received message. + bool Connect(Listener* listener); + + // Close this Channel explicitly. May be called multiple times. + void Close(); + + // Send a message over the Channel to the listener on the other end. + // + // This method may be called from any thread, so long as the `Channel` is not + // destroyed before it returns. + // + // If you Send() a message on a Close()'d channel, we delete the message + // immediately. + bool Send(mozilla::UniquePtr message); + + // Explicitly set the pid expected for the other side of this channel. This + // will be used for logging, and on Windows may be used for transferring + // handles between processes. + // + // If it is set this way, the "hello" message will be checked to ensure that + // the same pid is reported. + void SetOtherPid(base::ProcessId other_pid); + + // IsClosed() is safe to call from any thread, but the value returned may + // be out of date. + bool IsClosed() const; + +#if defined(XP_DARWIN) + // Configure the mach task_t for the peer task. + void SetOtherMachTask(task_t task); + + // Tell this pipe to accept mach ports. Exactly one side of the IPC connection + // must be set as `MODE_SERVER` and that side will be responsible for + // transferring the rights between processes. + void StartAcceptingMachPorts(Mode mode); +#elif defined(XP_WIN) + // Tell this pipe to accept handles. Exactly one side of the IPC connection + // must be set as `MODE_SERVER`, and that side will be responsible for calling + // `DuplicateHandle` to transfer the handle between processes. + void StartAcceptingHandles(Mode mode); +#endif + +#if defined(MOZ_WIDGET_ANDROID) + // Used to set the first IPC file descriptor in the child process on Android. + // See ipc_channel_posix.cc for further details on how this is used. + static void SetClientChannelFd(int fd); +#endif // defined(MOZ_WIDGET_ANDROID) + + // Get the first IPC channel handle in the child process. This will have been + // set by SetClientChannelFd on Android, will be a constant on other unix + // platforms, or will have been passed on the command line on Windows. + static ChannelHandle::ElementType GetClientChannelHandle(); + + // Create a new pair of pipe endpoints which can be used to establish a + // native IPC::Channel connection. + static bool CreateRawPipe(ChannelHandle* server, ChannelHandle* client); + + private: + // PIMPL to which all channel calls are delegated. + class ChannelImpl; + RefPtr channel_impl_; + + enum { +#if defined(XP_DARWIN) + // If the channel receives a message that contains file descriptors, then + // it will reply back with this message, indicating that the message has + // been received. The sending channel can then close any descriptors that + // had been marked as auto_close. This works around a sendmsg() bug on BSD + // where the kernel can eagerly close file descriptors that are in message + // queues but not yet delivered. + RECEIVED_FDS_MESSAGE_TYPE = kuint16max - 1, +#endif + + // The Hello message is internal to the Channel class. It is sent + // by the peer when the channel is connected. The message contains + // just the process id (pid). The message has a special routing_id + // (MSG_ROUTING_NONE) and type (HELLO_MESSAGE_TYPE). + HELLO_MESSAGE_TYPE = kuint16max // Maximum value of message type + // (uint16_t), to avoid conflicting with + // normal message types, which are + // enumeration constants starting from 0. + }; +}; + +} // namespace IPC + +#endif // CHROME_COMMON_IPC_CHANNEL_H_ diff --git a/ipc/chromium/src/chrome/common/ipc_channel_capability.h b/ipc/chromium/src/chrome/common/ipc_channel_capability.h new file mode 100644 index 0000000000..c23f16e577 --- /dev/null +++ b/ipc/chromium/src/chrome/common/ipc_channel_capability.h @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef CHROME_COMMON_IPC_CHANNEL_CAPABILITY_H_ +#define CHROME_COMMON_IPC_CHANNEL_CAPABILITY_H_ + +#include "mozilla/ThreadSafety.h" +#include "mozilla/Mutex.h" +#include "mozilla/EventTargetCapability.h" +#include "nsISerialEventTarget.h" + +namespace IPC { + +// A thread-safety capability used in IPC channel implementations. Combines an +// EventTargetCapability and a Mutex to allow using each independently, as well +// as combined together. +// +// The ChannelCapability grants shared access if on the IOThread or if the send +// mutex is held, and only allows exclusive access if both on the IO thread and +// holding the send mutex. This is similar to a `MutexSingleWriter`, but more +// flexible due to providing access to each sub-capability. +class MOZ_CAPABILITY("channel cap") ChannelCapability { + public: + using Mutex = mozilla::Mutex; + using Thread = mozilla::EventTargetCapability; + + ChannelCapability(const char* mutex_name, nsISerialEventTarget* io_thread) + : send_mutex_(mutex_name), io_thread_(io_thread) {} + + const Thread& IOThread() const MOZ_RETURN_CAPABILITY(io_thread_) { + return io_thread_; + } + Mutex& SendMutex() MOZ_RETURN_CAPABILITY(send_mutex_) { return send_mutex_; } + + // Note that we're on the IO thread, and thus have shared access to values + // guarded by the channel capability for the thread-safety analysis. + void NoteOnIOThread() const MOZ_REQUIRES(io_thread_) + MOZ_ASSERT_SHARED_CAPABILITY(this) {} + + // Note that we're holding the send mutex, and thus have shared access to + // values guarded by the channel capability for the thread-safety analysis. + void NoteSendMutex() const MOZ_REQUIRES(send_mutex_) + MOZ_ASSERT_SHARED_CAPABILITY(this) {} + + // Note that we're holding the send mutex while on the IO thread, and thus + // have exclusive access to values guarded by the channel capability for the + // thread-safety analysis. + void NoteExclusiveAccess() const MOZ_REQUIRES(io_thread_, send_mutex_) + MOZ_ASSERT_CAPABILITY(this) {} + + private: + mozilla::Mutex send_mutex_; + mozilla::EventTargetCapability io_thread_; +}; + +} // namespace IPC + +#endif // CHROME_COMMON_IPC_CHANNEL_CAPABILITY_H_ diff --git a/ipc/chromium/src/chrome/common/ipc_channel_posix.cc b/ipc/chromium/src/chrome/common/ipc_channel_posix.cc new file mode 100644 index 0000000000..0b34af40b1 --- /dev/null +++ b/ipc/chromium/src/chrome/common/ipc_channel_posix.cc @@ -0,0 +1,1256 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/common/ipc_channel_posix.h" + +#include +#include +#include +#include "mozilla/Mutex.h" +#if defined(XP_DARWIN) +# include +# include +# include "mozilla/UniquePtrExtensions.h" +# include "chrome/common/mach_ipc_mac.h" +#endif +#if defined(XP_DARWIN) || defined(XP_NETBSD) +# include +#endif +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "base/command_line.h" +#include "base/eintr_wrapper.h" +#include "base/logging.h" +#include "base/process.h" +#include "base/process_util.h" +#include "base/string_util.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/ipc_channel_utils.h" +#include "chrome/common/ipc_message_utils.h" +#include "mozilla/ipc/Endpoint.h" +#include "mozilla/ipc/ProtocolUtils.h" +#include "mozilla/Atomics.h" +#include "mozilla/StaticMutex.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Unused.h" + +// Use OS specific iovec array limit where it's possible. +#if defined(IOV_MAX) +static const size_t kMaxIOVecSize = IOV_MAX; +#elif defined(ANDROID) +static const size_t kMaxIOVecSize = 256; +#else +static const size_t kMaxIOVecSize = 16; +#endif + +using namespace mozilla::ipc; + +namespace IPC { + +// IPC channels on Windows use named pipes (CreateNamedPipe()) with +// channel ids as the pipe names. Channels on POSIX use anonymous +// Unix domain sockets created via socketpair() as pipes. These don't +// quite line up. +// +// When creating a child subprocess, the parent side of the fork +// arranges it such that the initial control channel ends up on the +// magic file descriptor gClientChannelFd in the child. Future +// connections (file descriptors) can then be passed via that +// connection via sendmsg(). +// +// On Android, child processes are created as a service instead of +// forking the parent process. The Android Binder service is used to +// transport the IPC channel file descriptor to the child process. +// So rather than re-mapping the file descriptor to a known value, +// the received channel file descriptor is set by calling +// SetClientChannelFd before gecko has been initialized and started +// in the child process. + +//------------------------------------------------------------------------------ +namespace { + +// This is the file descriptor number that a client process expects to find its +// IPC socket. +static int gClientChannelFd = +#if defined(MOZ_WIDGET_ANDROID) + // On android the fd is set at the time of child creation. + -1 +#else + 3 +#endif // defined(MOZ_WIDGET_ANDROID) + ; + +//------------------------------------------------------------------------------ + +bool ErrorIsBrokenPipe(int err) { return err == EPIPE || err == ECONNRESET; } + +// Some Android ARM64 devices appear to have a bug where sendmsg +// sometimes returns 0xFFFFFFFF, which we're assuming is a -1 that was +// incorrectly truncated to 32-bit and then zero-extended. +// See bug 1660826 for details. +// +// This is a workaround to detect that value and replace it with -1 +// (and check that there really was an error), because the largest +// amount we'll ever write is Channel::kMaximumMessageSize (256MiB). +// +// The workaround is also enabled on x86_64 Android on debug builds, +// although the bug isn't known to manifest there, so that there will +// be some CI coverage of this code. + +static inline ssize_t corrected_sendmsg(int socket, + const struct msghdr* message, + int flags) { +#if defined(ANDROID) && \ + (defined(__aarch64__) || (defined(DEBUG) && defined(__x86_64__))) + static constexpr auto kBadValue = static_cast(0xFFFFFFFF); + static_assert(kBadValue > 0); + +# ifdef MOZ_DIAGNOSTIC_ASSERT_ENABLED + errno = 0; +# endif + ssize_t bytes_written = sendmsg(socket, message, flags); + if (bytes_written == kBadValue) { + MOZ_DIAGNOSTIC_ASSERT(errno != 0); + bytes_written = -1; + } + MOZ_DIAGNOSTIC_ASSERT(bytes_written < kBadValue); + return bytes_written; +#else + return sendmsg(socket, message, flags); +#endif +} + +} // namespace +//------------------------------------------------------------------------------ + +#if defined(MOZ_WIDGET_ANDROID) +void Channel::SetClientChannelFd(int fd) { gClientChannelFd = fd; } +#endif // defined(MOZ_WIDGET_ANDROID) + +int Channel::GetClientChannelHandle() { return gClientChannelFd; } + +Channel::ChannelImpl::ChannelImpl(ChannelHandle pipe, Mode mode, + base::ProcessId other_pid) + : chan_cap_("ChannelImpl::SendMutex", + MessageLoopForIO::current()->SerialEventTarget()), + other_pid_(other_pid) { + Init(mode); + SetPipe(pipe.release()); + + EnqueueHelloMessage(); +} + +void Channel::ChannelImpl::SetPipe(int fd) { + chan_cap_.NoteExclusiveAccess(); + + pipe_ = fd; + pipe_buf_len_ = 0; + if (fd >= 0) { + int buf_len; + socklen_t optlen = sizeof(buf_len); + if (getsockopt(fd, SOL_SOCKET, SO_SNDBUF, &buf_len, &optlen) != 0) { + CHROMIUM_LOG(WARNING) + << "Unable to determine pipe buffer size: " << strerror(errno); + return; + } + CHECK(optlen == sizeof(buf_len)); + CHECK(buf_len > 0); + pipe_buf_len_ = static_cast(buf_len); + } +} + +bool Channel::ChannelImpl::PipeBufHasSpaceAfter(size_t already_written) { + // If the OS didn't tell us the buffer size for some reason, then + // don't apply this limitation on the amount we try to write. + return pipe_buf_len_ == 0 || + static_cast(pipe_buf_len_) > already_written; +} + +void Channel::ChannelImpl::Init(Mode mode) { + // Verify that we fit in a "quantum-spaced" jemalloc bucket. + static_assert(sizeof(*this) <= 512, "Exceeded expected size class"); + + MOZ_RELEASE_ASSERT(kControlBufferHeaderSize >= CMSG_SPACE(0)); + MOZ_RELEASE_ASSERT(kControlBufferSize >= + CMSG_SPACE(sizeof(int) * kControlBufferMaxFds)); + + chan_cap_.NoteExclusiveAccess(); + + mode_ = mode; + is_blocked_on_write_ = false; + partial_write_.reset(); + input_buf_offset_ = 0; + input_buf_ = mozilla::MakeUnique(Channel::kReadBufferSize); + input_cmsg_buf_ = mozilla::MakeUnique(kControlBufferSize); + SetPipe(-1); + waiting_connect_ = true; +#if defined(XP_DARWIN) + last_pending_fd_id_ = 0; + other_task_ = nullptr; +#endif +} + +bool Channel::ChannelImpl::EnqueueHelloMessage() { + mozilla::UniquePtr msg( + new Message(MSG_ROUTING_NONE, HELLO_MESSAGE_TYPE)); + if (!msg->WriteInt(base::GetCurrentProcId())) { + CloseLocked(); + return false; + } + + OutputQueuePush(std::move(msg)); + return true; +} + +bool Channel::ChannelImpl::Connect(Listener* listener) { + IOThread().AssertOnCurrentThread(); + mozilla::MutexAutoLock lock(SendMutex()); + chan_cap_.NoteExclusiveAccess(); + + if (pipe_ == -1) { + return false; + } + + listener_ = listener; + + return ContinueConnect(); +} + +bool Channel::ChannelImpl::ContinueConnect() { + chan_cap_.NoteExclusiveAccess(); + MOZ_ASSERT(pipe_ != -1); + +#if defined(XP_DARWIN) + // If we're still waiting for our peer task to be provided, don't start + // listening yet. We'll start receiving messages once the task_t is set. + if (accept_mach_ports_ && privileged_ && !other_task_) { + MOZ_ASSERT(waiting_connect_); + return true; + } +#endif + + MessageLoopForIO::current()->WatchFileDescriptor( + pipe_, true, MessageLoopForIO::WATCH_READ, &read_watcher_, this); + waiting_connect_ = false; + + return ProcessOutgoingMessages(); +} + +void Channel::ChannelImpl::SetOtherPid(base::ProcessId other_pid) { + IOThread().AssertOnCurrentThread(); + mozilla::MutexAutoLock lock(SendMutex()); + chan_cap_.NoteExclusiveAccess(); + MOZ_RELEASE_ASSERT( + other_pid_ == base::kInvalidProcessId || other_pid_ == other_pid, + "Multiple sources of SetOtherPid disagree!"); + other_pid_ = other_pid; +} + +bool Channel::ChannelImpl::ProcessIncomingMessages() { + chan_cap_.NoteOnIOThread(); + + struct msghdr msg = {0}; + struct iovec iov; + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_control = input_cmsg_buf_.get(); + + for (;;) { + msg.msg_controllen = kControlBufferSize; + + if (pipe_ == -1) return false; + + // In some cases the beginning of a message will be stored in input_buf_. We + // don't want to overwrite that, so we store the new data after it. + iov.iov_base = input_buf_.get() + input_buf_offset_; + iov.iov_len = Channel::kReadBufferSize - input_buf_offset_; + + // Read from pipe. + // recvmsg() returns 0 if the connection has closed or EAGAIN if no data + // is waiting on the pipe. + ssize_t bytes_read = HANDLE_EINTR(recvmsg(pipe_, &msg, MSG_DONTWAIT)); + + if (bytes_read < 0) { + if (errno == EAGAIN) { + return true; + } else { + if (!ErrorIsBrokenPipe(errno)) { + CHROMIUM_LOG(ERROR) + << "pipe error (fd " << pipe_ << "): " << strerror(errno); + } + return false; + } + } else if (bytes_read == 0) { + // The pipe has closed... + Close(); + return false; + } + DCHECK(bytes_read); + + // a pointer to an array of |num_wire_fds| file descriptors from the read + const int* wire_fds = NULL; + unsigned num_wire_fds = 0; + + // walk the list of control messages and, if we find an array of file + // descriptors, save a pointer to the array + + // This next if statement is to work around an OSX issue where + // CMSG_FIRSTHDR will return non-NULL in the case that controllen == 0. + // Here's a test case: + // + // int main() { + // struct msghdr msg; + // msg.msg_control = &msg; + // msg.msg_controllen = 0; + // if (CMSG_FIRSTHDR(&msg)) + // printf("Bug found!\n"); + // } + if (msg.msg_controllen > 0) { + // On OSX, CMSG_FIRSTHDR doesn't handle the case where controllen is 0 + // and will return a pointer into nowhere. + for (struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msg); cmsg; + cmsg = CMSG_NXTHDR(&msg, cmsg)) { + if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) { + const unsigned payload_len = cmsg->cmsg_len - CMSG_LEN(0); + DCHECK(payload_len % sizeof(int) == 0); + wire_fds = reinterpret_cast(CMSG_DATA(cmsg)); + num_wire_fds = payload_len / 4; + + if (msg.msg_flags & MSG_CTRUNC) { + CHROMIUM_LOG(ERROR) + << "SCM_RIGHTS message was truncated" + << " cmsg_len:" << cmsg->cmsg_len << " fd:" << pipe_; + for (unsigned i = 0; i < num_wire_fds; ++i) + IGNORE_EINTR(close(wire_fds[i])); + return false; + } + break; + } + } + } + + // Process messages from input buffer. + const char* p = input_buf_.get(); + const char* end = input_buf_.get() + input_buf_offset_ + bytes_read; + + // A pointer to an array of |num_fds| file descriptors which includes any + // fds that have spilled over from a previous read. + const int* fds; + unsigned num_fds; + unsigned fds_i = 0; // the index of the first unused descriptor + + if (input_overflow_fds_.empty()) { + fds = wire_fds; + num_fds = num_wire_fds; + } else { + // This code may look like a no-op in the case where + // num_wire_fds == 0, but in fact: + // + // 1. wire_fds will be nullptr, so passing it to memcpy is + // undefined behavior according to the C standard, even though + // the memcpy length is 0. + // + // 2. prev_size will be an out-of-bounds index for + // input_overflow_fds_; this is undefined behavior according to + // the C++ standard, even though the element only has its + // pointer taken and isn't accessed (and the corresponding + // operation on a C array would be defined). + // + // UBSan makes #1 a fatal error, and assertions in libstdc++ do + // the same for #2 if enabled. + if (num_wire_fds > 0) { + const size_t prev_size = input_overflow_fds_.size(); + input_overflow_fds_.resize(prev_size + num_wire_fds); + memcpy(&input_overflow_fds_[prev_size], wire_fds, + num_wire_fds * sizeof(int)); + } + fds = &input_overflow_fds_[0]; + num_fds = input_overflow_fds_.size(); + } + + // The data for the message we're currently reading consists of any data + // stored in incoming_message_ followed by data in input_buf_ (followed by + // other messages). + + // NOTE: We re-check `pipe_` after each message to make sure we weren't + // closed while calling `OnMessageReceived` or `OnChannelConnected`. + while (p < end && pipe_ != -1) { + // Try to figure out how big the message is. Size is 0 if we haven't read + // enough of the header to know the size. + uint32_t message_length = 0; + if (incoming_message_) { + message_length = incoming_message_->size(); + } else { + message_length = Message::MessageSize(p, end); + } + + if (!message_length) { + // We haven't seen the full message header. + MOZ_ASSERT(!incoming_message_); + + // Move everything we have to the start of the buffer. We'll finish + // reading this message when we get more data. For now we leave it in + // input_buf_. + memmove(input_buf_.get(), p, end - p); + input_buf_offset_ = end - p; + + break; + } + + input_buf_offset_ = 0; + + bool partial; + if (incoming_message_) { + // We already have some data for this message stored in + // incoming_message_. We want to append the new data there. + Message& m = *incoming_message_; + + // How much data from this message remains to be added to + // incoming_message_? + MOZ_DIAGNOSTIC_ASSERT(message_length > m.CurrentSize()); + uint32_t remaining = message_length - m.CurrentSize(); + + // How much data from this message is stored in input_buf_? + uint32_t in_buf = std::min(remaining, uint32_t(end - p)); + + m.InputBytes(p, in_buf); + p += in_buf; + + // Are we done reading this message? + partial = in_buf != remaining; + } else { + // How much data from this message is stored in input_buf_? + uint32_t in_buf = std::min(message_length, uint32_t(end - p)); + + incoming_message_ = mozilla::MakeUnique(p, in_buf); + p += in_buf; + + // Are we done reading this message? + partial = in_buf != message_length; + } + + if (partial) { + break; + } + + Message& m = *incoming_message_; + + if (m.header()->num_handles) { + // the message has file descriptors + const char* error = NULL; + if (m.header()->num_handles > num_fds - fds_i) { + // the message has been completely received, but we didn't get + // enough file descriptors. + error = "Message needs unreceived descriptors"; + } + + if (m.header()->num_handles > + IPC::Message::MAX_DESCRIPTORS_PER_MESSAGE) { + // There are too many descriptors in this message + error = "Message requires an excessive number of descriptors"; + } + + if (error) { + CHROMIUM_LOG(WARNING) + << error << " channel:" << this << " message-type:" << m.type() + << " header()->num_handles:" << m.header()->num_handles + << " num_fds:" << num_fds << " fds_i:" << fds_i; + // close the existing file descriptors so that we don't leak them + for (unsigned i = fds_i; i < num_fds; ++i) + IGNORE_EINTR(close(fds[i])); + input_overflow_fds_.clear(); + // abort the connection + return false; + } + +#if defined(XP_DARWIN) + // Send a message to the other side, indicating that we are now + // responsible for closing the descriptor. + auto fdAck = mozilla::MakeUnique(MSG_ROUTING_NONE, + RECEIVED_FDS_MESSAGE_TYPE); + DCHECK(m.fd_cookie() != 0); + fdAck->set_fd_cookie(m.fd_cookie()); + { + mozilla::MutexAutoLock lock(SendMutex()); + OutputQueuePush(std::move(fdAck)); + } +#endif + + nsTArray handles(m.header()->num_handles); + for (unsigned end_i = fds_i + m.header()->num_handles; fds_i < end_i; + ++fds_i) { + handles.AppendElement(mozilla::UniqueFileHandle(fds[fds_i])); + } + m.SetAttachedFileHandles(std::move(handles)); + } + + // Note: We set other_pid_ below when we receive a Hello message (which + // has no routing ID), but we only emit a profiler marker for messages + // with a routing ID, so there's no conflict here. + AddIPCProfilerMarker(m, other_pid_, MessageDirection::eReceiving, + MessagePhase::TransferEnd); + +#ifdef IPC_MESSAGE_DEBUG_EXTRA + DLOG(INFO) << "received message on channel @" << this << " with type " + << m.type(); +#endif + + if (m.routing_id() == MSG_ROUTING_NONE && + m.type() == HELLO_MESSAGE_TYPE) { + // The Hello message contains only the process id. + int32_t other_pid = MessageIterator(m).NextInt(); + SetOtherPid(other_pid); + listener_->OnChannelConnected(other_pid); +#if defined(XP_DARWIN) + } else if (m.routing_id() == MSG_ROUTING_NONE && + m.type() == RECEIVED_FDS_MESSAGE_TYPE) { + DCHECK(m.fd_cookie() != 0); + CloseDescriptors(m.fd_cookie()); +#endif + } else { + mozilla::LogIPCMessage::Run run(&m); +#if defined(XP_DARWIN) + if (!AcceptMachPorts(m)) { + return false; + } +#endif + listener_->OnMessageReceived(std::move(incoming_message_)); + } + + incoming_message_ = nullptr; + } + + input_overflow_fds_ = std::vector(&fds[fds_i], &fds[num_fds]); + + // When the input data buffer is empty, the overflow fds should be too. If + // this is not the case, we probably have a rogue renderer which is trying + // to fill our descriptor table. + if (!incoming_message_ && input_buf_offset_ == 0 && + !input_overflow_fds_.empty()) { + // We close these descriptors in Close() + return false; + } + } +} + +bool Channel::ChannelImpl::ProcessOutgoingMessages() { + // NOTE: This method may be called on threads other than `IOThread()`. + chan_cap_.NoteSendMutex(); + + DCHECK(!waiting_connect_); // Why are we trying to send messages if there's + // no connection? + is_blocked_on_write_ = false; + + if (output_queue_.IsEmpty()) return true; + + if (pipe_ == -1) return false; + + // Write out all the messages we can till the write blocks or there are no + // more outgoing messages. + while (!output_queue_.IsEmpty()) { + Message* msg = output_queue_.FirstElement().get(); + + struct msghdr msgh = {0}; + + char cmsgBuf[kControlBufferSize]; + + if (partial_write_.isNothing()) { +#if defined(XP_DARWIN) + if (!TransferMachPorts(*msg)) { + return false; + } +#endif + + if (msg->attached_handles_.Length() > + IPC::Message::MAX_DESCRIPTORS_PER_MESSAGE) { + MOZ_DIAGNOSTIC_ASSERT(false, "Too many file descriptors!"); + CHROMIUM_LOG(FATAL) << "Too many file descriptors!"; + // This should not be reached. + return false; + } + + msg->header()->num_handles = msg->attached_handles_.Length(); +#if defined(XP_DARWIN) + if (!msg->attached_handles_.IsEmpty()) { + msg->set_fd_cookie(++last_pending_fd_id_); + } +#endif + + Pickle::BufferList::IterImpl iter(msg->Buffers()); + MOZ_DIAGNOSTIC_ASSERT(!iter.Done(), "empty message"); + partial_write_.emplace(PartialWrite{iter, msg->attached_handles_}); + + AddIPCProfilerMarker(*msg, other_pid_, MessageDirection::eSending, + MessagePhase::TransferStart); + } + + if (partial_write_->iter_.Done()) { + MOZ_DIAGNOSTIC_ASSERT(false, "partial_write_->iter_ should not be done"); + // report a send error to our caller, which will close the channel. + return false; + } + + // How much of this message have we written so far? + Pickle::BufferList::IterImpl iter = partial_write_->iter_; + auto handles = partial_write_->handles_; + + // Serialize attached file descriptors into the cmsg header. Only up to + // kControlBufferMaxFds can be serialized at once, so messages with more + // attachments must be sent over multiple `sendmsg` calls. + const size_t num_fds = std::min(handles.Length(), kControlBufferMaxFds); + size_t max_amt_to_write = iter.TotalBytesAvailable(msg->Buffers()); + if (num_fds > 0) { + msgh.msg_control = cmsgBuf; + msgh.msg_controllen = CMSG_LEN(sizeof(int) * num_fds); + struct cmsghdr* cmsg = CMSG_FIRSTHDR(&msgh); + cmsg->cmsg_level = SOL_SOCKET; + cmsg->cmsg_type = SCM_RIGHTS; + cmsg->cmsg_len = msgh.msg_controllen; + for (size_t i = 0; i < num_fds; ++i) { + reinterpret_cast(CMSG_DATA(cmsg))[i] = handles[i].get(); + } + + // Avoid writing one byte per remaining handle in excess of + // kControlBufferMaxFds. Each handle written will consume a minimum of 4 + // bytes in the message (to store it's index), so we can depend on there + // being enough data to send every handle. + size_t remaining = handles.Length() - num_fds; + MOZ_ASSERT(max_amt_to_write > remaining, + "must be at least one byte in the message for each handle"); + max_amt_to_write -= remaining; + } + + // Store remaining segments to write into iovec. + // + // Don't add more than kMaxIOVecSize iovecs so that we avoid + // OS-dependent limits. Also, stop adding iovecs if we've already + // prepared to write at least the full buffer size. + struct iovec iov[kMaxIOVecSize]; + size_t iov_count = 0; + size_t amt_to_write = 0; + while (!iter.Done() && iov_count < kMaxIOVecSize && + PipeBufHasSpaceAfter(amt_to_write) && + amt_to_write < max_amt_to_write) { + char* data = iter.Data(); + size_t size = + std::min(iter.RemainingInSegment(), max_amt_to_write - amt_to_write); + + iov[iov_count].iov_base = data; + iov[iov_count].iov_len = size; + iov_count++; + amt_to_write += size; + iter.Advance(msg->Buffers(), size); + } + MOZ_ASSERT(amt_to_write <= max_amt_to_write); + MOZ_ASSERT(amt_to_write > 0); + + const bool intentional_short_write = !iter.Done(); + msgh.msg_iov = iov; + msgh.msg_iovlen = iov_count; + + ssize_t bytes_written = + HANDLE_EINTR(corrected_sendmsg(pipe_, &msgh, MSG_DONTWAIT)); + + if (bytes_written < 0) { + switch (errno) { + case EAGAIN: + // Not an error; the sendmsg would have blocked, so return to the + // event loop and try again later. + break; +#if defined(XP_DARWIN) || defined(XP_NETBSD) + // (Note: this comment is copied from https://crrev.com/86c3d9ef4fdf6; + // see also bug 1142693 comment #73.) + // + // On OS X if sendmsg() is trying to send fds between processes and + // there isn't enough room in the output buffer to send the fd + // structure over atomically then EMSGSIZE is returned. The same + // applies to NetBSD as well. + // + // EMSGSIZE presents a problem since the system APIs can only call us + // when there's room in the socket buffer and not when there is + // "enough" room. + // + // The current behavior is to return to the event loop when EMSGSIZE + // is received and hopefull service another FD. This is however still + // technically a busy wait since the event loop will call us right + // back until the receiver has read enough data to allow passing the + // FD over atomically. + case EMSGSIZE: + // Because this is likely to result in a busy-wait, we'll try to make + // it easier for the receiver to make progress, but only if we're on + // the I/O thread already. + if (IOThread().IsOnCurrentThread()) { + sched_yield(); + } + break; +#endif + default: + if (!ErrorIsBrokenPipe(errno)) { + CHROMIUM_LOG(ERROR) << "pipe error: " << strerror(errno); + } + return false; + } + } + + if (intentional_short_write || + static_cast(bytes_written) != amt_to_write) { + // If write() fails with EAGAIN or EMSGSIZE then bytes_written will be -1. + if (bytes_written > 0) { + MOZ_DIAGNOSTIC_ASSERT(intentional_short_write || + static_cast(bytes_written) < + amt_to_write); + partial_write_->iter_.AdvanceAcrossSegments(msg->Buffers(), + bytes_written); + partial_write_->handles_ = handles.From(num_fds); + // We should not hit the end of the buffer. + MOZ_DIAGNOSTIC_ASSERT(!partial_write_->iter_.Done()); + } + + is_blocked_on_write_ = true; + if (IOThread().IsOnCurrentThread()) { + // If we're on the I/O thread already, tell libevent to call us back + // when things are unblocked. + MessageLoopForIO::current()->WatchFileDescriptor( + pipe_, + false, // One shot + MessageLoopForIO::WATCH_WRITE, &write_watcher_, this); + } else { + // Otherwise, emulate being called back from libevent on the I/O thread, + // which will re-try the write, and then potentially start watching if + // still necessary. + IOThread().Dispatch(mozilla::NewRunnableMethod( + "ChannelImpl::ContinueProcessOutgoing", this, + &ChannelImpl::OnFileCanWriteWithoutBlocking, -1)); + } + return true; + } else { + MOZ_ASSERT(partial_write_->handles_.Length() == num_fds, + "not all handles were sent"); + partial_write_.reset(); + +#if defined(XP_DARWIN) + if (!msg->attached_handles_.IsEmpty()) { + pending_fds_.push_back(PendingDescriptors{ + msg->fd_cookie(), std::move(msg->attached_handles_)}); + } +#else + if (bytes_written > 0) { + msg->attached_handles_.Clear(); + } +#endif + + // Message sent OK! + + AddIPCProfilerMarker(*msg, other_pid_, MessageDirection::eSending, + MessagePhase::TransferEnd); + +#ifdef IPC_MESSAGE_DEBUG_EXTRA + DLOG(INFO) << "sent message @" << msg << " on channel @" << this + << " with type " << msg->type(); +#endif + OutputQueuePop(); + // msg has been destroyed, so clear the dangling reference. + msg = nullptr; + } + } + return true; +} + +bool Channel::ChannelImpl::Send(mozilla::UniquePtr message) { + // NOTE: This method may be called on threads other than `IOThread()`. + mozilla::MutexAutoLock lock(SendMutex()); + chan_cap_.NoteSendMutex(); + +#ifdef IPC_MESSAGE_DEBUG_EXTRA + DLOG(INFO) << "sending message @" << message.get() << " on channel @" << this + << " with type " << message->type() << " (" + << output_queue_.Count() << " in queue)"; +#endif + + // If the channel has been closed, ProcessOutgoingMessages() is never going + // to pop anything off output_queue; output_queue will only get emptied when + // the channel is destructed. We might as well delete message now, instead + // of waiting for the channel to be destructed. + if (pipe_ == -1) { + if (mozilla::ipc::LoggingEnabled()) { + fprintf(stderr, + "Can't send message %s, because this channel is closed.\n", + message->name()); + } + return false; + } + + OutputQueuePush(std::move(message)); + if (!waiting_connect_) { + if (!is_blocked_on_write_) { + if (!ProcessOutgoingMessages()) return false; + } + } + + return true; +} + +// Called by libevent when we can read from th pipe without blocking. +void Channel::ChannelImpl::OnFileCanReadWithoutBlocking(int fd) { + IOThread().AssertOnCurrentThread(); + chan_cap_.NoteOnIOThread(); + + if (!waiting_connect_ && fd == pipe_ && pipe_ != -1) { + if (!ProcessIncomingMessages()) { + Close(); + listener_->OnChannelError(); + // The OnChannelError() call may delete this, so we need to exit now. + return; + } + } +} + +#if defined(XP_DARWIN) +void Channel::ChannelImpl::CloseDescriptors(uint32_t pending_fd_id) { + mozilla::MutexAutoLock lock(SendMutex()); + chan_cap_.NoteExclusiveAccess(); + + DCHECK(pending_fd_id != 0); + for (std::list::iterator i = pending_fds_.begin(); + i != pending_fds_.end(); i++) { + if ((*i).id == pending_fd_id) { + pending_fds_.erase(i); + return; + } + } + DCHECK(false) << "pending_fd_id not in our list!"; +} +#endif + +void Channel::ChannelImpl::OutputQueuePush(mozilla::UniquePtr msg) { + chan_cap_.NoteSendMutex(); + + mozilla::LogIPCMessage::LogDispatchWithPid(msg.get(), other_pid_); + + MOZ_DIAGNOSTIC_ASSERT(pipe_ != -1); + msg->AssertAsLargeAsHeader(); + output_queue_.Push(std::move(msg)); +} + +void Channel::ChannelImpl::OutputQueuePop() { + // Clear any reference to the front of output_queue_ before we destroy it. + partial_write_.reset(); + + mozilla::UniquePtr message = output_queue_.Pop(); +} + +// Called by libevent when we can write to the pipe without blocking. +void Channel::ChannelImpl::OnFileCanWriteWithoutBlocking(int fd) { + RefPtr grip(this); + IOThread().AssertOnCurrentThread(); + mozilla::ReleasableMutexAutoLock lock(SendMutex()); + chan_cap_.NoteExclusiveAccess(); + if (pipe_ != -1 && !ProcessOutgoingMessages()) { + CloseLocked(); + lock.Unlock(); + listener_->OnChannelError(); + } +} + +void Channel::ChannelImpl::Close() { + IOThread().AssertOnCurrentThread(); + mozilla::MutexAutoLock lock(SendMutex()); + CloseLocked(); +} + +void Channel::ChannelImpl::CloseLocked() { + chan_cap_.NoteExclusiveAccess(); + + // Close can be called multiple times, so we need to make sure we're + // idempotent. + + // Unregister libevent for the FIFO and close it. + read_watcher_.StopWatchingFileDescriptor(); + write_watcher_.StopWatchingFileDescriptor(); + if (pipe_ != -1) { + IGNORE_EINTR(close(pipe_)); + SetPipe(-1); + } + + while (!output_queue_.IsEmpty()) { + OutputQueuePop(); + } + + // Close any outstanding, received file descriptors + for (std::vector::iterator i = input_overflow_fds_.begin(); + i != input_overflow_fds_.end(); ++i) { + IGNORE_EINTR(close(*i)); + } + input_overflow_fds_.clear(); + +#if defined(XP_DARWIN) + pending_fds_.clear(); + + other_task_ = nullptr; +#endif +} + +#if defined(XP_DARWIN) +void Channel::ChannelImpl::SetOtherMachTask(task_t task) { + IOThread().AssertOnCurrentThread(); + mozilla::MutexAutoLock lock(SendMutex()); + chan_cap_.NoteExclusiveAccess(); + + if (NS_WARN_IF(pipe_ == -1)) { + return; + } + + MOZ_ASSERT(accept_mach_ports_ && privileged_ && waiting_connect_); + other_task_ = mozilla::RetainMachSendRight(task); + // Now that `other_task_` is provided, we can continue connecting. + ContinueConnect(); +} + +void Channel::ChannelImpl::StartAcceptingMachPorts(Mode mode) { + IOThread().AssertOnCurrentThread(); + mozilla::MutexAutoLock lock(SendMutex()); + chan_cap_.NoteExclusiveAccess(); + + if (accept_mach_ports_) { + MOZ_ASSERT(privileged_ == (MODE_SERVER == mode)); + return; + } + accept_mach_ports_ = true; + privileged_ = MODE_SERVER == mode; +} + +//------------------------------------------------------------------------------ +// Mach port transferring logic +// +// It is currently not possible to directly transfer a mach send right between +// two content processes using SCM_RIGHTS, unlike how we can handle file +// descriptors. This means that mach ports need to be transferred through a +// separate mechanism. This file only implements support for transferring mach +// ports between a (potentially sandboxed) child process and the parent process. +// Support for transferring mach ports between other process pairs is handled by +// `NodeController`, which is responsible for relaying messages which carry +// handles via the parent process. +// +// The logic which we use for doing this is based on the following from +// Chromium, which pioneered this technique. As of this writing, chromium no +// longer uses this strategy, as all IPC messages are sent using mach ports on +// macOS. +// https://source.chromium.org/chromium/chromium/src/+/9f707e5e04598d8303fa99ca29eb507c839767d8:mojo/core/mach_port_relay.cc +// https://source.chromium.org/chromium/chromium/src/+/9f707e5e04598d8303fa99ca29eb507c839767d8:base/mac/mach_port_util.cc. +// +// As we only need to consider messages between the privileged (parent) and +// unprivileged (child) processes in this code, there are 2 relevant cases which +// we need to handle: +// +// # Unprivileged (child) to Privileged (parent) +// +// As the privileged process has access to the unprivileged process' `task_t`, +// it is possible to directly extract the mach port from the target process' +// address space, given its name, using `mach_port_extract_right`. +// +// To transfer the port, the unprivileged process will leak a reference to the +// send right, and include the port's name in the message footer. The privileged +// process will extract that port right (and drop the reference in the old +// process) using `mach_port_extract_right` with `MACH_MSG_TYPE_MOVE_SEND`. The +// call to `mach_port_extract_right` is handled by `BrokerExtractSendRight` +// +// # Privileged (parent) to Unprivileged (child) +// +// Unfortunately, the process of transferring a right into a target process is +// more complex. The only well-supported way to transfer a right into a process +// is by sending it with `mach_msg`, and receiving it on the other side [1]. +// +// To work around this, the privileged process uses `mach_port_allocate` to +// create a new receive right in the target process using its `task_t`, and +// `mach_port_extract_right` to extract a send-once right to that port. It then +// sends a message to the port with port we're intending to send as an +// attachment. This is handled by `BrokerTransferSendRight`, which returns the +// name of the newly created receive right in the target process to be sent in +// the message footer. +// +// In the unprivileged process, `mach_msg` is used to receive a single message +// from the receive right, which will have the actual port we were trying to +// transfer as an attachment. This is handled by the `MachReceivePortSendRight` +// function. +// +// [1] We cannot use `mach_port_insert_right` to transfer the right into the +// target process, as that method requires explicitly specifying the remote +// port's name, and we do not control the port name allocator. + +// Extract a send right from the given peer task. A reference to the remote +// right will be dropped. See comment above for details. +static mozilla::UniqueMachSendRight BrokerExtractSendRight( + task_t task, mach_port_name_t name) { + mach_port_t extractedRight = MACH_PORT_NULL; + mach_msg_type_name_t extractedRightType; + kern_return_t kr = + mach_port_extract_right(task, name, MACH_MSG_TYPE_MOVE_SEND, + &extractedRight, &extractedRightType); + if (kr != KERN_SUCCESS) { + CHROMIUM_LOG(ERROR) << "failed to extract port right from other process. " + << mach_error_string(kr); + return nullptr; + } + MOZ_ASSERT(extractedRightType == MACH_MSG_TYPE_PORT_SEND, + "We asked the OS for a send port"); + return mozilla::UniqueMachSendRight(extractedRight); +} + +// Transfer a send right to the given peer task. The name of a receive right in +// the remote process will be returned if successful. The sent port can be +// obtained from that port in the peer task using `MachReceivePortSendRight`. +// See comment above for details. +static mozilla::Maybe BrokerTransferSendRight( + task_t task, mozilla::UniqueMachSendRight port_to_send) { + mach_port_name_t endpoint; + kern_return_t kr = + mach_port_allocate(task, MACH_PORT_RIGHT_RECEIVE, &endpoint); + if (kr != KERN_SUCCESS) { + CHROMIUM_LOG(ERROR) + << "Unable to create receive right in TransferMachPorts. " + << mach_error_string(kr); + return mozilla::Nothing(); + } + + // Clean up the endpoint on error. + auto destroyEndpoint = + mozilla::MakeScopeExit([&] { mach_port_deallocate(task, endpoint); }); + + // Change its message queue limit so that it accepts one message. + mach_port_limits limits = {}; + limits.mpl_qlimit = 1; + kr = mach_port_set_attributes(task, endpoint, MACH_PORT_LIMITS_INFO, + reinterpret_cast(&limits), + MACH_PORT_LIMITS_INFO_COUNT); + if (kr != KERN_SUCCESS) { + CHROMIUM_LOG(ERROR) + << "Unable configure receive right in TransferMachPorts. " + << mach_error_string(kr); + return mozilla::Nothing(); + } + + // Get a send right. + mach_port_t send_once_right; + mach_msg_type_name_t send_right_type; + kr = mach_port_extract_right(task, endpoint, MACH_MSG_TYPE_MAKE_SEND_ONCE, + &send_once_right, &send_right_type); + if (kr != KERN_SUCCESS) { + CHROMIUM_LOG(ERROR) << "Unable extract send right in TransferMachPorts. " + << mach_error_string(kr); + return mozilla::Nothing(); + } + MOZ_ASSERT(MACH_MSG_TYPE_PORT_SEND_ONCE == send_right_type); + + kr = MachSendPortSendRight(send_once_right, port_to_send.get(), + mozilla::Some(0), MACH_MSG_TYPE_MOVE_SEND_ONCE); + if (kr != KERN_SUCCESS) { + // This right will be destroyed due to being a SEND_ONCE right if we + // succeed. + mach_port_deallocate(mach_task_self(), send_once_right); + CHROMIUM_LOG(ERROR) << "Unable to transfer right in TransferMachPorts. " + << mach_error_string(kr); + return mozilla::Nothing(); + } + + destroyEndpoint.release(); + return mozilla::Some(endpoint); +} + +// Process footer information attached to the message, and acquire owning +// references to any transferred mach ports. See comment above for details. +bool Channel::ChannelImpl::AcceptMachPorts(Message& msg) { + chan_cap_.NoteOnIOThread(); + + uint32_t num_send_rights = msg.header()->num_send_rights; + if (num_send_rights == 0) { + return true; + } + + if (!accept_mach_ports_) { + CHROMIUM_LOG(ERROR) << "invalid message: " << msg.name() + << ". channel is not configured to accept mach ports"; + return false; + } + + // Read in the payload from the footer, truncating the message. + nsTArray payload; + payload.AppendElements(num_send_rights); + if (!msg.ReadFooter(payload.Elements(), num_send_rights * sizeof(uint32_t), + /* truncate */ true)) { + CHROMIUM_LOG(ERROR) << "failed to read mach port payload from message"; + return false; + } + msg.header()->num_send_rights = 0; + + // Read in the handles themselves, transferring ownership as required. + nsTArray rights(num_send_rights); + for (uint32_t name : payload) { + mozilla::UniqueMachSendRight right; + if (privileged_) { + if (!other_task_) { + CHROMIUM_LOG(ERROR) << "other_task_ is invalid in AcceptMachPorts"; + return false; + } + right = BrokerExtractSendRight(other_task_.get(), name); + } else { + kern_return_t kr = MachReceivePortSendRight( + mozilla::UniqueMachReceiveRight(name), mozilla::Some(0), &right); + if (kr != KERN_SUCCESS) { + CHROMIUM_LOG(ERROR) + << "failed to receive mach send right. " << mach_error_string(kr); + return false; + } + } + if (!right) { + return false; + } + rights.AppendElement(std::move(right)); + } + + // We're done with the handle footer, truncate the message at that point. + msg.attached_send_rights_ = std::move(rights); + MOZ_ASSERT(msg.num_send_rights() == num_send_rights); + return true; +} + +// Transfer ownership of any attached mach ports to the peer task, and add the +// required information for AcceptMachPorts to the message footer. See comment +// above for details. +bool Channel::ChannelImpl::TransferMachPorts(Message& msg) { + uint32_t num_send_rights = msg.num_send_rights(); + if (num_send_rights == 0) { + return true; + } + + if (!accept_mach_ports_) { + CHROMIUM_LOG(ERROR) << "cannot send message: " << msg.name() + << ". channel is not configured to accept mach ports"; + return false; + } + +# ifdef DEBUG + uint32_t rights_offset = msg.header()->payload_size; +# endif + + nsTArray payload(num_send_rights); + for (auto& port_to_send : msg.attached_send_rights_) { + if (privileged_) { + if (!other_task_) { + CHROMIUM_LOG(ERROR) << "other_task_ is invalid in TransferMachPorts"; + return false; + } + mozilla::Maybe endpoint = + BrokerTransferSendRight(other_task_.get(), std::move(port_to_send)); + if (!endpoint) { + return false; + } + payload.AppendElement(*endpoint); + } else { + payload.AppendElement(port_to_send.release()); + } + } + msg.attached_send_rights_.Clear(); + + msg.WriteFooter(payload.Elements(), payload.Length() * sizeof(uint32_t)); + msg.header()->num_send_rights = num_send_rights; + + MOZ_ASSERT(msg.header()->payload_size == + rights_offset + (sizeof(uint32_t) * num_send_rights), + "Unexpected number of bytes written for send rights footer?"); + return true; +} +#endif + +//------------------------------------------------------------------------------ +// Channel's methods simply call through to ChannelImpl. +Channel::Channel(ChannelHandle pipe, Mode mode, base::ProcessId other_pid) + : channel_impl_(new ChannelImpl(std::move(pipe), mode, other_pid)) { + MOZ_COUNT_CTOR(IPC::Channel); +} + +Channel::~Channel() { MOZ_COUNT_DTOR(IPC::Channel); } + +bool Channel::Connect(Listener* listener) { + return channel_impl_->Connect(listener); +} + +void Channel::Close() { channel_impl_->Close(); } + +bool Channel::Send(mozilla::UniquePtr message) { + return channel_impl_->Send(std::move(message)); +} + +void Channel::SetOtherPid(base::ProcessId other_pid) { + channel_impl_->SetOtherPid(other_pid); +} + +bool Channel::IsClosed() const { return channel_impl_->IsClosed(); } + +#if defined(XP_DARWIN) +void Channel::SetOtherMachTask(task_t task) { + channel_impl_->SetOtherMachTask(task); +} + +void Channel::StartAcceptingMachPorts(Mode mode) { + channel_impl_->StartAcceptingMachPorts(mode); +} +#endif + +// static +bool Channel::CreateRawPipe(ChannelHandle* server, ChannelHandle* client) { + int fds[2]; + if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) < 0) { + mozilla::ipc::AnnotateCrashReportWithErrno( + CrashReporter::Annotation::IpcCreatePipeSocketPairErrno, errno); + return false; + } + + auto configureFd = [](int fd) -> bool { + // Mark the endpoints as non-blocking + if (fcntl(fd, F_SETFL, O_NONBLOCK) == -1) { + mozilla::ipc::AnnotateCrashReportWithErrno( + CrashReporter::Annotation::IpcCreatePipeFcntlErrno, errno); + return false; + } + + // Mark the pipes as FD_CLOEXEC + int flags = fcntl(fd, F_GETFD); + if (flags == -1) { + mozilla::ipc::AnnotateCrashReportWithErrno( + CrashReporter::Annotation::IpcCreatePipeCloExecErrno, errno); + return false; + } + flags |= FD_CLOEXEC; + if (fcntl(fd, F_SETFD, flags) == -1) { + mozilla::ipc::AnnotateCrashReportWithErrno( + CrashReporter::Annotation::IpcCreatePipeCloExecErrno, errno); + return false; + } + return true; + }; + + if (!configureFd(fds[0]) || !configureFd(fds[1])) { + IGNORE_EINTR(close(fds[0])); + IGNORE_EINTR(close(fds[1])); + return false; + } + + server->reset(fds[0]); + client->reset(fds[1]); + return true; +} + +} // namespace IPC diff --git a/ipc/chromium/src/chrome/common/ipc_channel_posix.h b/ipc/chromium/src/chrome/common/ipc_channel_posix.h new file mode 100644 index 0000000000..b70640d04e --- /dev/null +++ b/ipc/chromium/src/chrome/common/ipc_channel_posix.h @@ -0,0 +1,203 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_COMMON_IPC_CHANNEL_POSIX_H_ +#define CHROME_COMMON_IPC_CHANNEL_POSIX_H_ + +#include "chrome/common/ipc_channel.h" +#include "chrome/common/ipc_channel_capability.h" + +#include // for CMSG macros + +#include +#include +#include +#include + +#include "base/message_loop.h" +#include "base/process.h" +#include "base/task.h" + +#include "mozilla/Maybe.h" +#include "mozilla/Queue.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/UniquePtrExtensions.h" +#include "nsISupports.h" + +namespace IPC { + +// An implementation of ChannelImpl for POSIX systems that works via +// socketpairs. See the .cc file for an overview of the implementation. +class Channel::ChannelImpl : public MessageLoopForIO::Watcher { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DELETE_ON_EVENT_TARGET( + ChannelImpl, IOThread().GetEventTarget()); + + // Mirror methods of Channel, see ipc_channel.h for description. + ChannelImpl(ChannelHandle pipe, Mode mode, base::ProcessId other_pid); + bool Connect(Listener* listener) MOZ_EXCLUDES(SendMutex()); + void Close() MOZ_EXCLUDES(SendMutex()); + + // NOTE: `Send` may be called on threads other than the I/O thread. + bool Send(mozilla::UniquePtr message) MOZ_EXCLUDES(SendMutex()); + + void SetOtherPid(base::ProcessId other_pid); + + // See the comment in ipc_channel.h for info on IsClosed() + // NOTE: `IsClosed` may be called on threads other than the I/O thread. + bool IsClosed() MOZ_EXCLUDES(SendMutex()) { + mozilla::MutexAutoLock lock(SendMutex()); + chan_cap_.NoteSendMutex(); + return pipe_ == -1; + } + +#if defined(XP_DARWIN) + void SetOtherMachTask(task_t task) MOZ_EXCLUDES(SendMutex()); + + void StartAcceptingMachPorts(Mode mode) MOZ_EXCLUDES(SendMutex()); +#endif + + private: + ~ChannelImpl() { Close(); } + + void Init(Mode mode) MOZ_REQUIRES(SendMutex(), IOThread()); + void SetPipe(int fd) MOZ_REQUIRES(SendMutex(), IOThread()); + bool PipeBufHasSpaceAfter(size_t already_written) + MOZ_REQUIRES_SHARED(chan_cap_); + bool EnqueueHelloMessage() MOZ_REQUIRES(SendMutex(), IOThread()); + bool ContinueConnect() MOZ_REQUIRES(SendMutex(), IOThread()); + void CloseLocked() MOZ_REQUIRES(SendMutex(), IOThread()); + + bool ProcessIncomingMessages() MOZ_REQUIRES(IOThread()); + bool ProcessOutgoingMessages() MOZ_REQUIRES(SendMutex()); + + // MessageLoopForIO::Watcher implementation. + virtual void OnFileCanReadWithoutBlocking(int fd) override; + virtual void OnFileCanWriteWithoutBlocking(int fd) override; + +#if defined(XP_DARWIN) + void CloseDescriptors(uint32_t pending_fd_id) MOZ_REQUIRES(IOThread()) + MOZ_EXCLUDES(SendMutex()); + + // Called on a Message immediately before it is sent/recieved to transfer + // handles to the remote process, or accept handles from the remote process. + bool AcceptMachPorts(Message& msg) MOZ_REQUIRES(IOThread()); + bool TransferMachPorts(Message& msg) MOZ_REQUIRES_SHARED(chan_cap_); +#endif + + void OutputQueuePush(mozilla::UniquePtr msg) + MOZ_REQUIRES(SendMutex()); + void OutputQueuePop() MOZ_REQUIRES(SendMutex()); + + const ChannelCapability::Thread& IOThread() const + MOZ_RETURN_CAPABILITY(chan_cap_.IOThread()) { + return chan_cap_.IOThread(); + } + + ChannelCapability::Mutex& SendMutex() + MOZ_RETURN_CAPABILITY(chan_cap_.SendMutex()) { + return chan_cap_.SendMutex(); + } + + // Compound capability of a Mutex and the IO thread. + ChannelCapability chan_cap_; + + Mode mode_ MOZ_GUARDED_BY(IOThread()); + + // After accepting one client connection on our server socket we want to + // stop listening. + MessageLoopForIO::FileDescriptorWatcher read_watcher_ + MOZ_GUARDED_BY(IOThread()); + MessageLoopForIO::FileDescriptorWatcher write_watcher_ + MOZ_GUARDED_BY(IOThread()); + + // Indicates whether we're currently blocked waiting for a write to complete. + bool is_blocked_on_write_ MOZ_GUARDED_BY(SendMutex()) = false; + + // If sending a message blocks then we use this iterator to keep track of + // where in the message we are. It gets reset when the message is finished + // sending. + struct PartialWrite { + Pickle::BufferList::IterImpl iter_; + mozilla::Span handles_; + }; + mozilla::Maybe partial_write_ MOZ_GUARDED_BY(SendMutex()); + + int pipe_ MOZ_GUARDED_BY(chan_cap_); + // The SO_SNDBUF value of pipe_, or 0 if unknown. + unsigned pipe_buf_len_ MOZ_GUARDED_BY(chan_cap_); + + Listener* listener_ MOZ_GUARDED_BY(IOThread()); + + // Messages to be sent are queued here. + mozilla::Queue, 64> output_queue_ + MOZ_GUARDED_BY(SendMutex()); + + // We read from the pipe into these buffers. + size_t input_buf_offset_ MOZ_GUARDED_BY(IOThread()); + mozilla::UniquePtr input_buf_ MOZ_GUARDED_BY(IOThread()); + mozilla::UniquePtr input_cmsg_buf_ MOZ_GUARDED_BY(IOThread()); + + // The control message buffer will hold all of the file descriptors that will + // be read in during a single recvmsg call. Message::WriteFileDescriptor + // always writes one word of data for every file descriptor added to the + // message, and the number of file descriptors per recvmsg will not exceed + // kControlBufferMaxFds. This is based on the true maximum SCM_RIGHTS + // descriptor count, which is just over 250 on both Linux and macOS. + // + // This buffer also holds a control message header of size CMSG_SPACE(0) + // bytes. However, CMSG_SPACE is not a constant on Macs, so we can't use it + // here. Consequently, we pick a number here that is at least CMSG_SPACE(0) on + // all platforms. We assert at runtime, in Channel::ChannelImpl::Init, that + // it's big enough. + static constexpr size_t kControlBufferMaxFds = 200; + static constexpr size_t kControlBufferHeaderSize = 32; + static constexpr size_t kControlBufferSize = + kControlBufferMaxFds * sizeof(int) + kControlBufferHeaderSize; + + // Large incoming messages that span multiple pipe buffers get built-up in the + // buffers of this message. + mozilla::UniquePtr incoming_message_ MOZ_GUARDED_BY(IOThread()); + std::vector input_overflow_fds_ MOZ_GUARDED_BY(IOThread()); + + // Will be set to `true` until `Connect()` has been called and communication + // is ready. For privileged connections on macOS, this will not be cleared + // until the peer mach port has been provided to allow transferring mach + // ports. + bool waiting_connect_ MOZ_GUARDED_BY(chan_cap_) = true; + + // We keep track of the PID of the other side of this channel so that we can + // record this when generating logs of IPC messages. + base::ProcessId other_pid_ MOZ_GUARDED_BY(chan_cap_) = + base::kInvalidProcessId; + +#if defined(XP_DARWIN) + struct PendingDescriptors { + uint32_t id; + nsTArray handles; + }; + + std::list pending_fds_ MOZ_GUARDED_BY(SendMutex()); + + // A generation ID for RECEIVED_FD messages. + uint32_t last_pending_fd_id_ MOZ_GUARDED_BY(SendMutex()) = 0; + + // Whether or not to accept mach ports from a remote process, and whether this + // process is the privileged side of a IPC::Channel which can transfer mach + // ports. + bool accept_mach_ports_ MOZ_GUARDED_BY(chan_cap_) = false; + bool privileged_ MOZ_GUARDED_BY(chan_cap_) = false; + + // If available, the task port for the remote process. + mozilla::UniqueMachSendRight other_task_ MOZ_GUARDED_BY(chan_cap_); +#endif + + DISALLOW_COPY_AND_ASSIGN(ChannelImpl); +}; + +} // namespace IPC + +#endif // CHROME_COMMON_IPC_CHANNEL_POSIX_H_ diff --git a/ipc/chromium/src/chrome/common/ipc_channel_utils.cc b/ipc/chromium/src/chrome/common/ipc_channel_utils.cc new file mode 100644 index 0000000000..7e737762f3 --- /dev/null +++ b/ipc/chromium/src/chrome/common/ipc_channel_utils.cc @@ -0,0 +1,58 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "chrome/common/ipc_channel_utils.h" + +#include "mozilla/ProfilerMarkers.h" +#include "chrome/common/ipc_message.h" +#include "base/process.h" + +namespace IPC { + +void AddIPCProfilerMarker(const Message& aMessage, int32_t aOtherPid, + mozilla::ipc::MessageDirection aDirection, + mozilla::ipc::MessagePhase aPhase) { + if (aMessage.routing_id() != MSG_ROUTING_NONE && + profiler_feature_active(ProfilerFeature::IPCMessages)) { + if (static_cast(aOtherPid) == base::kInvalidProcessId) { + DLOG(WARNING) << "Unable to record IPC profile marker, other PID not set"; + return; + } + + if (profiler_is_locked_on_current_thread()) { + // One of the profiler mutexes is locked on this thread, don't record + // markers, because we don't want to expose profiler IPCs due to the + // profiler itself, and also to avoid possible re-entrancy issues. + return; + } + + // The current timestamp must be given to the `IPCMarker` payload. + [[maybe_unused]] const mozilla::TimeStamp now = mozilla::TimeStamp::Now(); + bool isThreadBeingProfiled = + profiler_thread_is_being_profiled_for_markers(); + PROFILER_MARKER( + "IPC", IPC, + mozilla::MarkerOptions( + mozilla::MarkerTiming::InstantAt(now), + // If the thread is being profiled, add the marker to + // the current thread. If the thread is not being + // profiled, add the marker to the main thread. It will + // appear in the main thread's IPC track. Profiler + // analysis UI correlates all the IPC markers from + // different threads and generates processed markers. + isThreadBeingProfiled ? mozilla::MarkerThreadId::CurrentThread() + : mozilla::MarkerThreadId::MainThread()), + IPCMarker, now, now, aOtherPid, aMessage.seqno(), aMessage.type(), + mozilla::ipc::UnknownSide, aDirection, aPhase, aMessage.is_sync(), + // aOriginThreadId: If the thread is being profiled, do not include a + // thread ID, as it's the same as the markers. Only include this field + // when the marker is being sent from another thread. + isThreadBeingProfiled ? mozilla::MarkerThreadId{} + : mozilla::MarkerThreadId::CurrentThread()); + } +} + +} // namespace IPC diff --git a/ipc/chromium/src/chrome/common/ipc_channel_utils.h b/ipc/chromium/src/chrome/common/ipc_channel_utils.h new file mode 100644 index 0000000000..1bcfcf7032 --- /dev/null +++ b/ipc/chromium/src/chrome/common/ipc_channel_utils.h @@ -0,0 +1,21 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef CHROME_COMMON_IPC_CHANNEL_UTILS_H_ +#define CHROME_COMMON_IPC_CHANNEL_UTILS_H_ + +#include "chrome/common/ipc_message.h" +#include "mozilla/ipc/MessageChannel.h" + +namespace IPC { + +void AddIPCProfilerMarker(const Message& aMessage, int32_t aOtherPid, + mozilla::ipc::MessageDirection aDirection, + mozilla::ipc::MessagePhase aPhase); + +} // namespace IPC + +#endif // CHROME_COMMON_IPC_CHANNEL_UTILS_H_ diff --git a/ipc/chromium/src/chrome/common/ipc_channel_win.cc b/ipc/chromium/src/chrome/common/ipc_channel_win.cc new file mode 100644 index 0000000000..3be582b3e2 --- /dev/null +++ b/ipc/chromium/src/chrome/common/ipc_channel_win.cc @@ -0,0 +1,814 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/common/ipc_channel_win.h" + +#include +#include +#include +#include + +#include "base/command_line.h" +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/process.h" +#include "base/process_util.h" +#include "base/rand_util.h" +#include "base/string_util.h" +#include "base/win_util.h" +#include "chrome/common/chrome_switches.h" +#include "chrome/common/ipc_channel_utils.h" +#include "chrome/common/ipc_message_utils.h" +#include "mozilla/ipc/ProtocolUtils.h" +#include "mozilla/Atomics.h" +#include "mozilla/LateWriteChecks.h" +#include "mozilla/RandomNum.h" +#include "nsThreadUtils.h" + +using namespace mozilla::ipc; + +namespace IPC { +//------------------------------------------------------------------------------ + +Channel::ChannelImpl::State::State(ChannelImpl* channel) { + memset(&context.overlapped, 0, sizeof(context.overlapped)); + context.handler = channel; +} + +Channel::ChannelImpl::State::~State() { + COMPILE_ASSERT(!offsetof(Channel::ChannelImpl::State, context), + starts_with_io_context); +} + +//------------------------------------------------------------------------------ + +Channel::ChannelImpl::ChannelImpl(ChannelHandle pipe, Mode mode, + base::ProcessId other_pid) + : chan_cap_("ChannelImpl::SendMutex", + MessageLoopForIO::current()->SerialEventTarget()), + ALLOW_THIS_IN_INITIALIZER_LIST(input_state_(this)), + ALLOW_THIS_IN_INITIALIZER_LIST(output_state_(this)), + other_pid_(other_pid) { + Init(mode); + + if (!pipe) { + return; + } + + pipe_ = pipe.release(); + EnqueueHelloMessage(); +} + +void Channel::ChannelImpl::Init(Mode mode) { + // Verify that we fit in a "quantum-spaced" jemalloc bucket. + static_assert(sizeof(*this) <= 512, "Exceeded expected size class"); + + chan_cap_.NoteExclusiveAccess(); + + mode_ = mode; + pipe_ = INVALID_HANDLE_VALUE; + waiting_connect_ = true; + processing_incoming_ = false; + input_buf_offset_ = 0; + input_buf_ = mozilla::MakeUnique(Channel::kReadBufferSize); + accept_handles_ = false; + privileged_ = false; + other_process_ = INVALID_HANDLE_VALUE; +} + +void Channel::ChannelImpl::OutputQueuePush(mozilla::UniquePtr msg) { + chan_cap_.NoteSendMutex(); + + mozilla::LogIPCMessage::LogDispatchWithPid(msg.get(), other_pid_); + + output_queue_.Push(std::move(msg)); +} + +void Channel::ChannelImpl::OutputQueuePop() { + mozilla::UniquePtr message = output_queue_.Pop(); +} + +void Channel::ChannelImpl::Close() { + IOThread().AssertOnCurrentThread(); + mozilla::MutexAutoLock lock(SendMutex()); + CloseLocked(); +} + +void Channel::ChannelImpl::CloseLocked() { + chan_cap_.NoteExclusiveAccess(); + + // If we still have pending I/O, cancel it. The references inside + // `input_state_` and `output_state_` will keep the buffers alive until they + // complete. + if (input_state_.is_pending || output_state_.is_pending) { + CancelIo(pipe_); + } + + // Closing the handle at this point prevents us from issuing more requests + // form OnIOCompleted(). + if (pipe_ != INVALID_HANDLE_VALUE) { + CloseHandle(pipe_); + pipe_ = INVALID_HANDLE_VALUE; + } + + // If we have a connection to the other process, close the handle. + if (other_process_ != INVALID_HANDLE_VALUE) { + CloseHandle(other_process_); + other_process_ = INVALID_HANDLE_VALUE; + } + + // Don't return from `CloseLocked()` until the IO has been completed, + // otherwise the IO thread may exit with outstanding IO, leaking the + // ChannelImpl. + // + // It's OK to unlock here, as calls to `Send` from other threads will be + // rejected, due to `pipe_` having been cleared. + while (input_state_.is_pending || output_state_.is_pending) { + mozilla::MutexAutoUnlock unlock(SendMutex()); + MessageLoopForIO::current()->WaitForIOCompletion(INFINITE, this); + } + + while (!output_queue_.IsEmpty()) { + OutputQueuePop(); + } +} + +bool Channel::ChannelImpl::Send(mozilla::UniquePtr message) { + mozilla::MutexAutoLock lock(SendMutex()); + chan_cap_.NoteSendMutex(); + +#ifdef IPC_MESSAGE_DEBUG_EXTRA + DLOG(INFO) << "sending message @" << message.get() << " on channel @" << this + << " with type " << message->type() << " (" + << output_queue_.Count() << " in queue)"; +#endif + + if (pipe_ == INVALID_HANDLE_VALUE) { + if (mozilla::ipc::LoggingEnabled()) { + fprintf(stderr, + "Can't send message %s, because this channel is closed.\n", + message->name()); + } + return false; + } + + OutputQueuePush(std::move(message)); + // ensure waiting to write + if (!waiting_connect_) { + if (!output_state_.is_pending) { + if (!ProcessOutgoingMessages(NULL, 0, false)) { + return false; + } + } + } + + return true; +} + +bool Channel::ChannelImpl::EnqueueHelloMessage() { + chan_cap_.NoteExclusiveAccess(); + + auto m = mozilla::MakeUnique(MSG_ROUTING_NONE, HELLO_MESSAGE_TYPE); + + // Also, don't send if the value is zero (for IPC backwards compatability). + if (!m->WriteInt(GetCurrentProcessId())) { + CloseHandle(pipe_); + pipe_ = INVALID_HANDLE_VALUE; + return false; + } + + OutputQueuePush(std::move(m)); + return true; +} + +bool Channel::ChannelImpl::Connect(Listener* listener) { + IOThread().AssertOnCurrentThread(); + mozilla::MutexAutoLock lock(SendMutex()); + chan_cap_.NoteExclusiveAccess(); + + if (pipe_ == INVALID_HANDLE_VALUE) return false; + + listener_ = listener; + + MessageLoopForIO::current()->RegisterIOHandler(pipe_, this); + waiting_connect_ = false; + + DCHECK(!input_state_.is_pending); + + // Complete setup asynchronously. By not setting input_state_.is_pending + // to `this`, we indicate to OnIOCompleted that this is the special + // initialization signal, while keeping a reference through the + // `RunnableMethod`. + IOThread().Dispatch( + mozilla::NewRunnableMethod( + "ContinueConnect", this, &ChannelImpl::OnIOCompleted, + &input_state_.context, 0, 0)); + + DCHECK(!output_state_.is_pending); + ProcessOutgoingMessages(NULL, 0, false); + return true; +} + +void Channel::ChannelImpl::SetOtherPid(base::ProcessId other_pid) { + IOThread().AssertOnCurrentThread(); + mozilla::MutexAutoLock lock(SendMutex()); + chan_cap_.NoteExclusiveAccess(); + MOZ_RELEASE_ASSERT( + other_pid_ == base::kInvalidProcessId || other_pid_ == other_pid, + "Multiple sources of SetOtherPid disagree!"); + other_pid_ = other_pid; + + // Now that we know the remote pid, open a privileged handle to the + // child process if needed to transfer handles to/from it. + if (privileged_ && other_process_ == INVALID_HANDLE_VALUE) { + other_process_ = OpenProcess(PROCESS_DUP_HANDLE, false, other_pid_); + if (!other_process_) { + other_process_ = INVALID_HANDLE_VALUE; + CHROMIUM_LOG(ERROR) << "Failed to acquire privileged handle to " + << other_pid_ << ", cannot accept handles"; + } + } +} + +bool Channel::ChannelImpl::ProcessIncomingMessages( + MessageLoopForIO::IOContext* context, DWORD bytes_read, bool was_pending) { + chan_cap_.NoteOnIOThread(); + + DCHECK(!input_state_.is_pending); + + if (was_pending) { + DCHECK(context); + + if (!context || !bytes_read) return false; + } else { + // This happens at channel initialization. + DCHECK(!bytes_read && context == &input_state_.context); + } + + for (;;) { + if (bytes_read == 0) { + if (INVALID_HANDLE_VALUE == pipe_) return false; + + // Read from pipe... + BOOL ok = ReadFile(pipe_, input_buf_.get() + input_buf_offset_, + Channel::kReadBufferSize - input_buf_offset_, + &bytes_read, &input_state_.context.overlapped); + if (!ok) { + DWORD err = GetLastError(); + if (err == ERROR_IO_PENDING) { + input_state_.is_pending = this; + return true; + } + if (err != ERROR_BROKEN_PIPE && err != ERROR_NO_DATA) { + CHROMIUM_LOG(ERROR) + << "pipe error in connection to " << other_pid_ << ": " << err; + } + return false; + } + input_state_.is_pending = this; + return true; + } + DCHECK(bytes_read); + + // Process messages from input buffer. + + const char* p = input_buf_.get(); + const char* end = input_buf_.get() + input_buf_offset_ + bytes_read; + + // NOTE: We re-check `pipe_` after each message to make sure we weren't + // closed while calling `OnMessageReceived` or `OnChannelConnected`. + while (p < end && INVALID_HANDLE_VALUE != pipe_) { + // Try to figure out how big the message is. Size is 0 if we haven't read + // enough of the header to know the size. + uint32_t message_length = 0; + if (incoming_message_) { + message_length = incoming_message_->size(); + } else { + message_length = Message::MessageSize(p, end); + } + + if (!message_length) { + // We haven't seen the full message header. + MOZ_ASSERT(!incoming_message_); + + // Move everything we have to the start of the buffer. We'll finish + // reading this message when we get more data. For now we leave it in + // input_buf_. + memmove(input_buf_.get(), p, end - p); + input_buf_offset_ = end - p; + + break; + } + + input_buf_offset_ = 0; + + bool partial; + if (incoming_message_) { + // We already have some data for this message stored in + // incoming_message_. We want to append the new data there. + Message& m = *incoming_message_; + + // How much data from this message remains to be added to + // incoming_message_? + MOZ_ASSERT(message_length > m.CurrentSize()); + uint32_t remaining = message_length - m.CurrentSize(); + + // How much data from this message is stored in input_buf_? + uint32_t in_buf = std::min(remaining, uint32_t(end - p)); + + m.InputBytes(p, in_buf); + p += in_buf; + + // Are we done reading this message? + partial = in_buf != remaining; + } else { + // How much data from this message is stored in input_buf_? + uint32_t in_buf = std::min(message_length, uint32_t(end - p)); + + incoming_message_ = mozilla::MakeUnique(p, in_buf); + p += in_buf; + + // Are we done reading this message? + partial = in_buf != message_length; + } + + if (partial) { + break; + } + + Message& m = *incoming_message_; + + // Note: We set other_pid_ below when we receive a Hello message (which + // has no routing ID), but we only emit a profiler marker for messages + // with a routing ID, so there's no conflict here. + AddIPCProfilerMarker(m, other_pid_, MessageDirection::eReceiving, + MessagePhase::TransferEnd); + +#ifdef IPC_MESSAGE_DEBUG_EXTRA + DLOG(INFO) << "received message on channel @" << this << " with type " + << m.type(); +#endif + if (m.routing_id() == MSG_ROUTING_NONE && + m.type() == HELLO_MESSAGE_TYPE) { + // The Hello message contains the process id and must include the + // shared secret, if we are waiting for it. + MessageIterator it = MessageIterator(m); + int32_t other_pid = it.NextInt(); + SetOtherPid(other_pid); + + listener_->OnChannelConnected(other_pid); + } else { + mozilla::LogIPCMessage::Run run(&m); + if (!AcceptHandles(m)) { + return false; + } + listener_->OnMessageReceived(std::move(incoming_message_)); + } + + incoming_message_ = nullptr; + } + + bytes_read = 0; // Get more data. + } +} + +bool Channel::ChannelImpl::ProcessOutgoingMessages( + MessageLoopForIO::IOContext* context, DWORD bytes_written, + bool was_pending) { + chan_cap_.NoteSendMutex(); + + DCHECK(!output_state_.is_pending); + DCHECK(!waiting_connect_); // Why are we trying to send messages if there's + // no connection? + if (was_pending) { + DCHECK(context); + if (!context || bytes_written == 0) { + DWORD err = GetLastError(); + if (err != ERROR_BROKEN_PIPE && err != ERROR_NO_DATA) { + CHROMIUM_LOG(ERROR) + << "pipe error in connection to " << other_pid_ << ": " << err; + } + return false; + } + // Message was sent. + DCHECK(!output_queue_.IsEmpty()); + Message* m = output_queue_.FirstElement().get(); + + MOZ_RELEASE_ASSERT(partial_write_iter_.isSome()); + Pickle::BufferList::IterImpl& iter = partial_write_iter_.ref(); + iter.Advance(m->Buffers(), bytes_written); + if (iter.Done()) { + AddIPCProfilerMarker(*m, other_pid_, MessageDirection::eSending, + MessagePhase::TransferEnd); + + partial_write_iter_.reset(); + OutputQueuePop(); + // m has been destroyed, so clear the dangling reference. + m = nullptr; + } + } + + if (output_queue_.IsEmpty()) return true; + + if (INVALID_HANDLE_VALUE == pipe_) return false; + + // Write to pipe... + Message* m = output_queue_.FirstElement().get(); + + if (partial_write_iter_.isNothing()) { + AddIPCProfilerMarker(*m, other_pid_, MessageDirection::eSending, + MessagePhase::TransferStart); + if (!TransferHandles(*m)) { + return false; + } + Pickle::BufferList::IterImpl iter(m->Buffers()); + partial_write_iter_.emplace(iter); + } + + Pickle::BufferList::IterImpl& iter = partial_write_iter_.ref(); + + // Don't count this write for the purposes of late write checking. If this + // message results in a legitimate file write, that will show up when it + // happens. + mozilla::PushSuspendLateWriteChecks(); + BOOL ok = WriteFile(pipe_, iter.Data(), iter.RemainingInSegment(), + &bytes_written, &output_state_.context.overlapped); + mozilla::PopSuspendLateWriteChecks(); + + if (!ok) { + DWORD err = GetLastError(); + if (err == ERROR_IO_PENDING) { + output_state_.is_pending = this; + +#ifdef IPC_MESSAGE_DEBUG_EXTRA + DLOG(INFO) << "sent pending message @" << m << " on channel @" << this + << " with type " << m->type(); +#endif + + return true; + } + if (err != ERROR_BROKEN_PIPE && err != ERROR_NO_DATA) { + CHROMIUM_LOG(ERROR) << "pipe error in connection to " << other_pid_ + << ": " << err; + } + return false; + } + +#ifdef IPC_MESSAGE_DEBUG_EXTRA + DLOG(INFO) << "sent message @" << m << " on channel @" << this + << " with type " << m->type(); +#endif + + output_state_.is_pending = this; + return true; +} + +void Channel::ChannelImpl::OnIOCompleted(MessageLoopForIO::IOContext* context, + DWORD bytes_transfered, DWORD error) { + // NOTE: In case the pending reference was the last reference, release it + // outside of the lock. + RefPtr was_pending; + + IOThread().AssertOnCurrentThread(); + chan_cap_.NoteOnIOThread(); + + bool ok; + if (context == &input_state_.context) { + was_pending = input_state_.is_pending.forget(); + // we don't support recursion through OnMessageReceived yet! + DCHECK(!processing_incoming_); + processing_incoming_ = true; + ok = ProcessIncomingMessages(context, bytes_transfered, was_pending); + processing_incoming_ = false; + } else { + mozilla::MutexAutoLock lock(SendMutex()); + DCHECK(context == &output_state_.context); + was_pending = output_state_.is_pending.forget(); + ok = ProcessOutgoingMessages(context, bytes_transfered, was_pending); + } + if (!ok && INVALID_HANDLE_VALUE != pipe_) { + // We don't want to re-enter Close(). + Close(); + listener_->OnChannelError(); + } +} + +void Channel::ChannelImpl::StartAcceptingHandles(Mode mode) { + IOThread().AssertOnCurrentThread(); + mozilla::MutexAutoLock lock(SendMutex()); + chan_cap_.NoteExclusiveAccess(); + + if (accept_handles_) { + MOZ_ASSERT(privileged_ == (mode == MODE_SERVER)); + return; + } + accept_handles_ = true; + privileged_ = mode == MODE_SERVER; + + if (privileged_ && other_pid_ != base::kInvalidProcessId && + other_process_ == INVALID_HANDLE_VALUE) { + other_process_ = OpenProcess(PROCESS_DUP_HANDLE, false, other_pid_); + if (!other_process_) { + other_process_ = INVALID_HANDLE_VALUE; + CHROMIUM_LOG(ERROR) << "Failed to acquire privileged handle to " + << other_pid_ << ", cannot accept handles"; + } + } +} + +// This logic is borrowed from Chromium's `base/win/nt_status.cc`, and is used +// to detect and silence DuplicateHandle errors caused due to the other process +// exiting. +// +// https://source.chromium.org/chromium/chromium/src/+/main:base/win/nt_status.cc;drc=e4622aaeccea84652488d1822c28c78b7115684f +static NTSTATUS GetLastNtStatus() { + using GetLastNtStatusFn = NTSTATUS NTAPI (*)(); + + static constexpr const wchar_t kNtDllName[] = L"ntdll.dll"; + static constexpr const char kLastStatusFnName[] = "RtlGetLastNtStatus"; + + // This is equivalent to calling NtCurrentTeb() and extracting + // LastStatusValue from the returned _TEB structure, except that the public + // _TEB struct definition does not actually specify the location of the + // LastStatusValue field. We avoid depending on such a definition by + // internally using RtlGetLastNtStatus() from ntdll.dll instead. + static auto* get_last_nt_status = reinterpret_cast( + ::GetProcAddress(::GetModuleHandle(kNtDllName), kLastStatusFnName)); + return get_last_nt_status(); +} + +// ERROR_ACCESS_DENIED may indicate that the remote process (which could be +// either the source or destination process here) is already terminated or has +// begun termination and therefore no longer has a handle table. We don't want +// these cases to crash because we know they happen in practice and are +// largely unavoidable. +// +// https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:mojo/core/platform_handle_in_transit.cc;l=47-53;drc=fdfd85f836e0e59c79ed9bf6d527a2b8f7fdeb6e +static bool WasOtherProcessExitingError(DWORD error) { + return error == ERROR_ACCESS_DENIED && + GetLastNtStatus() == STATUS_PROCESS_IS_TERMINATING; +} + +static uint32_t HandleToUint32(HANDLE h) { + // Cast through uintptr_t and then unsigned int to make the truncation to + // 32 bits explicit. Handles are size of-pointer but are always 32-bit values. + // https://docs.microsoft.com/en-ca/windows/win32/winprog64/interprocess-communication + // says: 64-bit versions of Windows use 32-bit handles for interoperability. + return static_cast(reinterpret_cast(h)); +} + +static HANDLE Uint32ToHandle(uint32_t h) { + return reinterpret_cast( + static_cast(static_cast(h))); +} + +bool Channel::ChannelImpl::AcceptHandles(Message& msg) { + chan_cap_.NoteOnIOThread(); + + MOZ_ASSERT(msg.num_handles() == 0); + + uint32_t num_handles = msg.header()->num_handles; + if (num_handles == 0) { + return true; + } + + if (!accept_handles_) { + CHROMIUM_LOG(ERROR) << "invalid message: " << msg.name() + << ". channel is not configured to accept handles"; + return false; + } + + // Read in the payload from the footer, truncating the message. + nsTArray payload; + payload.AppendElements(num_handles); + if (!msg.ReadFooter(payload.Elements(), num_handles * sizeof(uint32_t), + /* truncate */ true)) { + CHROMIUM_LOG(ERROR) << "failed to read handle payload from message"; + return false; + } + msg.header()->num_handles = 0; + + // Read in the handles themselves, transferring ownership as required. + nsTArray handles(num_handles); + for (uint32_t handleValue : payload) { + HANDLE ipc_handle = Uint32ToHandle(handleValue); + if (!ipc_handle || ipc_handle == INVALID_HANDLE_VALUE) { + CHROMIUM_LOG(ERROR) + << "Attempt to accept invalid or null handle from process " + << other_pid_ << " for message " << msg.name() << " in AcceptHandles"; + return false; + } + + // If we're the privileged process, the remote process will have leaked + // the sent handles in its local address space, and be relying on us to + // duplicate them, otherwise the remote privileged side will have + // transferred the handles to us already. + mozilla::UniqueFileHandle local_handle; + if (privileged_) { + MOZ_ASSERT(other_process_, "other_process_ cannot be null"); + if (other_process_ == INVALID_HANDLE_VALUE) { + CHROMIUM_LOG(ERROR) << "other_process_ is invalid in AcceptHandles"; + return false; + } + if (!::DuplicateHandle(other_process_, ipc_handle, GetCurrentProcess(), + getter_Transfers(local_handle), 0, FALSE, + DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE)) { + DWORD err = GetLastError(); + // Don't log out a scary looking error if this failed due to the target + // process terminating. + if (!WasOtherProcessExitingError(err)) { + CHROMIUM_LOG(ERROR) + << "DuplicateHandle failed for handle " << ipc_handle + << " from process " << other_pid_ << " for message " << msg.name() + << " in AcceptHandles with error: " << err; + } + return false; + } + } else { + local_handle.reset(ipc_handle); + } + + MOZ_DIAGNOSTIC_ASSERT( + local_handle, "Accepting invalid or null handle from another process"); + + // The handle is directly owned by this process now, and can be added to + // our `handles` array. + handles.AppendElement(std::move(local_handle)); + } + + // We're done with the handle footer, truncate the message at that point. + msg.SetAttachedFileHandles(std::move(handles)); + MOZ_ASSERT(msg.num_handles() == num_handles); + return true; +} + +bool Channel::ChannelImpl::TransferHandles(Message& msg) { + chan_cap_.NoteSendMutex(); + + MOZ_ASSERT(msg.header()->num_handles == 0); + + uint32_t num_handles = msg.num_handles(); + if (num_handles == 0) { + return true; + } + + if (!accept_handles_) { + CHROMIUM_LOG(ERROR) << "cannot send message: " << msg.name() + << ". channel is not configured to accept handles"; + return false; + } + +#ifdef DEBUG + uint32_t handles_offset = msg.header()->payload_size; +#endif + + nsTArray payload(num_handles); + for (uint32_t i = 0; i < num_handles; ++i) { + // Take ownership of the handle. + mozilla::UniqueFileHandle local_handle = + std::move(msg.attached_handles_[i]); + if (!local_handle) { + CHROMIUM_LOG(ERROR) + << "Attempt to transfer invalid or null handle to process " + << other_pid_ << " for message " << msg.name() + << " in TransferHandles"; + return false; + } + + // If we're the privileged process, transfer the HANDLE to our remote before + // sending the message. Otherwise, the remote privileged process will + // transfer the handle for us, so leak it. + HANDLE ipc_handle = NULL; + if (privileged_) { + MOZ_ASSERT(other_process_, "other_process_ cannot be null"); + if (other_process_ == INVALID_HANDLE_VALUE) { + CHROMIUM_LOG(ERROR) << "other_process_ is invalid in TransferHandles"; + return false; + } + if (!::DuplicateHandle(GetCurrentProcess(), local_handle.get(), + other_process_, &ipc_handle, 0, FALSE, + DUPLICATE_SAME_ACCESS)) { + DWORD err = GetLastError(); + // Don't log out a scary looking error if this failed due to the target + // process terminating. + if (!WasOtherProcessExitingError(err)) { + CHROMIUM_LOG(ERROR) << "DuplicateHandle failed for handle " + << (HANDLE)local_handle.get() << " to process " + << other_pid_ << " for message " << msg.name() + << " in TransferHandles with error: " << err; + } + return false; + } + } else { + // Release ownership of the handle. It'll be closed when the parent + // process transfers it with DuplicateHandle in the remote privileged + // process. + ipc_handle = local_handle.release(); + } + + MOZ_DIAGNOSTIC_ASSERT( + ipc_handle && ipc_handle != INVALID_HANDLE_VALUE, + "Transferring invalid or null handle to another process"); + + payload.AppendElement(HandleToUint32(ipc_handle)); + } + msg.attached_handles_.Clear(); + + msg.WriteFooter(payload.Elements(), payload.Length() * sizeof(uint32_t)); + msg.header()->num_handles = num_handles; + + MOZ_ASSERT(msg.header()->payload_size == + handles_offset + (sizeof(uint32_t) * num_handles), + "Unexpected number of bytes written for handles footer?"); + return true; +} + +//------------------------------------------------------------------------------ +// Channel's methods simply call through to ChannelImpl. +Channel::Channel(ChannelHandle pipe, Mode mode, base::ProcessId other_pid) + : channel_impl_(new ChannelImpl(std::move(pipe), mode, other_pid)) { + MOZ_COUNT_CTOR(IPC::Channel); +} + +Channel::~Channel() { MOZ_COUNT_DTOR(IPC::Channel); } + +bool Channel::Connect(Listener* listener) { + return channel_impl_->Connect(listener); +} + +void Channel::Close() { channel_impl_->Close(); } + +void Channel::StartAcceptingHandles(Mode mode) { + channel_impl_->StartAcceptingHandles(mode); +} + +bool Channel::Send(mozilla::UniquePtr message) { + return channel_impl_->Send(std::move(message)); +} + +void Channel::SetOtherPid(base::ProcessId other_pid) { + channel_impl_->SetOtherPid(other_pid); +} + +bool Channel::IsClosed() const { return channel_impl_->IsClosed(); } + +HANDLE Channel::GetClientChannelHandle() { + // Read the switch from the command line which passed the initial handle for + // this process, and convert it back into a HANDLE. + std::wstring switchValue = CommandLine::ForCurrentProcess()->GetSwitchValue( + switches::kProcessChannelID); + + uint32_t handleInt = std::stoul(switchValue); + return Uint32ToHandle(handleInt); +} + +// static +bool Channel::CreateRawPipe(ChannelHandle* server, ChannelHandle* client) { + std::wstring pipe_name = + StringPrintf(L"\\\\.\\pipe\\gecko.%lu.%lu.%I64u", ::GetCurrentProcessId(), + ::GetCurrentThreadId(), mozilla::RandomUint64OrDie()); + const DWORD kOpenMode = + PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED | FILE_FLAG_FIRST_PIPE_INSTANCE; + const DWORD kPipeMode = PIPE_TYPE_BYTE | PIPE_READMODE_BYTE; + *server = mozilla::UniqueFileHandle( + ::CreateNamedPipeW(pipe_name.c_str(), kOpenMode, kPipeMode, + 1, // Max instances. + Channel::kReadBufferSize, // Output buffer size. + Channel::kReadBufferSize, // Input buffer size. + 5000, // Timeout in ms. + nullptr)); // Default security descriptor. + if (!server) { + NS_WARNING( + nsPrintfCString("CreateNamedPipeW Failed %lu", ::GetLastError()).get()); + return false; + } + + const DWORD kDesiredAccess = GENERIC_READ | GENERIC_WRITE; + // The SECURITY_ANONYMOUS flag means that the server side cannot impersonate + // the client, which is useful as both server & client may be unprivileged. + const DWORD kFlags = + SECURITY_SQOS_PRESENT | SECURITY_ANONYMOUS | FILE_FLAG_OVERLAPPED; + *client = mozilla::UniqueFileHandle( + ::CreateFileW(pipe_name.c_str(), kDesiredAccess, 0, nullptr, + OPEN_EXISTING, kFlags, nullptr)); + if (!client) { + NS_WARNING( + nsPrintfCString("CreateFileW Failed %lu", ::GetLastError()).get()); + return false; + } + + // Since a client has connected, ConnectNamedPipe() should return zero and + // GetLastError() should return ERROR_PIPE_CONNECTED. + if (::ConnectNamedPipe(server->get(), nullptr) || + ::GetLastError() != ERROR_PIPE_CONNECTED) { + NS_WARNING( + nsPrintfCString("ConnectNamedPipe Failed %lu", ::GetLastError()).get()); + return false; + } + return true; +} + +} // namespace IPC diff --git a/ipc/chromium/src/chrome/common/ipc_channel_win.h b/ipc/chromium/src/chrome/common/ipc_channel_win.h new file mode 100644 index 0000000000..f091a81b76 --- /dev/null +++ b/ipc/chromium/src/chrome/common/ipc_channel_win.h @@ -0,0 +1,167 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_COMMON_IPC_CHANNEL_WIN_H_ +#define CHROME_COMMON_IPC_CHANNEL_WIN_H_ + +#include "chrome/common/ipc_channel.h" +#include "chrome/common/ipc_channel_capability.h" +#include "chrome/common/ipc_message.h" + +#include +#include + +#include "base/message_loop.h" +#include "base/process.h" +#include "base/task.h" +#include "nsISupportsImpl.h" + +#include "mozilla/EventTargetCapability.h" +#include "mozilla/Maybe.h" +#include "mozilla/Mutex.h" +#include "mozilla/Queue.h" +#include "mozilla/UniquePtr.h" + +namespace IPC { + +class Channel::ChannelImpl : public MessageLoopForIO::IOHandler { + public: + NS_INLINE_DECL_THREADSAFE_REFCOUNTING_WITH_DELETE_ON_EVENT_TARGET( + ChannelImpl, IOThread().GetEventTarget()); + + using ChannelHandle = Channel::ChannelHandle; + + // Mirror methods of Channel, see ipc_channel.h for description. + ChannelImpl(ChannelHandle pipe, Mode mode, base::ProcessId other_pid); + bool Connect(Listener* listener) MOZ_EXCLUDES(SendMutex()); + void Close() MOZ_EXCLUDES(SendMutex()); + void StartAcceptingHandles(Mode mode) MOZ_EXCLUDES(SendMutex()); + // NOTE: `Send` may be called on threads other than the I/O thread. + bool Send(mozilla::UniquePtr message) MOZ_EXCLUDES(SendMutex()); + + void SetOtherPid(base::ProcessId other_pid); + + // See the comment in ipc_channel.h for info on IsClosed() + // NOTE: `IsClosed` may be called on threads other than the I/O thread. + bool IsClosed() MOZ_EXCLUDES(SendMutex()) { + mozilla::MutexAutoLock lock(SendMutex()); + chan_cap_.NoteSendMutex(); + return pipe_ == INVALID_HANDLE_VALUE; + } + + private: + ~ChannelImpl() { + IOThread().AssertOnCurrentThread(); + if (pipe_ != INVALID_HANDLE_VALUE || + other_process_ != INVALID_HANDLE_VALUE) { + Close(); + } + } + + void Init(Mode mode) MOZ_REQUIRES(SendMutex(), IOThread()); + + void OutputQueuePush(mozilla::UniquePtr msg) + MOZ_REQUIRES(SendMutex()); + void OutputQueuePop() MOZ_REQUIRES(SendMutex()); + + bool EnqueueHelloMessage() MOZ_REQUIRES(SendMutex(), IOThread()); + void CloseLocked() MOZ_REQUIRES(SendMutex(), IOThread()); + + bool ProcessIncomingMessages(MessageLoopForIO::IOContext* context, + DWORD bytes_read, bool was_pending) + MOZ_REQUIRES(IOThread()); + bool ProcessOutgoingMessages(MessageLoopForIO::IOContext* context, + DWORD bytes_written, bool was_pending) + MOZ_REQUIRES(SendMutex()); + + // Called on a Message immediately before it is sent/recieved to transfer + // handles to the remote process, or accept handles from the remote process. + bool AcceptHandles(Message& msg) MOZ_REQUIRES(IOThread()); + bool TransferHandles(Message& msg) MOZ_REQUIRES(SendMutex()); + + // MessageLoop::IOHandler implementation. + virtual void OnIOCompleted(MessageLoopForIO::IOContext* context, + DWORD bytes_transfered, DWORD error); + + const ChannelCapability::Thread& IOThread() const + MOZ_RETURN_CAPABILITY(chan_cap_.IOThread()) { + return chan_cap_.IOThread(); + } + + ChannelCapability::Mutex& SendMutex() + MOZ_RETURN_CAPABILITY(chan_cap_.SendMutex()) { + return chan_cap_.SendMutex(); + } + + private: + // Compound capability of a Mutex and the IO thread. + ChannelCapability chan_cap_; + + Mode mode_ MOZ_GUARDED_BY(IOThread()); + + struct State { + explicit State(ChannelImpl* channel); + ~State(); + MessageLoopForIO::IOContext context; + // When there is pending I/O, this holds a strong reference to the + // ChannelImpl to prevent it from going away. + RefPtr is_pending; + }; + + State input_state_ MOZ_GUARDED_BY(IOThread()); + State output_state_ MOZ_GUARDED_BY(SendMutex()); + + HANDLE pipe_ MOZ_GUARDED_BY(chan_cap_) = INVALID_HANDLE_VALUE; + + Listener* listener_ MOZ_GUARDED_BY(IOThread()) = nullptr; + + // Messages to be sent are queued here. + mozilla::Queue, 64> output_queue_ + MOZ_GUARDED_BY(SendMutex()); + + // If sending a message blocks then we use this iterator to keep track of + // where in the message we are. It gets reset when the message is finished + // sending. + mozilla::Maybe partial_write_iter_ + MOZ_GUARDED_BY(SendMutex()); + + // We read from the pipe into this buffer + mozilla::UniquePtr input_buf_ MOZ_GUARDED_BY(IOThread()); + size_t input_buf_offset_ MOZ_GUARDED_BY(IOThread()) = 0; + + // Large incoming messages that span multiple pipe buffers get built-up in the + // buffers of this message. + mozilla::UniquePtr incoming_message_ MOZ_GUARDED_BY(IOThread()); + + // Will be set to `true` until `Connect()` has been called. + bool waiting_connect_ MOZ_GUARDED_BY(chan_cap_) = true; + + // This flag is set when processing incoming messages. It is used to + // avoid recursing through ProcessIncomingMessages, which could cause + // problems. TODO(darin): make this unnecessary + bool processing_incoming_ MOZ_GUARDED_BY(IOThread()) = false; + + // We keep track of the PID of the other side of this channel so that we can + // record this when generating logs of IPC messages. + base::ProcessId other_pid_ MOZ_GUARDED_BY(chan_cap_) = + base::kInvalidProcessId; + + // Whether or not to accept handles from a remote process, and whether this + // process is the privileged side of a IPC::Channel which can transfer + // handles. + bool accept_handles_ MOZ_GUARDED_BY(chan_cap_) = false; + bool privileged_ MOZ_GUARDED_BY(chan_cap_) = false; + + // A privileged process handle used to transfer HANDLEs to and from the remote + // process. This will only be used if `privileged_` is set. + HANDLE other_process_ MOZ_GUARDED_BY(chan_cap_) = INVALID_HANDLE_VALUE; + + DISALLOW_COPY_AND_ASSIGN(ChannelImpl); +}; + +} // namespace IPC + +#endif // CHROME_COMMON_IPC_CHANNEL_WIN_H_ diff --git a/ipc/chromium/src/chrome/common/ipc_message.cc b/ipc/chromium/src/chrome/common/ipc_message.cc new file mode 100644 index 0000000000..472c0fb4e1 --- /dev/null +++ b/ipc/chromium/src/chrome/common/ipc_message.cc @@ -0,0 +1,217 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/common/ipc_message.h" + +#include "base/logging.h" +#include "mojo/core/ports/event.h" + +#include + +#include "nsISupportsImpl.h" + +namespace IPC { + +//------------------------------------------------------------------------------ + +const mojo::core::ports::UserMessage::TypeInfo Message::kUserMessageTypeInfo{}; + +Message::~Message() { MOZ_COUNT_DTOR(IPC::Message); } + +Message::Message(int32_t routing_id, msgid_t type, uint32_t segment_capacity, + HeaderFlags flags) + : UserMessage(&kUserMessageTypeInfo), + Pickle(sizeof(Header), segment_capacity) { + MOZ_COUNT_CTOR(IPC::Message); + header()->routing = routing_id; + header()->type = type; + header()->flags = flags; + header()->num_handles = 0; + header()->txid = -1; + header()->seqno = 0; +#if defined(XP_DARWIN) + header()->cookie = 0; + header()->num_send_rights = 0; +#endif + header()->event_footer_size = 0; +} + +Message::Message(const char* data, int data_len) + : UserMessage(&kUserMessageTypeInfo), + Pickle(sizeof(Header), data, data_len) { + MOZ_COUNT_CTOR(IPC::Message); +} + +/*static*/ mozilla::UniquePtr Message::IPDLMessage( + int32_t routing_id, msgid_t type, uint32_t segment_capacity, + HeaderFlags flags) { + return mozilla::MakeUnique(routing_id, type, segment_capacity, + flags); +} + +/*static*/ mozilla::UniquePtr Message::ForSyncDispatchError( + NestedLevel level) { + auto m = mozilla::MakeUnique(0, 0, 0, HeaderFlags(level)); + auto& flags = m->header()->flags; + flags.SetSync(); + flags.SetReply(); + flags.SetReplyError(); + return m; +} + +void Message::WriteFooter(const void* data, uint32_t data_len) { + if (data_len == 0) { + return; + } + + WriteBytes(data, data_len); +} + +bool Message::ReadFooter(void* buffer, uint32_t buffer_len, bool truncate) { + if (buffer_len == 0) { + return true; + } + + if (NS_WARN_IF(AlignInt(header()->payload_size) != header()->payload_size) || + NS_WARN_IF(AlignInt(buffer_len) > header()->payload_size)) { + return false; + } + + // Seek to the start of the footer, and read it in. We read in with a + // duplicate of the iterator so we can use it to truncate later. + uint32_t offset = header()->payload_size - AlignInt(buffer_len); + PickleIterator footer_iter(*this); + if (NS_WARN_IF(!IgnoreBytes(&footer_iter, offset))) { + return false; + } + + PickleIterator read_iter(footer_iter); + bool ok = ReadBytesInto(&read_iter, buffer, buffer_len); + + // If requested, truncate the buffer to the start of the footer. + if (truncate) { + Truncate(&footer_iter); + } + return ok; +} + +bool Message::WriteFileHandle(mozilla::UniqueFileHandle handle) { + uint32_t handle_index = attached_handles_.Length(); + WriteUInt32(handle_index); + if (handle_index == MAX_DESCRIPTORS_PER_MESSAGE) { + return false; + } + attached_handles_.AppendElement(std::move(handle)); + return true; +} + +bool Message::ConsumeFileHandle(PickleIterator* iter, + mozilla::UniqueFileHandle* handle) const { + uint32_t handle_index; + if (!ReadUInt32(iter, &handle_index)) { + return false; + } + if (handle_index >= attached_handles_.Length()) { + return false; + } + // NOTE: This mutates the underlying array, replacing the handle with an + // invalid handle. + *handle = std::exchange(attached_handles_[handle_index], nullptr); + return true; +} + +void Message::SetAttachedFileHandles( + nsTArray handles) { + MOZ_DIAGNOSTIC_ASSERT(attached_handles_.IsEmpty()); + attached_handles_ = std::move(handles); +} + +uint32_t Message::num_handles() const { return attached_handles_.Length(); } + +void Message::WritePort(mozilla::ipc::ScopedPort port) { + uint32_t port_index = attached_ports_.Length(); + WriteUInt32(port_index); + attached_ports_.AppendElement(std::move(port)); +} + +bool Message::ConsumePort(PickleIterator* iter, + mozilla::ipc::ScopedPort* port) const { + uint32_t port_index; + if (!ReadUInt32(iter, &port_index)) { + return false; + } + if (port_index >= attached_ports_.Length()) { + return false; + } + // NOTE: This mutates the underlying array, replacing the port with a consumed + // port. + *port = std::exchange(attached_ports_[port_index], {}); + return true; +} + +void Message::SetAttachedPorts(nsTArray ports) { + MOZ_DIAGNOSTIC_ASSERT(attached_ports_.IsEmpty()); + attached_ports_ = std::move(ports); +} + +#if defined(XP_DARWIN) +bool Message::WriteMachSendRight(mozilla::UniqueMachSendRight port) { + uint32_t index = attached_send_rights_.Length(); + WriteUInt32(index); + if (index == MAX_DESCRIPTORS_PER_MESSAGE) { + return false; + } + attached_send_rights_.AppendElement(std::move(port)); + return true; +} + +bool Message::ConsumeMachSendRight(PickleIterator* iter, + mozilla::UniqueMachSendRight* port) const { + uint32_t index; + if (!ReadUInt32(iter, &index)) { + return false; + } + if (index >= attached_send_rights_.Length()) { + return false; + } + // NOTE: This mutates the underlying array, replacing the send right with a + // null right. + *port = std::exchange(attached_send_rights_[index], nullptr); + return true; +} + +uint32_t Message::num_send_rights() const { + return attached_send_rights_.Length(); +} +#endif + +bool Message::WillBeRoutedExternally( + mojo::core::ports::UserMessageEvent& event) { + if (!attached_ports_.IsEmpty()) { + // Explicitly attach any ports which were attached to this Message to this + // UserMessageEvent before we route it externally so that they can be + // transferred correctly. These ports will be recovered if needed in + // `GetMessage`. + MOZ_DIAGNOSTIC_ASSERT( + event.num_ports() == 0, + "Must not have previously attached ports to the UserMessageEvent"); + event.ReservePorts(attached_ports_.Length()); + for (size_t i = 0; i < event.num_ports(); ++i) { + event.ports()[i] = attached_ports_[i].Release().name(); + } + attached_ports_.Clear(); + } + return true; +} + +void Message::AssertAsLargeAsHeader() const { + MOZ_DIAGNOSTIC_ASSERT(size() >= sizeof(Header)); + MOZ_DIAGNOSTIC_ASSERT(CurrentSize() >= sizeof(Header)); + // Our buffers should agree with what our header specifies. + MOZ_DIAGNOSTIC_ASSERT(size() == CurrentSize()); +} + +} // namespace IPC diff --git a/ipc/chromium/src/chrome/common/ipc_message.h b/ipc/chromium/src/chrome/common/ipc_message.h new file mode 100644 index 0000000000..733ae9e493 --- /dev/null +++ b/ipc/chromium/src/chrome/common/ipc_message.h @@ -0,0 +1,424 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_COMMON_IPC_MESSAGE_H__ +#define CHROME_COMMON_IPC_MESSAGE_H__ + +#include + +#include "base/basictypes.h" +#include "base/pickle.h" +#include "mojo/core/ports/user_message.h" +#include "mojo/core/ports/port_ref.h" +#include "mozilla/RefPtr.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/UniquePtrExtensions.h" +#include "mozilla/ipc/ScopedPort.h" +#include "nsTArray.h" + +namespace mozilla { + +#ifdef FUZZING_SNAPSHOT +namespace fuzzing { +class IPCFuzzController; +} +#endif + +namespace ipc { +class MiniTransceiver; +} +} // namespace mozilla + +namespace IPC { + +//------------------------------------------------------------------------------ + +// Generated by IPDL compiler +const char* StringFromIPCMessageType(uint32_t aMessageType); + +class Channel; +class Message; +class MessageReader; +class MessageWriter; +struct LogData; + +class Message : public mojo::core::ports::UserMessage, public Pickle { + public: + static const TypeInfo kUserMessageTypeInfo; + + typedef uint32_t msgid_t; + + enum NestedLevel { + NOT_NESTED = 1, + NESTED_INSIDE_SYNC = 2, + NESTED_INSIDE_CPOW = 3 + }; + + enum PriorityValue { + NORMAL_PRIORITY = 0, + INPUT_PRIORITY = 1, + VSYNC_PRIORITY = 2, + MEDIUMHIGH_PRIORITY = 3, + CONTROL_PRIORITY = 4, + }; + + enum MessageCompression { + COMPRESSION_NONE, + COMPRESSION_ENABLED, + COMPRESSION_ALL + }; + + enum Sync { + SYNC = 0, + ASYNC = 1, + }; + + enum Constructor { + NOT_CONSTRUCTOR = 0, + CONSTRUCTOR = 1, + }; + + enum Reply { + NOT_REPLY = 0, + REPLY = 1, + }; + + enum LazySend { + EAGER_SEND = 0, + LAZY_SEND = 1, + }; + + // The hard limit of handles or file descriptors allowed in a single message. + static constexpr size_t MAX_DESCRIPTORS_PER_MESSAGE = 32767; + + class HeaderFlags { + friend class Message; +#ifdef FUZZING_SNAPSHOT + // IPCFuzzController calls various private API functions on the header. + friend class mozilla::fuzzing::IPCFuzzController; +#endif + + enum { + NESTED_MASK = 0x0003, + PRIO_MASK = 0x001C, + SYNC_BIT = 0x0020, + REPLY_BIT = 0x0040, + REPLY_ERROR_BIT = 0x0080, + LAZY_SEND_BIT = 0x0100, + COMPRESS_BIT = 0x0200, + COMPRESSALL_BIT = 0x0400, + CONSTRUCTOR_BIT = 0x0800, + RELAY_BIT = 0x1000, + }; + + public: + constexpr HeaderFlags() : mFlags(NOT_NESTED) {} + + explicit constexpr HeaderFlags(NestedLevel level) : mFlags(level) {} + + constexpr HeaderFlags(NestedLevel level, PriorityValue priority, + MessageCompression compression, LazySend lazy_send, + Constructor constructor, Sync sync, Reply reply) + : mFlags(level | (priority << 2) | + (compression == COMPRESSION_ENABLED ? COMPRESS_BIT + : compression == COMPRESSION_ALL ? COMPRESSALL_BIT + : 0) | + (lazy_send == LAZY_SEND ? LAZY_SEND_BIT : 0) | + (constructor == CONSTRUCTOR ? CONSTRUCTOR_BIT : 0) | + (sync == SYNC ? SYNC_BIT : 0) | + (reply == REPLY ? REPLY_BIT : 0)) {} + + NestedLevel Level() const { + return static_cast(mFlags & NESTED_MASK); + } + + PriorityValue Priority() const { + return static_cast((mFlags & PRIO_MASK) >> 2); + } + + MessageCompression Compression() const { + return ((mFlags & COMPRESS_BIT) ? COMPRESSION_ENABLED + : (mFlags & COMPRESSALL_BIT) ? COMPRESSION_ALL + : COMPRESSION_NONE); + } + + bool IsLazySend() const { return (mFlags & LAZY_SEND_BIT) != 0; } + + bool IsConstructor() const { return (mFlags & CONSTRUCTOR_BIT) != 0; } + bool IsSync() const { return (mFlags & SYNC_BIT) != 0; } + bool IsReply() const { return (mFlags & REPLY_BIT) != 0; } + + bool IsReplyError() const { return (mFlags & REPLY_ERROR_BIT) != 0; } + bool IsRelay() const { return (mFlags & RELAY_BIT) != 0; } + + private: + void SetConstructor() { mFlags |= CONSTRUCTOR_BIT; } + void SetSync() { mFlags |= SYNC_BIT; } + void SetReply() { mFlags |= REPLY_BIT; } + void SetReplyError() { mFlags |= REPLY_ERROR_BIT; } + void SetRelay(bool relay) { + if (relay) { + mFlags |= RELAY_BIT; + } else { + mFlags &= ~RELAY_BIT; + } + } + + uint32_t mFlags; + }; + + virtual ~Message(); + + // Initialize a message with a user-defined type, priority value, and + // destination WebView ID. + Message(int32_t routing_id, msgid_t type, + uint32_t segmentCapacity = 0, // 0 for the default capacity. + HeaderFlags flags = HeaderFlags()); + + Message(const char* data, int data_len); + + Message(const Message&) = delete; + Message(Message&&) = delete; + Message& operator=(const Message&) = delete; + Message& operator=(Message&&) = delete; + + // Helper method for the common case (default segmentCapacity, recording + // the write latency of messages) of IPDL message creation. This helps + // move the malloc and some of the parameter setting out of autogenerated + // code. + static mozilla::UniquePtr IPDLMessage(int32_t routing_id, + msgid_t type, + uint32_t segmentCapacity, + HeaderFlags flags); + + // One-off constructors for special error-handling messages. + static mozilla::UniquePtr ForSyncDispatchError(NestedLevel level); + + NestedLevel nested_level() const { return header()->flags.Level(); } + + PriorityValue priority() const { return header()->flags.Priority(); } + + bool is_constructor() const { return header()->flags.IsConstructor(); } + + // True if this is a synchronous message. + bool is_sync() const { return header()->flags.IsSync(); } + + MessageCompression compress_type() const { + return header()->flags.Compression(); + } + + bool is_lazy_send() const { return header()->flags.IsLazySend(); } + + bool is_reply() const { return header()->flags.IsReply(); } + + bool is_reply_error() const { return header()->flags.IsReplyError(); } + + msgid_t type() const { return header()->type; } + + int32_t routing_id() const { return header()->routing; } + + void set_routing_id(int32_t new_id) { header()->routing = new_id; } + + int32_t transaction_id() const { return header()->txid; } + + void set_transaction_id(int32_t txid) { header()->txid = txid; } + + int32_t seqno() const { return header()->seqno; } + + void set_seqno(int32_t aSeqno) { header()->seqno = aSeqno; } + + const char* name() const { return StringFromIPCMessageType(type()); } + + uint32_t num_handles() const; + + bool is_relay() const { return header()->flags.IsRelay(); } + void set_relay(bool new_relay) { header()->flags.SetRelay(new_relay); } + + template + static bool Dispatch(const Message* msg, T* obj, void (T::*func)()) { + (obj->*func)(); + return true; + } + + template + static bool Dispatch(const Message* msg, T* obj, void (T::*func)() const) { + (obj->*func)(); + return true; + } + + template + static bool Dispatch(const Message* msg, T* obj, + void (T::*func)(const Message&)) { + (obj->*func)(*msg); + return true; + } + + template + static bool Dispatch(const Message* msg, T* obj, + void (T::*func)(const Message&) const) { + (obj->*func)(*msg); + return true; + } + + // We should not be sending messages that are smaller than our header size. + void AssertAsLargeAsHeader() const; + + // UserMessage implementation + size_t GetSizeIfSerialized() const override { return size(); } + bool WillBeRoutedExternally(mojo::core::ports::UserMessageEvent&) override; + + // Write the given footer bytes to the end of the current message. The + // footer's `data_len` will be padded to a multiple of 4 bytes. + void WriteFooter(const void* data, uint32_t data_len); + // Read a footer written with `WriteFooter` from the end of the message, given + // a buffer and the length of the footer. If `truncate` is true, the message + // will be truncated, removing the footer. + [[nodiscard]] bool ReadFooter(void* buffer, uint32_t buffer_len, + bool truncate); + + uint32_t event_footer_size() const { return header()->event_footer_size; } + + void set_event_footer_size(uint32_t size) { + header()->event_footer_size = size; + } + + static int HeaderSize() { return sizeof(Header); } + + // Figure out how big the message starting at range_start is. Returns 0 if + // there's no enough data to determine (i.e., if [range_start, range_end) does + // not contain enough of the message header to know the size). + static uint32_t MessageSize(const char* range_start, const char* range_end) { + return Pickle::MessageSize(HeaderSize(), range_start, range_end); + } + + bool WriteFileHandle(mozilla::UniqueFileHandle handle); + + // WARNING: This method is marked as `const` so it can be called when + // deserializing the message, but will mutate it, consuming the handle. + bool ConsumeFileHandle(PickleIterator* iter, + mozilla::UniqueFileHandle* handle) const; + + // Called when receiving an IPC message to attach file handles which were + // received from IPC. Must only be called when there are no handles on this + // IPC::Message. + void SetAttachedFileHandles(nsTArray handles); + +#if defined(XP_DARWIN) + void set_fd_cookie(uint32_t cookie) { header()->cookie = cookie; } + uint32_t fd_cookie() const { return header()->cookie; } +#endif + + void WritePort(mozilla::ipc::ScopedPort port); + + // This method consumes the port from the message, preventing the message's + // destructor from destroying the port and meaning that future attempts to + // read this port will instead produce an invalid port. + // + // WARNING: This method is marked as `const` so it can be called when + // deserializing the message, but will mutate the message. + bool ConsumePort(PickleIterator* iter, mozilla::ipc::ScopedPort* port) const; + + // Called when loading an IPC message to attach ports which were recieved form + // IPC. Must only be called when there are no ports on this IPC::Message. + void SetAttachedPorts(nsTArray ports); + +#if defined(XP_DARWIN) + bool WriteMachSendRight(mozilla::UniqueMachSendRight port); + + // WARNING: This method is marked as `const` so it can be called when + // deserializing the message, but will mutate it, consuming the send rights. + bool ConsumeMachSendRight(PickleIterator* iter, + mozilla::UniqueMachSendRight* port) const; + + uint32_t num_send_rights() const; +#endif + + uint32_t num_relayed_attachments() const { +#if defined(XP_WIN) + return num_handles(); +#elif defined(XP_DARWIN) + return num_send_rights(); +#else + return 0; +#endif + } + +#ifdef FUZZING_SNAPSHOT + bool IsFuzzMsg() const { return isFuzzMsg; } + void SetFuzzMsg() { isFuzzMsg = true; } +#endif + + friend class Channel; + friend class MessageReplyDeserializer; + friend class SyncMessage; + friend class mozilla::ipc::MiniTransceiver; + +#if !defined(XP_DARWIN) && !defined(FUZZING_SNAPSHOT) + protected: +#endif + + struct Header : Pickle::Header { + int32_t routing; // ID of the view that this message is destined for + msgid_t type; // specifies the user-defined message type + HeaderFlags flags; // specifies control flags for the message + uint32_t num_handles; // the number of handles included with this message +#if defined(XP_DARWIN) + uint32_t cookie; // cookie to ACK that the descriptors have been read. + uint32_t num_send_rights; // the number of mach send rights included with + // this message +#endif + // For sync messages, a transaction ID for message ordering. + int32_t txid; + // Sequence number + int32_t seqno; + // Size of the message's event footer + uint32_t event_footer_size; + }; + + Header* header() { return headerT
(); } + const Header* header() const { return headerT
(); } + + // The set of file handles which are attached to this message. + // + // Mutable, as this array can be mutated during `ReadHandle` when + // deserializing a message. + mutable nsTArray attached_handles_; + + // The set of mojo ports which are attached to this message. + // + // Mutable, as this array can be mutated during `ConsumePort` when + // deserializing a message. + mutable nsTArray attached_ports_; + +#if defined(XP_DARWIN) + // The set of mach send rights which are attached to this message. + // + // Mutable, as this array can be mutated during `ConsumeMachSendRight` when + // deserializing a message. + mutable nsTArray attached_send_rights_; +#endif + +#ifdef FUZZING_SNAPSHOT + bool isFuzzMsg = false; +#endif +}; + +//------------------------------------------------------------------------------ + +} // namespace IPC + +enum SpecialRoutingIDs { + // indicates that we don't have a routing ID yet. + MSG_ROUTING_NONE = kint32min, + + // indicates a general message not sent to a particular tab. + MSG_ROUTING_CONTROL = kint32max +}; + +#define IPC_REPLY_ID 0xFFF0 // Special message id for replies +#define IPC_LOGGING_ID 0xFFF1 // Special message id for logging + +#endif // CHROME_COMMON_IPC_MESSAGE_H__ diff --git a/ipc/chromium/src/chrome/common/ipc_message_utils.cc b/ipc/chromium/src/chrome/common/ipc_message_utils.cc new file mode 100644 index 0000000000..49cb277e5f --- /dev/null +++ b/ipc/chromium/src/chrome/common/ipc_message_utils.cc @@ -0,0 +1,105 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "chrome/common/ipc_message_utils.h" +#include "mozilla/ipc/SharedMemory.h" +#include "mozilla/ipc/SharedMemoryBasic.h" + +namespace IPC { + +static uint32_t kShmemThreshold = 64 * 1024; + +MessageBufferWriter::MessageBufferWriter(MessageWriter* writer, + uint32_t full_len) + : writer_(writer) { + if (full_len > kShmemThreshold) { + shmem_ = new mozilla::ipc::SharedMemoryBasic(); + if (!shmem_->Create(full_len)) { + writer->FatalError("SharedMemory::Create failed!"); + return; + } + if (!shmem_->Map(full_len)) { + writer->FatalError("SharedMemory::Map failed"); + return; + } + if (!shmem_->WriteHandle(writer)) { + writer->FatalError("SharedMemory::WriterHandle failed"); + return; + } + buffer_ = reinterpret_cast(shmem_->memory()); + } + remaining_ = full_len; +} + +MessageBufferWriter::~MessageBufferWriter() { + if (remaining_ != 0) { + writer_->FatalError("didn't fully write message buffer"); + } +} + +bool MessageBufferWriter::WriteBytes(const void* data, uint32_t len) { + MOZ_RELEASE_ASSERT(len == remaining_ || (len % 4) == 0, + "all writes except for the final write must be a multiple " + "of 4 bytes in length due to padding"); + if (len > remaining_) { + writer_->FatalError("MessageBufferWriter overrun"); + return false; + } + remaining_ -= len; + // If we're serializing using a shared memory region, `buffer_` will be + // initialized to point into that region. + if (buffer_) { + memcpy(buffer_, data, len); + buffer_ += len; + return true; + } + return writer_->WriteBytes(data, len); +} + +MessageBufferReader::MessageBufferReader(MessageReader* reader, + uint32_t full_len) + : reader_(reader) { + if (full_len > kShmemThreshold) { + shmem_ = new mozilla::ipc::SharedMemoryBasic(); + if (!shmem_->ReadHandle(reader)) { + reader->FatalError("SharedMemory::ReadHandle failed!"); + return; + } + if (!shmem_->Map(full_len)) { + reader->FatalError("SharedMemory::Map failed"); + return; + } + buffer_ = reinterpret_cast(shmem_->memory()); + } + remaining_ = full_len; +} + +MessageBufferReader::~MessageBufferReader() { + if (remaining_ != 0) { + reader_->FatalError("didn't fully write message buffer"); + } +} + +bool MessageBufferReader::ReadBytesInto(void* data, uint32_t len) { + MOZ_RELEASE_ASSERT(len == remaining_ || (len % 4) == 0, + "all reads except for the final read must be a multiple " + "of 4 bytes in length due to padding"); + if (len > remaining_) { + reader_->FatalError("MessageBufferReader overrun"); + return false; + } + remaining_ -= len; + // If we're serializing using a shared memory region, `buffer_` will be + // initialized to point into that region. + if (buffer_) { + memcpy(data, buffer_, len); + buffer_ += len; + return true; + } + return reader_->ReadBytesInto(data, len); +} + +} // namespace IPC diff --git a/ipc/chromium/src/chrome/common/ipc_message_utils.h b/ipc/chromium/src/chrome/common/ipc_message_utils.h new file mode 100644 index 0000000000..a624aa174a --- /dev/null +++ b/ipc/chromium/src/chrome/common/ipc_message_utils.h @@ -0,0 +1,1092 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_COMMON_IPC_MESSAGE_UTILS_H_ +#define CHROME_COMMON_IPC_MESSAGE_UTILS_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include "ErrorList.h" +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/logging.h" +#include "base/pickle.h" +#include "base/string_util.h" +#include "chrome/common/ipc_message.h" +#include "mozilla/CheckedInt.h" +#include "mozilla/IntegerRange.h" + +#if defined(XP_WIN) +# include +#endif + +template +class RefPtr; +template +class nsCOMPtr; + +namespace mozilla::ipc { +class IProtocol; +template +struct IPDLParamTraits; +class SharedMemory; + +// Implemented in ProtocolUtils.cpp +MOZ_NEVER_INLINE void PickleFatalError(const char* aMsg, IProtocol* aActor); +} // namespace mozilla::ipc + +namespace IPC { + +/** + * Context used to serialize into an IPC::Message. Provides relevant context + * used when serializing. + */ +class MOZ_STACK_CLASS MessageWriter final { + public: + explicit MessageWriter(Message& message, + mozilla::ipc::IProtocol* actor = nullptr) + : message_(message), actor_(actor) {} + + MessageWriter(const MessageWriter&) = delete; + MessageWriter& operator=(const MessageWriter&) = delete; + + mozilla::ipc::IProtocol* GetActor() const { return actor_; } + +#define FORWARD_WRITE(name, type) \ + bool Write##name(const type& result) { return message_.Write##name(result); } + + FORWARD_WRITE(Bool, bool) + FORWARD_WRITE(Int16, int16_t) + FORWARD_WRITE(UInt16, uint16_t) + FORWARD_WRITE(Int, int) + FORWARD_WRITE(Long, long) + FORWARD_WRITE(ULong, unsigned long) + FORWARD_WRITE(Int32, int32_t) + FORWARD_WRITE(UInt32, uint32_t) + FORWARD_WRITE(Int64, int64_t) + FORWARD_WRITE(UInt64, uint64_t) + FORWARD_WRITE(Double, double) + FORWARD_WRITE(IntPtr, intptr_t) + FORWARD_WRITE(UnsignedChar, unsigned char) + FORWARD_WRITE(String, std::string) + FORWARD_WRITE(WString, std::wstring) + +#undef FORWARD_WRITE + + bool WriteData(const char* data, uint32_t length) { + return message_.WriteData(data, length); + } + + bool WriteBytes(const void* data, uint32_t data_len) { + return message_.WriteBytes(data, data_len); + } + + bool WriteBytesZeroCopy(void* data, uint32_t data_len, uint32_t capacity) { + return message_.WriteBytesZeroCopy(data, data_len, capacity); + } + + bool WriteSentinel(uint32_t sentinel) { + return message_.WriteSentinel(sentinel); + } + + bool WriteFileHandle(mozilla::UniqueFileHandle handle) { + return message_.WriteFileHandle(std::move(handle)); + } + + void WritePort(mozilla::ipc::ScopedPort port) { + message_.WritePort(std::move(port)); + } + +#if defined(XP_DARWIN) + bool WriteMachSendRight(mozilla::UniqueMachSendRight port) { + return message_.WriteMachSendRight(std::move(port)); + } +#endif + + void FatalError(const char* aErrorMsg) const { + mozilla::ipc::PickleFatalError(aErrorMsg, actor_); + } + + private: + Message& message_; + mozilla::ipc::IProtocol* actor_; +}; + +/** + * Context used to read data from an IPC::Message. Provides relevant context + * used when deserializing and tracks iteration. + */ +class MOZ_STACK_CLASS MessageReader final { + public: + explicit MessageReader(const Message& message, + mozilla::ipc::IProtocol* actor = nullptr) + : message_(message), iter_(message), actor_(actor) {} + + MessageReader(const MessageReader&) = delete; + MessageReader& operator=(const MessageReader&) = delete; + + mozilla::ipc::IProtocol* GetActor() const { return actor_; } + +#define FORWARD_READ(name, type) \ + [[nodiscard]] bool Read##name(type* result) { \ + return message_.Read##name(&iter_, result); \ + } + + FORWARD_READ(Bool, bool) + FORWARD_READ(Int16, int16_t) + FORWARD_READ(UInt16, uint16_t) + FORWARD_READ(Short, short) + FORWARD_READ(Int, int) + FORWARD_READ(Long, long) + FORWARD_READ(ULong, unsigned long) + FORWARD_READ(Int32, int32_t) + FORWARD_READ(UInt32, uint32_t) + FORWARD_READ(Int64, int64_t) + FORWARD_READ(UInt64, uint64_t) + FORWARD_READ(Double, double) + FORWARD_READ(IntPtr, intptr_t) + FORWARD_READ(UnsignedChar, unsigned char) + FORWARD_READ(String, std::string) + FORWARD_READ(WString, std::wstring) + + // Special version of ReadInt() which rejects negative values + FORWARD_READ(Length, int); + +#undef FORWARD_READ + + [[nodiscard]] bool ReadBytesInto(void* data, uint32_t length) { + return message_.ReadBytesInto(&iter_, data, length); + } + + [[nodiscard]] bool IgnoreBytes(uint32_t length) { + return message_.IgnoreBytes(&iter_, length); + } + + [[nodiscard]] bool ReadSentinel(uint32_t sentinel) { + return message_.ReadSentinel(&iter_, sentinel); + } + + bool IgnoreSentinel() { return message_.IgnoreSentinel(&iter_); } + + bool HasBytesAvailable(uint32_t len) { + return message_.HasBytesAvailable(&iter_, len); + } + + void EndRead() { message_.EndRead(iter_, message_.type()); } + + [[nodiscard]] bool ConsumeFileHandle(mozilla::UniqueFileHandle* handle) { + return message_.ConsumeFileHandle(&iter_, handle); + } + + [[nodiscard]] bool ConsumePort(mozilla::ipc::ScopedPort* port) { + return message_.ConsumePort(&iter_, port); + } + +#if defined(XP_DARWIN) + [[nodiscard]] bool ConsumeMachSendRight(mozilla::UniqueMachSendRight* port) { + return message_.ConsumeMachSendRight(&iter_, port); + } +#endif + + void FatalError(const char* aErrorMsg) const { + mozilla::ipc::PickleFatalError(aErrorMsg, actor_); + } + + private: + const Message& message_; + PickleIterator iter_; + mozilla::ipc::IProtocol* actor_; +}; + +namespace detail { + +// Helper for checking `T::kHasDeprecatedReadParamPrivateConstructor` using a +// fallback when the member isn't defined. +template +inline constexpr auto HasDeprecatedReadParamPrivateConstructor(int) + -> decltype(T::kHasDeprecatedReadParamPrivateConstructor) { + return T::kHasDeprecatedReadParamPrivateConstructor; +} + +template +inline constexpr bool HasDeprecatedReadParamPrivateConstructor(...) { + return false; +} + +} // namespace detail + +/** + * Result type returned from some `ParamTraits::Read` implementations, and + * from `IPC::ReadParam(MessageReader*)`. Either contains the value or + * indicates a failure to deserialize. + * + * This type can be thought of as a variant on `Maybe`, except that it + * unconditionally constructs the underlying value if it is default + * constructible. This helps keep code size down, especially when calling + * outparameter-based ReadParam implementations (bug 1815177). + */ +template || + detail::HasDeprecatedReadParamPrivateConstructor(0)> +class ReadResult { + public: + ReadResult() = default; + + template , int> = 0> + MOZ_IMPLICIT ReadResult(U&& aData) + : mIsOk(true), mData(std::forward(aData)) {} + + template + explicit ReadResult(std::in_place_t, Args&&... aArgs) + : mIsOk(true), mData(std::forward(aArgs)...) {} + + ReadResult(const ReadResult&) = default; + ReadResult(ReadResult&&) = default; + + template , int> = 0> + MOZ_IMPLICIT ReadResult& operator=(U&& aData) { + mIsOk = true; + mData = std::forward(aData); + return *this; + } + + ReadResult& operator=(const ReadResult&) = default; + ReadResult& operator=(ReadResult&&) noexcept = default; + + // Check if the ReadResult contains a valid value. + explicit operator bool() const { return isOk(); } + bool isOk() const { return mIsOk; } + + // Get the data from this ReadResult. + T& get() { + MOZ_ASSERT(mIsOk); + return mData; + } + const T& get() const { + MOZ_ASSERT(mIsOk); + return mData; + } + + T& operator*() { return get(); } + const T& operator*() const { return get(); } + + T* operator->() { return &get(); } + const T* operator->() const { return &get(); } + + // Try to extract a `Maybe` from this ReadResult. + mozilla::Maybe TakeMaybe() { + if (mIsOk) { + mIsOk = false; + return mozilla::Some(std::move(mData)); + } + return mozilla::Nothing(); + } + + // Get the underlying data from this ReadResult, even if not OK. + // + // This is only available for types which are default constructible, and is + // used to optimize old-style `ReadParam` calls. + T& GetStorage() { return mData; } + + // Compliment to `GetStorage` used to set the ReadResult into an OK state + // without constructing the underlying value. + void SetOk(bool aIsOk) { mIsOk = aIsOk; } + + private: + bool mIsOk = false; + T mData{}; +}; + +template +class ReadResult { + public: + ReadResult() = default; + + template , int> = 0> + MOZ_IMPLICIT ReadResult(U&& aData) + : mData(std::in_place, std::forward(aData)) {} + + template + explicit ReadResult(std::in_place_t, Args&&... aArgs) + : mData(std::in_place, std::forward(aArgs)...) {} + + ReadResult(const ReadResult&) = default; + ReadResult(ReadResult&&) = default; + + template , int> = 0> + MOZ_IMPLICIT ReadResult& operator=(U&& aData) { + mData.reset(); + mData.emplace(std::forward(aData)); + return *this; + } + + ReadResult& operator=(const ReadResult&) = default; + ReadResult& operator=(ReadResult&&) noexcept = default; + + // Check if the ReadResult contains a valid value. + explicit operator bool() const { return isOk(); } + bool isOk() const { return mData.isSome(); } + + // Get the data from this ReadResult. + T& get() { return mData.ref(); } + const T& get() const { return mData.ref(); } + + T& operator*() { return get(); } + const T& operator*() const { return get(); } + + T* operator->() { return &get(); } + const T* operator->() const { return &get(); } + + // Try to extract a `Maybe` from this ReadResult. + mozilla::Maybe TakeMaybe() { return std::move(mData); } + + // These methods are only available if the type is default constructible. + T& GetStorage() = delete; + void SetOk(bool aIsOk) = delete; + + private: + mozilla::Maybe mData; +}; + +//----------------------------------------------------------------------------- +// An iterator class for reading the fields contained within a Message. + +class MessageIterator { + public: + explicit MessageIterator(const Message& m) : msg_(m), iter_(m) {} + int NextInt() const { + int val; + if (!msg_.ReadInt(&iter_, &val)) NOTREACHED(); + return val; + } + intptr_t NextIntPtr() const { + intptr_t val; + if (!msg_.ReadIntPtr(&iter_, &val)) NOTREACHED(); + return val; + } + const std::string NextString() const { + std::string val; + if (!msg_.ReadString(&iter_, &val)) NOTREACHED(); + return val; + } + const std::wstring NextWString() const { + std::wstring val; + if (!msg_.ReadWString(&iter_, &val)) NOTREACHED(); + return val; + } + + private: + const Message& msg_; + mutable PickleIterator iter_; +}; + +//----------------------------------------------------------------------------- +// ParamTraits specializations, etc. +// +// The full set of types ParamTraits is specialized upon contains *possibly* +// repeated types: unsigned long may be uint32_t or size_t, unsigned long long +// may be uint64_t or size_t, nsresult may be uint32_t, and so on. You can't +// have ParamTraits *and* ParamTraits if unsigned int +// is uint32_t -- that's multiple definitions, and you can only have one. +// +// You could use #ifs and macro conditions to avoid duplicates, but they'd be +// hairy: heavily dependent upon OS and compiler author choices, forced to +// address all conflicts by hand. Happily there's a better way. The basic +// idea looks like this, where T -> U represents T inheriting from U: +// +// class ParamTraits

+// | +// --> class ParamTraits1

+// | +// --> class ParamTraits2

+// | +// --> class ParamTraitsN

// or however many levels +// +// The default specialization of ParamTraits{M}

is an empty class that +// inherits from ParamTraits{M + 1}

(or nothing in the base case). +// +// Now partition the set of parameter types into sets without duplicates. +// Assign each set of types to a level M. Then specialize ParamTraitsM for +// each of those types. A reference to ParamTraits

will consist of some +// number of empty classes inheriting in sequence, ending in a non-empty +// ParamTraits{N}

. It's okay for the parameter types to be duplicative: +// either name of a type will resolve to the same ParamTraits{N}

. +// +// The nice thing is that because templates are instantiated lazily, if we +// indeed have uint32_t == unsigned int, say, with the former in level N and +// the latter in M > N, ParamTraitsM won't be created (as long as +// nobody uses ParamTraitsM, but why would you), and no duplicate +// code will be compiled or extra symbols generated. It's as efficient at +// runtime as manually figuring out and avoiding conflicts by #ifs. +// +// The scheme we follow below names the various classes according to the types +// in them, and the number of ParamTraits levels is larger, but otherwise it's +// exactly the above idea. +// + +template +struct ParamTraits; + +template +inline void WriteParam(MessageWriter* writer, P&& p) { + ParamTraits>::Write(writer, std::forward

(p)); +} + +namespace detail { + +template +inline constexpr auto ParamTraitsReadUsesOutParam() + -> decltype(ParamTraits

::Read(std::declval(), + std::declval())) { + return true; +} + +template +inline constexpr auto ParamTraitsReadUsesOutParam() + -> decltype(ParamTraits

::Read(std::declval()), bool{}) { + return false; +} + +} // namespace detail + +template +inline bool WARN_UNUSED_RESULT ReadParam(MessageReader* reader, P* p) { + if constexpr (!detail::ParamTraitsReadUsesOutParam

()) { + auto maybe = ParamTraits

::Read(reader); + if (maybe) { + *p = std::move(*maybe); + return true; + } + return false; + } else { + return ParamTraits

::Read(reader, p); + } +} + +template +inline ReadResult

WARN_UNUSED_RESULT ReadParam(MessageReader* reader) { + if constexpr (!detail::ParamTraitsReadUsesOutParam

()) { + return ParamTraits

::Read(reader); + } else { + ReadResult

p; + p.SetOk(ParamTraits

::Read(reader, &p.GetStorage())); + return p; + } +} + +class MOZ_STACK_CLASS MessageBufferWriter { + public: + // Create a MessageBufferWriter to write `full_len` bytes into `writer`. + // If the length exceeds a threshold, a shared memory region may be used + // instead of including the data inline. + // + // NOTE: This does _NOT_ write out the length of the buffer. + // NOTE: Data written this way _MUST_ be read using `MessageBufferReader`. + MessageBufferWriter(MessageWriter* writer, uint32_t full_len); + ~MessageBufferWriter(); + + MessageBufferWriter(const MessageBufferWriter&) = delete; + MessageBufferWriter& operator=(const MessageBufferWriter&) = delete; + + // Write `len` bytes from `data` into the message. + // + // Exactly `full_len` bytes should be written across multiple calls before the + // `MessageBufferWriter` is destroyed. + // + // WARNING: all writes (other than the last write) must be multiples of 4 + // bytes in length. Not doing this will lead to padding being introduced into + // the payload and break things. This can probably be improved in the future + // with deeper integration between `MessageBufferWriter` and `Pickle`. + bool WriteBytes(const void* data, uint32_t len); + + private: + MessageWriter* writer_; + RefPtr shmem_; + char* buffer_ = nullptr; + uint32_t remaining_ = 0; +}; + +class MOZ_STACK_CLASS MessageBufferReader { + public: + // Create a MessageBufferReader to read `full_len` bytes from `reader` which + // were written using `MessageBufferWriter`. + // + // NOTE: This may consume a shared memory region from the message, meaning + // that the same data cannot be read multiple times. + // NOTE: Data read this way _MUST_ be written using `MessageBufferWriter`. + MessageBufferReader(MessageReader* reader, uint32_t full_len); + ~MessageBufferReader(); + + MessageBufferReader(const MessageBufferReader&) = delete; + MessageBufferReader& operator=(const MessageBufferReader&) = delete; + + // Read `count` bytes from the message into `data`. + // + // Exactly `full_len` bytes should be read across multiple calls before the + // `MessageBufferReader` is destroyed. + // + // WARNING: all reads (other than the last read) must be multiples of 4 bytes + // in length. Not doing this will lead to bytes being skipped in the payload + // and break things. This can probably be improved in the future with deeper + // integration between `MessageBufferReader` and `Pickle`. + [[nodiscard]] bool ReadBytesInto(void* data, uint32_t len); + + private: + MessageReader* reader_; + RefPtr shmem_; + const char* buffer_ = nullptr; + uint32_t remaining_ = 0; +}; + +// Whether or not it is safe to serialize the given type using +// `WriteBytesOrShmem`. +template +constexpr bool kUseWriteBytes = + !std::is_same_v>, bool> && + (std::is_integral_v>> || + std::is_floating_point_v>>); + +/** + * Helper for writing a contiguous sequence (such as for a string or array) into + * a message, with optimizations for basic integral and floating point types. + * + * Integral types will be copied into shared memory if the sequence exceeds 64k + * bytes in size. + * + * Values written with this method must be read with `ReadSequenceParam`. + * + * The type parameter specifies the semantics to use, and should generally + * either be `P&&` or `const P&`. The constness of the `data` argument should + * match this parameter. + */ +template +void WriteSequenceParam(MessageWriter* writer, std::remove_reference_t

* data, + size_t length) { + mozilla::CheckedUint32 ipc_length(length); + if (!ipc_length.isValid()) { + writer->FatalError("invalid length passed to WriteSequenceParam"); + return; + } + writer->WriteUInt32(ipc_length.value()); + + if constexpr (kUseWriteBytes

) { + mozilla::CheckedUint32 byte_length = + ipc_length * sizeof(std::remove_reference_t

); + if (!byte_length.isValid()) { + writer->FatalError("invalid byte length in WriteSequenceParam"); + return; + } + MessageBufferWriter buf_writer(writer, byte_length.value()); + buf_writer.WriteBytes(data, byte_length.value()); + } else { + auto* end = data + length; + for (auto* it = data; it != end; ++it) { + WriteParam(writer, std::forward

(*it)); + } + } +} + +template +bool ReadSequenceParamImpl(MessageReader* reader, P* data, uint32_t length) { + if (length == 0) { + return true; + } + if (!data) { + reader->FatalError("allocation failed in ReadSequenceParam"); + return false; + } + + if constexpr (kUseWriteBytes

) { + mozilla::CheckedUint32 byte_length(length); + byte_length *= sizeof(P); + if (!byte_length.isValid()) { + reader->FatalError("invalid byte length in ReadSequenceParam"); + return false; + } + MessageBufferReader buf_reader(reader, byte_length.value()); + return buf_reader.ReadBytesInto(data, byte_length.value()); + } else { + P* end = data + length; + for (auto* it = data; it != end; ++it) { + if (!ReadParam(reader, it)) { + return false; + } + } + return true; + } +} + +template +bool ReadSequenceParamImpl(MessageReader* reader, mozilla::Maybe&& data, + uint32_t length) { + static_assert(!kUseWriteBytes

, + "Cannot return an output iterator if !kUseWriteBytes

"); + static_assert( + std::is_base_of_v::iterator_category>, + "must be Maybe"); + if (length == 0) { + return true; + } + if (!data) { + reader->FatalError("allocation failed in ReadSequenceParam"); + return false; + } + + for (uint32_t i = 0; i < length; ++i) { + auto elt = ReadParam

(reader); + if (!elt) { + return false; + } + *data.ref() = std::move(*elt); + ++data.ref(); + } + return true; +} + +/** + * Helper for reading a contiguous sequence (such as a string or array) into a + * message which was previously written using `WriteSequenceParam`. + * + * The function argument `allocator` will be called with the length of the + * sequence, and must return either a pointer to the memory region which the + * sequence should be read into, or a Maybe of a C++ output iterator which will + * infallibly accept length elements, and append them to the output sequence. + * + * If the type satisfies kUseWriteBytes, output iterators are not supported. + */ +template +bool WARN_UNUSED_RESULT ReadSequenceParam(MessageReader* reader, + F&& allocator) { + uint32_t length = 0; + if (!reader->ReadUInt32(&length)) { + reader->FatalError("failed to read byte length in ReadSequenceParam"); + return false; + } + + return ReadSequenceParamImpl

(reader, allocator(length), length); +} + +// Temporary fallback class to allow types to declare serialization using the +// IPDLParamTraits type class. Will be removed once all remaining +// IPDLParamTraits implementations are gone. (bug 1754009) + +template +struct ParamTraitsIPDLFallback { + template + static auto Write(MessageWriter* writer, R&& p) + -> decltype(mozilla::ipc::IPDLParamTraits

::Write(writer, + writer->GetActor(), + std::forward(p))) { + mozilla::ipc::IPDLParamTraits

::Write(writer, writer->GetActor(), + std::forward(p)); + } + template + static auto Read(MessageReader* reader, R* r) + -> decltype(mozilla::ipc::IPDLParamTraits

::Read(reader, + reader->GetActor(), + r)) { + return mozilla::ipc::IPDLParamTraits

::Read(reader, reader->GetActor(), + r); + } +}; + +// Fundamental types. + +template +struct ParamTraitsFundamental : ParamTraitsIPDLFallback

{}; + +template <> +struct ParamTraitsFundamental { + typedef bool param_type; + static void Write(MessageWriter* writer, const param_type& p) { + writer->WriteBool(p); + } + static bool Read(MessageReader* reader, param_type* r) { + return reader->ReadBool(r); + } +}; + +template <> +struct ParamTraitsFundamental { + typedef int param_type; + static void Write(MessageWriter* writer, const param_type& p) { + writer->WriteInt(p); + } + static bool Read(MessageReader* reader, param_type* r) { + return reader->ReadInt(r); + } +}; + +template <> +struct ParamTraitsFundamental { + typedef long param_type; + static void Write(MessageWriter* writer, const param_type& p) { + writer->WriteLong(p); + } + static bool Read(MessageReader* reader, param_type* r) { + return reader->ReadLong(r); + } +}; + +template <> +struct ParamTraitsFundamental { + typedef unsigned long param_type; + static void Write(MessageWriter* writer, const param_type& p) { + writer->WriteULong(p); + } + static bool Read(MessageReader* reader, param_type* r) { + return reader->ReadULong(r); + } +}; + +template <> +struct ParamTraitsFundamental { + typedef long long param_type; + static void Write(MessageWriter* writer, const param_type& p) { + writer->WriteBytes(&p, sizeof(param_type)); + } + static bool Read(MessageReader* reader, param_type* r) { + return reader->ReadBytesInto(r, sizeof(*r)); + } +}; + +template <> +struct ParamTraitsFundamental { + typedef unsigned long long param_type; + static void Write(MessageWriter* writer, const param_type& p) { + writer->WriteBytes(&p, sizeof(param_type)); + } + static bool Read(MessageReader* reader, param_type* r) { + return reader->ReadBytesInto(r, sizeof(*r)); + } +}; + +template <> +struct ParamTraitsFundamental { + typedef double param_type; + static void Write(MessageWriter* writer, const param_type& p) { + writer->WriteDouble(p); + } + static bool Read(MessageReader* reader, param_type* r) { + return reader->ReadDouble(r); + } +}; + +// Fixed-size types. + +template +struct ParamTraitsFixed : ParamTraitsFundamental

{}; + +template <> +struct ParamTraitsFixed { + typedef int16_t param_type; + static void Write(MessageWriter* writer, const param_type& p) { + writer->WriteInt16(p); + } + static bool Read(MessageReader* reader, param_type* r) { + return reader->ReadInt16(r); + } +}; + +template <> +struct ParamTraitsFixed { + typedef uint16_t param_type; + static void Write(MessageWriter* writer, const param_type& p) { + writer->WriteUInt16(p); + } + static bool Read(MessageReader* reader, param_type* r) { + return reader->ReadUInt16(r); + } +}; + +template <> +struct ParamTraitsFixed { + typedef uint32_t param_type; + static void Write(MessageWriter* writer, const param_type& p) { + writer->WriteUInt32(p); + } + static bool Read(MessageReader* reader, param_type* r) { + return reader->ReadUInt32(r); + } +}; + +template <> +struct ParamTraitsFixed { + typedef int64_t param_type; + static void Write(MessageWriter* writer, const param_type& p) { + writer->WriteInt64(p); + } + static bool Read(MessageReader* reader, param_type* r) { + return reader->ReadInt64(r); + } +}; + +template <> +struct ParamTraitsFixed { + typedef uint64_t param_type; + static void Write(MessageWriter* writer, const param_type& p) { + writer->WriteInt64(static_cast(p)); + } + static bool Read(MessageReader* reader, param_type* r) { + return reader->ReadInt64(reinterpret_cast(r)); + } +}; + +// std::* types. + +template +struct ParamTraitsStd : ParamTraitsFixed

{}; + +template +struct ParamTraitsStd> { + typedef std::basic_string param_type; + static void Write(MessageWriter* writer, const param_type& p) { + WriteSequenceParam(writer, p.data(), p.size()); + } + static bool Read(MessageReader* reader, param_type* r) { + return ReadSequenceParam(reader, [&](uint32_t length) -> T* { + r->resize(length); + return r->data(); + }); + } +}; + +template +struct ParamTraitsStd> { + typedef std::map param_type; + static void Write(MessageWriter* writer, const param_type& p) { + WriteParam(writer, static_cast(p.size())); + typename param_type::const_iterator iter; + for (iter = p.begin(); iter != p.end(); ++iter) { + WriteParam(writer, iter->first); + WriteParam(writer, iter->second); + } + } + static bool Read(MessageReader* reader, param_type* r) { + int size; + if (!ReadParam(reader, &size) || size < 0) return false; + for (int i = 0; i < size; ++i) { + K k; + if (!ReadParam(reader, &k)) return false; + V& value = (*r)[k]; + if (!ReadParam(reader, &value)) return false; + } + return true; + } +}; + +// Windows-specific types. + +template +struct ParamTraitsWindows : ParamTraitsStd

{}; + +#if defined(XP_WIN) +template <> +struct ParamTraitsWindows { + static_assert(sizeof(HANDLE) == sizeof(intptr_t), "Wrong size for HANDLE?"); + + static void Write(MessageWriter* writer, HANDLE p) { + writer->WriteIntPtr(reinterpret_cast(p)); + } + static bool Read(MessageReader* reader, HANDLE* r) { + return reader->ReadIntPtr(reinterpret_cast(r)); + } +}; + +template <> +struct ParamTraitsWindows { + static_assert(sizeof(HWND) == sizeof(intptr_t), "Wrong size for HWND?"); + + static void Write(MessageWriter* writer, HWND p) { + writer->WriteIntPtr(reinterpret_cast(p)); + } + static bool Read(MessageReader* reader, HWND* r) { + return reader->ReadIntPtr(reinterpret_cast(r)); + } +}; +#endif // defined(XP_WIN) + +// Various ipc/chromium types. + +template +struct ParamTraitsIPC : ParamTraitsWindows

{}; + +// `UniqueFileHandle` may be serialized over IPC channels. On the receiving +// side, the UniqueFileHandle is a valid duplicate of the handle which was +// transmitted. +// +// When sending a UniqueFileHandle, the handle must be valid at the time of +// transmission. As transmission is asynchronous, this requires passing +// ownership of the handle to IPC. +// +// A UniqueFileHandle may only be read once. After it has been read once, it +// will be consumed, and future reads will return an invalid handle. +template <> +struct ParamTraitsIPC { + typedef mozilla::UniqueFileHandle param_type; + static void Write(MessageWriter* writer, param_type&& p) { + const bool valid = p != nullptr; + WriteParam(writer, valid); + if (valid) { + if (!writer->WriteFileHandle(std::move(p))) { + writer->FatalError("Too many file handles for one message!"); + NOTREACHED() << "Too many file handles for one message!"; + } + } + } + static bool Read(MessageReader* reader, param_type* r) { + bool valid; + if (!ReadParam(reader, &valid)) { + reader->FatalError("Error reading file handle validity"); + return false; + } + + if (!valid) { + *r = nullptr; + return true; + } + + if (!reader->ConsumeFileHandle(r)) { + reader->FatalError("File handle not found in message!"); + return false; + } + return true; + } +}; + +#if defined(XP_DARWIN) +// `UniqueMachSendRight` may be serialized over IPC channels. On the receiving +// side, the UniqueMachSendRight is the local name of the right which was +// transmitted. +// +// When sending a UniqueMachSendRight, the right must be valid at the time of +// transmission. As transmission is asynchronous, this requires passing +// ownership of the handle to IPC. +// +// A UniqueMachSendRight may only be read once. After it has been read once, it +// will be consumed, and future reads will return an invalid right. +template <> +struct ParamTraitsIPC { + typedef mozilla::UniqueMachSendRight param_type; + static void Write(MessageWriter* writer, param_type&& p) { + const bool valid = p != nullptr; + WriteParam(writer, valid); + if (valid) { + if (!writer->WriteMachSendRight(std::move(p))) { + writer->FatalError("Too many mach send rights for one message!"); + NOTREACHED() << "Too many mach send rights for one message!"; + } + } + } + static bool Read(MessageReader* reader, param_type* r) { + bool valid; + if (!ReadParam(reader, &valid)) { + reader->FatalError("Error reading mach send right validity"); + return false; + } + + if (!valid) { + *r = nullptr; + return true; + } + + if (!reader->ConsumeMachSendRight(r)) { + reader->FatalError("Mach send right not found in message!"); + return false; + } + return true; + } +}; +#endif + +// Mozilla-specific types. + +template +struct ParamTraitsMozilla : ParamTraitsIPC

{}; + +// Sending-only specialization for mozilla::Span. Uses an identical +// serialization format as `const nsTArray&`. +template +struct ParamTraitsMozilla> { + static void Write(MessageWriter* writer, mozilla::Span p) { + WriteSequenceParam(writer, p.Elements(), p.Length()); + } +}; + +template <> +struct ParamTraitsMozilla { + typedef nsresult param_type; + static void Write(MessageWriter* writer, const param_type& p) { + writer->WriteUInt32(static_cast(p)); + } + static bool Read(MessageReader* reader, param_type* r) { + return reader->ReadUInt32(reinterpret_cast(r)); + } +}; + +// See comments for the IPDLParamTraits specializations for RefPtr and +// nsCOMPtr for more details. +template +struct ParamTraitsMozilla> { + static void Write(MessageWriter* writer, const RefPtr& p) { + ParamTraits::Write(writer, p.get()); + } + + static bool Read(MessageReader* reader, RefPtr* r) { + return ParamTraits::Read(reader, r); + } +}; + +template +struct ParamTraitsMozilla> { + static void Write(MessageWriter* writer, const nsCOMPtr& p) { + ParamTraits::Write(writer, p.get()); + } + + static bool Read(MessageReader* reader, nsCOMPtr* r) { + RefPtr refptr; + if (!ParamTraits::Read(reader, &refptr)) { + return false; + } + *r = std::move(refptr); + return true; + } +}; + +template +struct ParamTraitsMozilla> { + static void Write(MessageWriter* writer, const mozilla::NotNull& p) { + ParamTraits::Write(writer, p.get()); + } + + static ReadResult> Read(MessageReader* reader) { + auto ptr = ReadParam(reader); + if (!ptr) { + return {}; + } + if (!*ptr) { + reader->FatalError("unexpected null value"); + return {}; + } + return mozilla::WrapNotNull(std::move(*ptr)); + } +}; + +// Finally, ParamTraits itself. + +template +struct ParamTraits : ParamTraitsMozilla

{}; + +} // namespace IPC + +#endif // CHROME_COMMON_IPC_MESSAGE_UTILS_H_ diff --git a/ipc/chromium/src/chrome/common/mach_ipc_mac.cc b/ipc/chromium/src/chrome/common/mach_ipc_mac.cc new file mode 100644 index 0000000000..2fa1a47fd3 --- /dev/null +++ b/ipc/chromium/src/chrome/common/mach_ipc_mac.cc @@ -0,0 +1,93 @@ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/common/mach_ipc_mac.h" + +#include "base/logging.h" +#include "mozilla/UniquePtrExtensions.h" +#include "nsDebug.h" + +namespace { +// Struct for sending a Mach message with a single port. +struct MachSinglePortMessage { + mach_msg_header_t header; + mach_msg_body_t body; + mach_msg_port_descriptor_t data; +}; + +// Struct for receiving a Mach message with a single port. +struct MachSinglePortMessageTrailer : MachSinglePortMessage { + mach_msg_audit_trailer_t trailer; +}; +} // namespace + +//============================================================================== +kern_return_t MachSendPortSendRight( + mach_port_t endpoint, mach_port_t attachment, + mozilla::Maybe opt_timeout, + mach_msg_type_name_t endpoint_disposition) { + mach_msg_option_t opts = MACH_SEND_MSG; + mach_msg_timeout_t timeout = MACH_MSG_TIMEOUT_NONE; + if (opt_timeout) { + opts |= MACH_SEND_TIMEOUT; + timeout = *opt_timeout; + } + + MachSinglePortMessage send_msg{}; + send_msg.header.msgh_bits = + MACH_MSGH_BITS(endpoint_disposition, 0) | MACH_MSGH_BITS_COMPLEX; + send_msg.header.msgh_size = sizeof(send_msg); + send_msg.header.msgh_remote_port = endpoint; + send_msg.header.msgh_local_port = MACH_PORT_NULL; + send_msg.header.msgh_reserved = 0; + send_msg.header.msgh_id = 0; + send_msg.body.msgh_descriptor_count = 1; + send_msg.data.name = attachment; + send_msg.data.disposition = MACH_MSG_TYPE_COPY_SEND; + send_msg.data.type = MACH_MSG_PORT_DESCRIPTOR; + + return mach_msg(&send_msg.header, opts, send_msg.header.msgh_size, 0, + MACH_PORT_NULL, timeout, MACH_PORT_NULL); +} + +//============================================================================== +kern_return_t MachReceivePortSendRight( + const mozilla::UniqueMachReceiveRight& endpoint, + mozilla::Maybe opt_timeout, + mozilla::UniqueMachSendRight* attachment, audit_token_t* audit_token) { + mach_msg_option_t opts = MACH_RCV_MSG | + MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0) | + MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT); + mach_msg_timeout_t timeout = MACH_MSG_TIMEOUT_NONE; + if (opt_timeout) { + opts |= MACH_RCV_TIMEOUT; + timeout = *opt_timeout; + } + + MachSinglePortMessageTrailer recv_msg{}; + recv_msg.header.msgh_local_port = endpoint.get(); + recv_msg.header.msgh_size = sizeof(recv_msg); + + kern_return_t kr = + mach_msg(&recv_msg.header, opts, 0, recv_msg.header.msgh_size, + endpoint.get(), timeout, MACH_PORT_NULL); + if (kr != KERN_SUCCESS) { + return kr; + } + + if (NS_WARN_IF(!(recv_msg.header.msgh_bits & MACH_MSGH_BITS_COMPLEX)) || + NS_WARN_IF(recv_msg.body.msgh_descriptor_count != 1) || + NS_WARN_IF(recv_msg.data.type != MACH_MSG_PORT_DESCRIPTOR) || + NS_WARN_IF(recv_msg.data.disposition != MACH_MSG_TYPE_MOVE_SEND) || + NS_WARN_IF(recv_msg.header.msgh_size != sizeof(MachSinglePortMessage))) { + mach_msg_destroy(&recv_msg.header); + return KERN_FAILURE; // Invalid message format + } + + attachment->reset(recv_msg.data.name); + if (audit_token) { + *audit_token = recv_msg.trailer.msgh_audit; + } + return KERN_SUCCESS; +} diff --git a/ipc/chromium/src/chrome/common/mach_ipc_mac.h b/ipc/chromium/src/chrome/common/mach_ipc_mac.h new file mode 100644 index 0000000000..c9af70c0c6 --- /dev/null +++ b/ipc/chromium/src/chrome/common/mach_ipc_mac.h @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_MACH_IPC_MAC_H_ +#define BASE_MACH_IPC_MAC_H_ + +#include +#include +#include +#include + +#include "mozilla/Maybe.h" +#include "mozilla/Result.h" +#include "mozilla/UniquePtrExtensions.h" + +//============================================================================== +// Helper function for sending a minimal mach IPC messages with a single send +// right attached. The endpoint will not be consumed unless the +// `endpoint_disposition` argument is set to a consuming disposition, and +// `KERN_SUCCESS` is returned. +kern_return_t MachSendPortSendRight( + mach_port_t endpoint, mach_port_t attachment, + mozilla::Maybe opt_timeout, + mach_msg_type_name_t endpoint_disposition = MACH_MSG_TYPE_COPY_SEND); + +//============================================================================== +// Helper function for receiving a minimal mach IPC message with a single send +// right attached. +// If the `audit_token` parameter is provided, it will be populated with the +// sender's audit token, which can be used to verify the identity of the sender. +kern_return_t MachReceivePortSendRight( + const mozilla::UniqueMachReceiveRight& endpoint, + mozilla::Maybe opt_timeout, + mozilla::UniqueMachSendRight* attachment, + audit_token_t* audit_token = nullptr); + +#endif // BASE_MACH_IPC_MAC_H_ diff --git a/ipc/chromium/src/chrome/common/mach_message_source_mac.cc b/ipc/chromium/src/chrome/common/mach_message_source_mac.cc new file mode 100644 index 0000000000..d3be7efdb5 --- /dev/null +++ b/ipc/chromium/src/chrome/common/mach_message_source_mac.cc @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/common/mach_message_source_mac.h" + +#include "base/logging.h" + +MachMessageSource::MachMessageSource(mach_port_t port, + MachPortListener* msg_listener, + bool* success) { + DCHECK(msg_listener); + DCHECK(success); + DCHECK(port != MACH_PORT_NULL); + + CFMachPortContext port_context = {0}; + port_context.info = msg_listener; + + scoped_cftyperef cf_mach_port_ref(CFMachPortCreateWithPort( + kCFAllocatorDefault, port, MachMessageSource::OnReceiveMachMessage, + &port_context, NULL)); + + if (cf_mach_port_ref.get() == NULL) { + CHROMIUM_LOG(WARNING) << "CFMachPortCreate failed"; + *success = false; + return; + } + + // Create a RL source. + machport_runloop_ref_.reset(CFMachPortCreateRunLoopSource( + kCFAllocatorDefault, cf_mach_port_ref.get(), 0)); + + if (machport_runloop_ref_.get() == NULL) { + CHROMIUM_LOG(WARNING) << "CFMachPortCreateRunLoopSource failed"; + *success = false; + return; + } + + CFRunLoopAddSource(CFRunLoopGetCurrent(), machport_runloop_ref_.get(), + kCFRunLoopCommonModes); + *success = true; +} + +MachMessageSource::~MachMessageSource() { + CFRunLoopRemoveSource(CFRunLoopGetCurrent(), machport_runloop_ref_.get(), + kCFRunLoopCommonModes); +} + +// static +void MachMessageSource::OnReceiveMachMessage(CFMachPortRef port, void* msg, + CFIndex size, void* closure) { + MachPortListener* msg_listener = static_cast(closure); + size_t msg_size = (size < 0) ? 0 : static_cast(size); + DCHECK(msg && msg_size > 0); // this should never happen! + + if (msg_listener && msg && msg_size > 0) { + msg_listener->OnMachMessageReceived(msg, msg_size); + } +} diff --git a/ipc/chromium/src/chrome/common/mach_message_source_mac.h b/ipc/chromium/src/chrome/common/mach_message_source_mac.h new file mode 100644 index 0000000000..92ce0a73d4 --- /dev/null +++ b/ipc/chromium/src/chrome/common/mach_message_source_mac.h @@ -0,0 +1,60 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_COMMON_MACH_MESSAGE_SOURCE_MAC_H_ +#define CHROME_COMMON_MACH_MESSAGE_SOURCE_MAC_H_ + +#include + +#include "base/scoped_cftyperef.h" + +// Handles registering and cleaning up after a CFRunloopSource for a Mach port. +// Messages received on the port are piped through to a delegate. +// +// Example: +// class MyListener : public MachMessageSource::MachPortListener { +// public: +// void OnMachMessageReceived(void* mach_msg, size_t size) { +// printf("received message on Mach port\n"); +// } +// }; +// +// mach_port_t a_port = ...; +// MyListener listener; +// bool success = false; +// MachMessageSource message_source(port, listener, &success); +// +// if (!success) { +// exit(1); // Couldn't register mach runloop source. +// } +// +// CFRunLoopRun(); // Process messages on runloop... +class MachMessageSource { + public: + // Classes that want to listen on a Mach port can implement + // OnMachMessageReceived, |mach_msg| is a pointer to the raw message data and + // |size| is the buffer size; + class MachPortListener { + public: + virtual void OnMachMessageReceived(void* mach_msg, size_t size) = 0; + }; + + // |listener| is a week reference passed to CF, it needs to remain in + // existence till this object is destroeyd. + MachMessageSource(mach_port_t port, MachPortListener* listener, + bool* success); + ~MachMessageSource(); + + private: + // Called by CF when a new message arrives on the Mach port. + static void OnReceiveMachMessage(CFMachPortRef port, void* msg, CFIndex size, + void* closure); + + scoped_cftyperef machport_runloop_ref_; + DISALLOW_COPY_AND_ASSIGN(MachMessageSource); +}; + +#endif // CHROME_COMMON_MACH_MESSAGE_SOURCE_MAC_H_ diff --git a/ipc/chromium/src/chrome/common/process_watcher.h b/ipc/chromium/src/chrome/common/process_watcher.h new file mode 100644 index 0000000000..f1ccae6310 --- /dev/null +++ b/ipc/chromium/src/chrome/common/process_watcher.h @@ -0,0 +1,38 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CHROME_COMMON_PROCESS_WATCHER_H_ +#define CHROME_COMMON_PROCESS_WATCHER_H_ + +#include "base/basictypes.h" +#include "base/process_util.h" + +class ProcessWatcher { + public: + // This method ensures that the specified process eventually terminates, and + // then it closes the given process handle. + // + // It assumes that the process has already been signalled to exit, and it + // begins by waiting a small amount of time for it to exit. If the process + // does not appear to have exited, then this function starts to become + // aggressive about ensuring that the process terminates. + // + // This method does not block the calling thread. + // + // NOTE: The process handle must have been opened with the PROCESS_TERMINATE + // and SYNCHRONIZE permissions. + // + static void EnsureProcessTerminated(base::ProcessHandle process_handle, + bool force = true); + + private: + // Do not instantiate this class. + ProcessWatcher(); + + DISALLOW_COPY_AND_ASSIGN(ProcessWatcher); +}; + +#endif // CHROME_COMMON_PROCESS_WATCHER_H_ diff --git a/ipc/chromium/src/chrome/common/process_watcher_posix_sigchld.cc b/ipc/chromium/src/chrome/common/process_watcher_posix_sigchld.cc new file mode 100644 index 0000000000..8f62dfdbda --- /dev/null +++ b/ipc/chromium/src/chrome/common/process_watcher_posix_sigchld.cc @@ -0,0 +1,257 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include +#include +#include +#include +#include +#include + +#include "base/eintr_wrapper.h" +#include "base/message_loop.h" +#include "base/process_util.h" +#include "prenv.h" + +#include "chrome/common/process_watcher.h" + +// Maximum amount of time (in milliseconds) to wait for the process to exit. +// XXX/cjones: fairly arbitrary, chosen to match process_watcher_win.cc +static constexpr int kMaxWaitMs = 2000; + +// This is also somewhat arbitrary, but loosely based on Try results. +// See also toolkit.asyncshutdown.crash_timeout (currently 60s) after +// which the parent process will be killed. +#ifdef MOZ_CODE_COVERAGE +// Code coverage instrumentation can be slow (especially when writing +// out data, which has to take a lock on the data files). +static constexpr int kShutdownWaitMs = 80000; +#elif defined(MOZ_ASAN) || defined(MOZ_TSAN) +// Sanitizers slow things down in some cases; see bug 1806224. +static constexpr int kShutdownWaitMs = 40000; +#else +static constexpr int kShutdownWaitMs = 8000; +#endif + +namespace { + +class ChildReaper : public base::MessagePumpLibevent::SignalEvent, + public base::MessagePumpLibevent::SignalWatcher { + public: + explicit ChildReaper(pid_t process) : process_(process) {} + + virtual ~ChildReaper() { + // subclasses should have cleaned up |process_| already + DCHECK(!process_); + + // StopCatching() is implicit + } + + virtual void OnSignal(int sig) override { + DCHECK(SIGCHLD == sig); + DCHECK(process_); + + // this may be the SIGCHLD for a process other than |process_| + if (base::IsProcessDead(process_)) { + process_ = 0; + StopCatching(); + } + } + + protected: + void WaitForChildExit() { + CHECK(process_); + while (!base::IsProcessDead(process_, true)) { + // It doesn't matter if this is interrupted; we just need to + // wait for some amount of time while the other process status + // event is (hopefully) handled. This is used only during an + // error case at shutdown, so a 1s wait won't be too noticeable. + sleep(1); + } + } + + pid_t process_; + + private: + ChildReaper(const ChildReaper&) = delete; + + const ChildReaper& operator=(const ChildReaper&) = delete; +}; + +// Fear the reaper +class ChildGrimReaper : public ChildReaper, public mozilla::Runnable { + public: + explicit ChildGrimReaper(pid_t process) + : ChildReaper(process), mozilla::Runnable("ChildGrimReaper") {} + + virtual ~ChildGrimReaper() { + if (process_) KillProcess(); + } + + NS_IMETHOD Run() override { + // we may have already been signaled by the time this runs + if (process_) KillProcess(); + + return NS_OK; + } + + private: + void KillProcess() { + DCHECK(process_); + + if (base::IsProcessDead(process_)) { + process_ = 0; + return; + } + + if (0 == kill(process_, SIGKILL)) { + // XXX this will block for whatever amount of time it takes the + // XXX OS to tear down the process's resources. might need to + // XXX rethink this if it proves expensive + WaitForChildExit(); + } else { + CHROMIUM_LOG(ERROR) << "Failed to deliver SIGKILL to " << process_ << "!" + << "(" << errno << ")."; + } + process_ = 0; + } + + ChildGrimReaper(const ChildGrimReaper&) = delete; + + const ChildGrimReaper& operator=(const ChildGrimReaper&) = delete; +}; + +class ChildLaxReaper : public ChildReaper, + public MessageLoop::DestructionObserver { + public: + explicit ChildLaxReaper(pid_t process) : ChildReaper(process) {} + + virtual ~ChildLaxReaper() { + // WillDestroyCurrentMessageLoop() should have reaped process_ already + DCHECK(!process_); + } + + virtual void OnSignal(int sig) override { + ChildReaper::OnSignal(sig); + + if (!process_) { + MessageLoop::current()->RemoveDestructionObserver(this); + delete this; + } + } + + virtual void WillDestroyCurrentMessageLoop() override { + DCHECK(process_); + if (!process_) { + return; + } + + // Exception for the fake hang tests in ipc/glue/test/browser + if (!PR_GetEnv("MOZ_TEST_CHILD_EXIT_HANG")) { + CrashProcessIfHanging(); + } + if (process_) { + WaitForChildExit(); + process_ = 0; + } + + // XXX don't think this is necessary, since destruction can only + // be observed once, but can't hurt + MessageLoop::current()->RemoveDestructionObserver(this); + delete this; + } + + private: + ChildLaxReaper(const ChildLaxReaper&) = delete; + + void CrashProcessIfHanging() { + if (base::IsProcessDead(process_)) { + process_ = 0; + return; + } + + // If child processes seems to be hanging on shutdown, wait for a + // reasonable time. The wait is global instead of per-process + // because the child processes should be shutting down in + // parallel, and also we're potentially racing global timeouts + // like nsTerminator. (The counter doesn't need to be atomic; + // this is always called on the I/O thread.) + static int sWaitMs = kShutdownWaitMs; + if (sWaitMs > 0) { + CHROMIUM_LOG(WARNING) + << "Process " << process_ + << " may be hanging at shutdown; will wait for up to " << sWaitMs + << "ms"; + } + // There isn't a way to do a time-limited wait that's both + // portable and doesn't require messing with signals. Instead, we + // sleep in short increments and poll the process status. + while (sWaitMs > 0) { + static constexpr int kWaitTickMs = 200; + struct timespec ts = {kWaitTickMs / 1000, (kWaitTickMs % 1000) * 1000000}; + HANDLE_EINTR(nanosleep(&ts, &ts)); + sWaitMs -= kWaitTickMs; + + if (base::IsProcessDead(process_)) { + process_ = 0; + return; + } + } + + // We want TreeHerder to flag this log line as an error, so that + // this is more obviously a deliberate crash; "fatal error" is one + // of the strings it looks for. + CHROMIUM_LOG(ERROR) + << "Process " << process_ + << " hanging at shutdown; attempting crash report (fatal error)."; + + kill(process_, SIGABRT); + } + + const ChildLaxReaper& operator=(const ChildLaxReaper&) = delete; +}; + +} // namespace + +/** + * Do everything possible to ensure that |process| has been reaped + * before this process exits. + * + * |grim| decides how strict to be with the child's shutdown. + * + * | child exit timeout | upon parent shutdown: + * +--------------------+---------------------------------- + * force=true | 2 seconds | kill(child, SIGKILL) + * force=false | infinite | waitpid(child) + * + * If a child process doesn't shut down properly, and |grim=false| + * used, then the parent will wait on the child forever. So, + * |force=false| is expected to be used when an external entity can be + * responsible for terminating hung processes, e.g. automated test + * harnesses. + */ +void ProcessWatcher::EnsureProcessTerminated(base::ProcessHandle process, + bool force) { + DCHECK(process != base::GetCurrentProcId()); + DCHECK(process > 0); + + if (base::IsProcessDead(process)) return; + + MessageLoopForIO* loop = MessageLoopForIO::current(); + if (force) { + RefPtr reaper = new ChildGrimReaper(process); + + loop->CatchSignal(SIGCHLD, reaper, reaper); + // |loop| takes ownership of |reaper| + loop->PostDelayedTask(reaper.forget(), kMaxWaitMs); + } else { + ChildLaxReaper* reaper = new ChildLaxReaper(process); + + loop->CatchSignal(SIGCHLD, reaper, reaper); + // |reaper| destroys itself after destruction notification + loop->AddDestructionObserver(reaper); + } +} diff --git a/ipc/chromium/src/chrome/common/process_watcher_win.cc b/ipc/chromium/src/chrome/common/process_watcher_win.cc new file mode 100644 index 0000000000..aa05ca4d68 --- /dev/null +++ b/ipc/chromium/src/chrome/common/process_watcher_win.cc @@ -0,0 +1,252 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "chrome/common/process_watcher.h" + +#include +#include +#include +#include "base/message_loop.h" +#include "base/object_watcher.h" +#include "prenv.h" + +// Maximum amount of time (in milliseconds) to wait for the process to exit. +static constexpr int kWaitInterval = 2000; + +// This is somewhat arbitrary, but based on Try run results. When +// changing this, be aware of toolkit.asyncshutdown.crash_timeout +// (currently 60s), after which the parent process will be killed. +#ifdef MOZ_CODE_COVERAGE +// Child processes seem to take longer to shut down on ccov builds, at +// least in the wdspec tests; ~20s has been observed, and we'll spam +// false positives unless this is increased. +static constexpr DWORD kShutdownWaitMs = 80000; +#elif defined(MOZ_ASAN) || defined(MOZ_TSAN) +// Sanitizers also slow things down in some cases; see bug 1806224. +static constexpr DWORD kShutdownWaitMs = 40000; +#else +static constexpr DWORD kShutdownWaitMs = 20000; +#endif + +namespace { + +static bool IsProcessDead(base::ProcessHandle process) { + return WaitForSingleObject(process, 0) == WAIT_OBJECT_0; +} + +class ChildReaper : public mozilla::Runnable, + public base::ObjectWatcher::Delegate, + public MessageLoop::DestructionObserver { + public: + explicit ChildReaper(base::ProcessHandle process, bool force) + : mozilla::Runnable("ChildReaper"), process_(process), force_(force) { + watcher_.StartWatching(process_, this); + } + + virtual ~ChildReaper() { + if (process_) { + KillProcess(); + DCHECK(!process_) << "Make sure to close the handle."; + } + } + + // MessageLoop::DestructionObserver ----------------------------------------- + + virtual void WillDestroyCurrentMessageLoop() { + MOZ_ASSERT(!force_); + if (process_) { + // Exception for the fake hang tests in ipc/glue/test/browser + if (!PR_GetEnv("MOZ_TEST_CHILD_EXIT_HANG")) { + CrashProcessIfHanging(); + } + WaitForSingleObject(process_, INFINITE); + base::CloseProcessHandle(process_); + process_ = 0; + + MessageLoop::current()->RemoveDestructionObserver(this); + delete this; + } + } + + // Task --------------------------------------------------------------------- + + NS_IMETHOD Run() override { + MOZ_ASSERT(force_); + if (process_) { + KillProcess(); + } + return NS_OK; + } + + // MessageLoop::Watcher ----------------------------------------------------- + + virtual void OnObjectSignaled(HANDLE object) { + // When we're called from KillProcess, the ObjectWatcher may still be + // watching. the process handle, so make sure it has stopped. + watcher_.StopWatching(); + + base::CloseProcessHandle(process_); + process_ = 0; + + if (!force_) { + MessageLoop::current()->RemoveDestructionObserver(this); + delete this; + } + } + + private: + void KillProcess() { + MOZ_ASSERT(force_); + + // OK, time to get frisky. We don't actually care when the process + // terminates. We just care that it eventually terminates, and that's what + // TerminateProcess should do for us. Don't check for the result code since + // it fails quite often. This should be investigated eventually. + TerminateProcess(process_, base::PROCESS_END_PROCESS_WAS_HUNG); + + // Now, just cleanup as if the process exited normally. + OnObjectSignaled(process_); + } + + void CrashProcessIfHanging() { + if (IsProcessDead(process_)) { + return; + } + DWORD pid = GetProcessId(process_); + DCHECK(pid != 0); + + // If child processes seems to be hanging on shutdown, wait for a + // reasonable time. The wait is global instead of per-process + // because the child processes should be shutting down in + // parallel, and also we're potentially racing global timeouts + // like nsTerminator. (The counter doesn't need to be atomic; + // this is always called on the I/O thread.) + static DWORD sWaitMs = kShutdownWaitMs; + if (sWaitMs > 0) { + CHROMIUM_LOG(WARNING) + << "Process " << pid + << " may be hanging at shutdown; will wait for up to " << sWaitMs + << "ms"; + } + const auto beforeWait = mozilla::TimeStamp::NowLoRes(); + const DWORD waitStatus = WaitForSingleObject(process_, sWaitMs); + + const double elapsed = + (mozilla::TimeStamp::NowLoRes() - beforeWait).ToMilliseconds(); + sWaitMs -= static_cast( + std::clamp(elapsed, 0.0, static_cast(sWaitMs))); + + switch (waitStatus) { + case WAIT_TIMEOUT: + // The process is still running. + break; + case WAIT_OBJECT_0: + // The process exited. + return; + case WAIT_FAILED: + CHROMIUM_LOG(ERROR) << "Waiting for process " << pid + << " failed; error " << GetLastError(); + DCHECK(false) << "WaitForSingleObject failed"; + // Process status unclear; assume it's gone. + return; + default: + DCHECK(false) << "WaitForSingleObject returned " << waitStatus; + // Again, not clear what's happening so avoid touching the process + return; + } + + // We want TreeHerder to flag this log line as an error, so that + // this is more obviously a deliberate crash; "fatal error" is one + // of the strings it looks for. + CHROMIUM_LOG(ERROR) + << "Process " << pid + << " hanging at shutdown; attempting crash report (fatal error)"; + + // We're going to use CreateRemoteThread to call DbgBreakPoint in + // the target process; it's in a "known DLL" so it should be at + // the same address in all processes. (Normal libraries, like + // xul.dll, are usually at the same address but can be relocated + // in case of conflict.) + // + // DbgBreakPoint doesn't take an argument, so we can give it an + // arbitrary value to make it easier to identify these crash + // reports. (reinterpret_cast isn't constexpr, so this is + // declared as an integer and cast to the required type later.) + // The primary use case for all of this is in CI, where we'll also + // have log messages, but if these crashes end up in Socorro in + // significant numbers then we'll be able to look for this value. + static constexpr uint64_t kIpcMagic = 0x43504900435049; + + const HMODULE ntdll = GetModuleHandleW(L"ntdll.dll"); + if (!ntdll) { + CHROMIUM_LOG(ERROR) << "couldn't find ntdll.dll: error " + << GetLastError(); + return; + } + const auto dbgBreak = reinterpret_cast( + GetProcAddress(ntdll, "DbgBreakPoint")); + if (!dbgBreak) { + CHROMIUM_LOG(ERROR) << "couldn't find DbgBreakPoint: error " + << GetLastError(); + return; + } + + const DWORD rights = PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | + PROCESS_VM_OPERATION | PROCESS_VM_WRITE | + PROCESS_VM_READ; + HANDLE process_priv = nullptr; + if (!DuplicateHandle(GetCurrentProcess(), process_, GetCurrentProcess(), + &process_priv, rights, /* inherit */ FALSE, + /* options */ 0)) { + const auto error = GetLastError(); + CHROMIUM_LOG(ERROR) << "OpenProcess: error " << error; + } else { + DCHECK(process_priv); + HANDLE thread = + CreateRemoteThread(process_priv, /* sec attr */ nullptr, + /* stack */ 0, dbgBreak, (LPVOID)kIpcMagic, + /* flags */ 0, nullptr); + if (!thread) { + const auto error = GetLastError(); + CHROMIUM_LOG(ERROR) << "CreateRemoteThread: error " << error; + } else { + CloseHandle(thread); + } + CloseHandle(process_priv); + } + } + + // The process that we are watching. + base::ProcessHandle process_; + + base::ObjectWatcher watcher_; + + bool force_; + + DISALLOW_EVIL_CONSTRUCTORS(ChildReaper); +}; + +} // namespace + +// static +void ProcessWatcher::EnsureProcessTerminated(base::ProcessHandle process, + bool force) { + DCHECK(process != GetCurrentProcess()); + + // If already signaled, then we are done! + if (IsProcessDead(process)) { + base::CloseProcessHandle(process); + return; + } + + MessageLoopForIO* loop = MessageLoopForIO::current(); + if (force) { + RefPtr task = new ChildReaper(process, force); + loop->PostDelayedTask(task.forget(), kWaitInterval); + } else { + loop->AddDestructionObserver(new ChildReaper(process, force)); + } +} -- cgit v1.2.3