| Index: util/mach/child_port_handshake.h
|
| diff --git a/util/mach/child_port_handshake.h b/util/mach/child_port_handshake.h
|
| index 734220beffd4b7209f6375596277b252703fcba9..c1d7ab9a592d3a306faedbe4eb1a0ebd58aa7031 100644
|
| --- a/util/mach/child_port_handshake.h
|
| +++ b/util/mach/child_port_handshake.h
|
| @@ -21,7 +21,7 @@
|
|
|
| #include "base/basictypes.h"
|
| #include "base/files/scoped_file.h"
|
| -#include "util/mach/child_port_server.h"
|
| +#include "util/mach/child_port_types.h"
|
|
|
| namespace crashpad {
|
|
|
| @@ -31,80 +31,177 @@ class ChildPortHandshakeTest;
|
| } // namespace
|
| } // namespace test
|
|
|
| -//! \brief Implements a handshake protocol that allows a parent process to
|
| -//! obtain a Mach port right from a child process.
|
| +//! \brief Implements a handshake protocol that allows processes to exchange
|
| +//! port rights.
|
| //!
|
| //! Ordinarily, there is no way for parent and child processes to exchange port
|
| //! rights, outside of the rights that children inherit from their parents.
|
| //! These include task-special ports and exception ports, but all of these have
|
| //! system-defined uses, and cannot reliably be replaced: in a multi-threaded
|
| -//! parent, it is impossible to temporarily change one an inheritable port while
|
| +//! parent, it is impossible to temporarily change an inheritable port while
|
| //! maintaining a guarantee that another thread will not attempt to use it, and
|
| //! in children, it difficult to guarantee that nothing will attempt to use an
|
| //! inheritable port before it can be replaced with the correct one. This latter
|
| //! concern is becoming increasingly more pronounced as system libraries perform
|
| -//! more operations that rely on an inheritable port in module initializers.
|
| +//! more operations that rely on an inherited port in module initializers.
|
| //!
|
| -//! The protocol implemented by this class involves a server that runs in the
|
| -//! parent process. The server is published with the bootstrap server, which the
|
| -//! child has access to because the bootstrap port is one of the inherited
|
| -//! task-special ports. The parent and child also share a pipe, which the parent
|
| -//! can write to and the child can read from. After launching a child process,
|
| -//! the parent will write a random token to this pipe, along with the name under
|
| -//! which its server has been registered with the bootstrap server. The child
|
| -//! can then obtain a send right to this server with `bootstrap_look_up()`, and
|
| -//! send a check-in message containing the token value and the port right of its
|
| -//! choice by calling `child_port_check_in()`.
|
| +//! The protocol implemented by this class involves a server that runs in one
|
| +//! process. The server is published with the bootstrap server, which the other
|
| +//! process has access to because the bootstrap port is one of the inherited
|
| +//! task-special ports. The two processes also share a pipe, which the server
|
| +//! can write to and the client can read from. The server will write a random
|
| +//! token to this pipe, along with the name under which its service has been
|
| +//! registered with the bootstrap server. The client can then obtain a send
|
| +//! right to this service with `bootstrap_look_up()`, and send a check-in
|
| +//! message containing the token value and the port right of its choice by
|
| +//! calling `child_port_check_in()`.
|
| //!
|
| -//! The inclusion of the token authenticates the child to its parent. This is
|
| +//! The inclusion of the token authenticates the client to the server. This is
|
| //! necessary because the service is published with the bootstrap server, which
|
| -//! opens up access to it to more than the child process. Because the token is
|
| -//! passed to the child by a shared pipe, it constitutes a shared secret not
|
| +//! opens up access to it to more than the intended client. Because the token is
|
| +//! passed to the client by a shared pipe, it constitutes a shared secret not
|
| //! known by other processes that may have incidental access to the server. The
|
| //! ChildPortHandshake server considers its randomly-generated token valid until
|
| //! a client checks in with it. This mechanism is used instead of examining the
|
| //! request message’s audit trailer to verify the sender’s process ID because in
|
| -//! some process architectures, it may be impossible to verify the child’s
|
| -//! process ID. This may happen when the child disassociates from the parent
|
| -//! with a double fork(), and the actual client is the parent’s grandchild. In
|
| -//! this case, the child would not check in, but the grandchild, in possession
|
| -//! of the token, would check in.
|
| +//! some process architectures, it may be impossible to verify the client’s
|
| +//! process ID.
|
| //!
|
| //! The shared pipe serves another purpose: the server monitors it for an
|
| //! end-of-file (no readers) condition. Once detected, it will stop its blocking
|
| -//! wait for a client to check in. This mechanism was chosen over monitoring a
|
| -//! child process directly for exit to account for the possibility that the
|
| -//! child might disassociate with a double fork().
|
| +//! wait for a client to check in. This mechanism was also chosen for its
|
| +//! ability to function properly in diverse process architectures.
|
| //!
|
| -//! This class can be used to allow a child process to provide its parent with
|
| -//! a send right to its task port, in cases where it is desirable for the parent
|
| -//! to have such access. It can also be used to allow a child process to
|
| -//! establish its own server and provide its parent with a send right to that
|
| -//! server, for cases where a service is provided and it is undesirable or
|
| -//! impossible to provide it via the bootstrap or launchd interfaces.
|
| -class ChildPortHandshake : public ChildPortServer::Interface {
|
| +//! This class can be used to allow a child process to provide its parent with a
|
| +//! send right to its task port, in cases where it is desirable for the parent
|
| +//! to have such access. It can also be used to allow a parent process to
|
| +//! transfer a receive right to a child process that implements the server for
|
| +//! that right, or for a child process to establish its own server and provide
|
| +//! its parent with a send right to that server, for cases where a service is
|
| +//! provided and it is undesirable or impossible to provide it via the bootstrap
|
| +//! or launchd interfaces.
|
| +//!
|
| +//! Example parent process, running a client that sends a receive right to its
|
| +//! child:
|
| +//! \code
|
| +//! ChildPortHandshake child_port_handshake;
|
| +//! base::ScopedFD server_write_fd = child_port_handshake.ServerWriteFD();
|
| +//! std::string server_write_fd_string =
|
| +//! base::StringPrintf("%d", server_write_fd);
|
| +//!
|
| +//! pid_t pid = fork();
|
| +//! if (pid == 0) {
|
| +//! // Child
|
| +//!
|
| +//! // Close all file descriptors above STDERR_FILENO except for
|
| +//! // server_write_fd. Let the child know what file descriptor to use for
|
| +//! // server_write_fd by passing it as argv[1]. Example code for the child
|
| +//! // process is below.
|
| +//! CloseMultipleNowOrOnExec(STDERR_FILENO + 1, server_write_fd);
|
| +//! execlp("child", "child", server_write_fd_string.c_str(), nullptr);
|
| +//! }
|
| +//!
|
| +//! // Parent
|
| +//!
|
| +//! // Close the child’s end of the pipe.
|
| +//! server_write_fd.reset();
|
| +//!
|
| +//! // Make a new Mach receive right.
|
| +//! base::mac::ScopedMachReceiveRight
|
| +//! receive_right(NewMachPort(MACH_PORT_RIGHT_RECEIVE));
|
| +//!
|
| +//! // Make a send right corresponding to the receive right.
|
| +//! mach_port_t send_right;
|
| +//! mach_msg_type_name_t send_right_type;
|
| +//! mach_port_extract_right(mach_task_self(),
|
| +//! receive_right.get(),
|
| +//! MACH_MSG_TYPE_MAKE_SEND,
|
| +//! &send_right,
|
| +//! &send_right_type);
|
| +//! base::mac::ScopedMachSendRight send_right_owner(send_right);
|
| +//!
|
| +//! // Send the receive right to the child process, retaining the send right
|
| +//! // for use in the parent process.
|
| +//! if (child_port_handshake.RunClient(receive_right,
|
| +//! MACH_MSG_TYPE_MOVE_RECEIVE)) {
|
| +//! ignore_result(receive_right.release());
|
| +//! }
|
| +//! \endcode
|
| +//!
|
| +//! Example child process, running a server that receives a receive right from
|
| +//! its parent:
|
| +//! \code
|
| +//! int main(int argc, char* argv[]) {
|
| +//! // The parent passed server_write_fd in argv[1].
|
| +//! base::ScopedFD server_write_fd(atoi(argv[1]));
|
| +//!
|
| +//! // Obtain a receive right from the parent process.
|
| +//! base::mac::ScopedMachReceiveRight receive_right(
|
| +//! ChildPortHandshake::RunServerForFD(
|
| +//! server_write_fd.Pass(),
|
| +//! ChildPortHandshake::PortRightType::kReceiveRight));
|
| +//! }
|
| +//! \endcode
|
| +class ChildPortHandshake {
|
| public:
|
| - //! \brief Initializes the server.
|
| - //!
|
| - //! This creates the pipe so that the “read” side can be obtained by calling
|
| - //! ReadPipeFD().
|
| - ChildPortHandshake();
|
| + //! \brief Controls whether a receive or send right is expected to be
|
| + //! obtained from the client by the server’s call to RunServer().
|
| + enum class PortRightType {
|
| + //! \brief The server expects to receive a receive right.
|
| + kReceiveRight = 0,
|
| +
|
| + //! \brief The server expects to receive a send or send-once right.
|
| + kSendRight,
|
| + };
|
|
|
| + ChildPortHandshake();
|
| ~ChildPortHandshake();
|
|
|
| //! \brief Obtains the “read” side of the pipe, to be used by the client.
|
| //!
|
| - //! Callers must obtain this file descriptor and arrange for the caller to
|
| - //! have access to it before calling RunServer().
|
| + //! This file descriptor must be passed to RunClientForFD().
|
| //!
|
| //! \return The file descriptor that the client should read from.
|
| - int ReadPipeFD() const;
|
| + base::ScopedFD ClientReadFD();
|
| +
|
| + //! \brief Obtains the “write” side of the pipe, to be used by the server.
|
| + //!
|
| + //! This file descriptor must be passed to RunServerForFD().
|
| + //!
|
| + //! \return The file descriptor that the server should write to.
|
| + base::ScopedFD ServerWriteFD();
|
| +
|
| + //! \brief Runs the server.
|
| + //!
|
| + //! This method closes the “read” side of the pipe in-process, so that the
|
| + //! client process holds the only file descriptor that can read from the pipe.
|
| + //! It then calls RunServerForFD() using the “write” side of the pipe. If
|
| + //! ClientReadFD() has already been called in the server process, the caller
|
| + //! must ensure that the file descriptor returned by ClientReadFD() is closed
|
| + //! prior to calling this method.
|
| + mach_port_t RunServer(PortRightType port_right_type);
|
| +
|
| + //! \brief Runs the client.
|
| + //!
|
| + //! This method closes the “write” side of the pipe in-process, so that the
|
| + //! server process holds the only file descriptor that can write to the pipe.
|
| + //! It then calls RunClientForFD() using the “read” side of the pipe. If
|
| + //! ServerWriteFD() has already been called in the client process, the caller
|
| + //! must ensure that the file descriptor returned by ServerWriteFD() is closed
|
| + //! prior to calling this method.
|
| + //!
|
| + //! \return `true` on success, `false` on failure with a message logged.
|
| + bool RunClient(mach_port_t port, mach_msg_type_name_t right_type);
|
|
|
| //! \brief Runs the server.
|
| //!
|
| - //! This method performs these tasks:
|
| - //! - Closes the “read” side of the pipe in-process, so that the client
|
| - //! process holds the only file descriptor that can read from the pipe.
|
| + //! If a ChildPortHandshake object is available, don’t call this static
|
| + //! function. Instead, call RunServer(), which wraps this function. When using
|
| + //! this function, the caller is responsible for ensuring that the client
|
| + //! “read” side of the pipe is closed in the server process prior to calling
|
| + //! this function.
|
| + //!
|
| + //! This function performs these tasks:
|
| //! - Creates a random token and sends it via the pipe.
|
| //! - Checks its service in with the bootstrap server, and sends the name
|
| //! of its bootstrap service mapping via the pipe.
|
| @@ -114,33 +211,35 @@ class ChildPortHandshake : public ChildPortServer::Interface {
|
| //! interpret and validate it, and if the message is valid, returns the
|
| //! port right extracted from the message. If the message is not valid,
|
| //! this method will continue waiting for a valid message. Valid messages
|
| - //! are properly formatted and have the correct token. If a valid message
|
| - //! carries a send or send-once right, it will be returned. If a valid
|
| - //! message contains a receive right, it will be destroyed and
|
| - //! `MACH_PORT_NULL` will be returned. If a message is not valid, this
|
| + //! are properly formatted and have the correct token. The right carried in
|
| + //! a valid message will be returned. If a message is not valid, this
|
| //! method will continue waiting for pipe EOF or a valid message.
|
| //! - When notified of pipe EOF, returns `MACH_PORT_NULL`.
|
| //! - Regardless of return value, destroys the server’s receive right and
|
| //! closes the pipe.
|
| //!
|
| - //! \return On success, the send or send-once right to the port provided by
|
| - //! the client. The caller takes ownership of this right. On failure,
|
| - //! `MACH_PORT_NULL`, indicating that the client did not check in properly
|
| - //! before terminating, where termination is detected by noticing that the
|
| - //! read side of the shared pipe has closed. On failure, a message
|
| - //! indiciating the nature of the failure will be logged.
|
| - mach_port_t RunServer();
|
| -
|
| - // ChildPortServer::Interface:
|
| - kern_return_t HandleChildPortCheckIn(child_port_server_t server,
|
| - child_port_token_t token,
|
| - mach_port_t port,
|
| - mach_msg_type_name_t right_type,
|
| - const mach_msg_trailer_t* trailer,
|
| - bool* destroy_request) override;
|
| + //! \param[in] port_right_type The port right type expected to be received
|
| + //! from the client. If the port right received from the client does not
|
| + //! match the expected type, the received port right will be destroyed,
|
| + //! and `MACH_PORT_NULL` will be returned.
|
| + //!
|
| + //! \return On success, the port right provided by the client. The caller
|
| + //! takes ownership of this right. On failure, `MACH_PORT_NULL`,
|
| + //! indicating that the client did not check in properly before
|
| + //! terminating, where termination is detected by detecting that the read
|
| + //! side of the shared pipe has closed. On failure, a message indicating
|
| + //! the nature of the failure will be logged.
|
| + static mach_port_t RunServerForFD(base::ScopedFD server_write_fd,
|
| + PortRightType port_right_type);
|
|
|
| //! \brief Runs the client.
|
| //!
|
| + //! If a ChildPortHandshake object is available, don’t call this static
|
| + //! function. Instead, call RunClient(), which wraps this function. When using
|
| + //! this function, the caller is responsible for ensuring that the server
|
| + //! “write” side of the pipe is closed in the client process prior to calling
|
| + //! this function.
|
| + //!
|
| //! This function performs these tasks:
|
| //! - Reads the token from the pipe.
|
| //! - Reads the bootstrap service name from the pipe.
|
| @@ -155,32 +254,42 @@ class ChildPortHandshake : public ChildPortServer::Interface {
|
| //! check-in to occur without blocking to wait for a reply.
|
| //!
|
| //! \param[in] pipe_read The “read” side of the pipe shared with the server
|
| - //! process.
|
| - //! \param[in] port The port that will be passed to the server by
|
| + //! process. This function takes ownership of this file descriptor, and
|
| + //! will close it prior to returning.
|
| + //! \param[in] port The port right that will be passed to the server by
|
| //! `child_port_check_in()`.
|
| - //! \param[in] right_type The right type to furnish the parent with. If \a
|
| + //! \param[in] right_type The right type to furnish the server with. If \a
|
| //! port is a send right, this can be `MACH_MSG_TYPE_COPY_SEND` or
|
| //! `MACH_MSG_TYPE_MOVE_SEND`. If \a port is a send-once right, this can
|
| - //! be `MACH_MSG_TYPE_MOVE_SEND_ONCE`. If \a port is a receive right,
|
| - //! this can be `MACH_MSG_TYPE_MAKE_SEND`. `MACH_MSG_TYPE_MOVE_RECEIVE`
|
| - //! is supported by the client interface but will be silently rejected by
|
| - //! server run by RunServer(), which expects to receive only send or
|
| - //! send-once rights.
|
| - static void RunClient(int pipe_read,
|
| - mach_port_t port,
|
| - mach_msg_type_name_t right_type);
|
| + //! be `MACH_MSG_TYPE_MOVE_SEND_ONCE`. If \a port is a receive right, this
|
| + //! can be `MACH_MSG_TYPE_MAKE_SEND`, `MACH_MSG_TYPE_MAKE_SEND_ONCE`, or
|
| + //! `MACH_MSG_TYPE_MOVE_RECEIVE`.
|
| + //!
|
| + //! \return `true` on success, `false` on failure with a message logged. On
|
| + //! failure, the port right corresponding to a \a right_type of
|
| + //! `MACH_MSG_TYPE_MOVE_*` is not consumed, and the caller must dispose of
|
| + //! the right if necessary.
|
| + static bool RunClientForFD(base::ScopedFD client_read_fd,
|
| + mach_port_t port,
|
| + mach_msg_type_name_t right_type);
|
|
|
| private:
|
| //! \brief Runs the read-from-pipe portion of the client’s side of the
|
| //! handshake. This is an implementation detail of RunClient and is only
|
| //! exposed for testing purposes.
|
| //!
|
| + //! When using this function and RunClientInternal_SendCheckIn(), the caller
|
| + //! is responsible for closing \a pipe_read at an appropriate time, normally
|
| + //! after calling RunClientInternal_SendCheckIn().
|
| + //!
|
| //! \param[in] pipe_read The “read” side of the pipe shared with the server
|
| //! process.
|
| //! \param[out] token The token value read from \a pipe_read.
|
| //! \param[out] service_name The service name as registered with the bootstrap
|
| //! server, read from \a pipe_read.
|
| - static void RunClientInternal_ReadPipe(int pipe_read,
|
| + //!
|
| + //! \return `true` on success, `false` on failure with a message logged.
|
| + static bool RunClientInternal_ReadPipe(int pipe_read,
|
| child_port_token_t* token,
|
| std::string* service_name);
|
|
|
| @@ -188,34 +297,25 @@ class ChildPortHandshake : public ChildPortServer::Interface {
|
| //! This is an implementation detail of RunClient and is only exposed for
|
| //! testing purposes.
|
| //!
|
| + //! When using this RunClientInternal_ReadPipe() and this function, the caller
|
| + //! is responsible for closing the “read” side of the pipe at an appropriate
|
| + //! time, normally after calling this function.
|
| + //!
|
| //! \param[in] service_name The service name as registered with the bootstrap
|
| //! server, to be looked up with `bootstrap_look_up()`.
|
| //! \param[in] token The token value to provide during check-in.
|
| //! \param[in] port The port that will be passed to the server by
|
| //! `child_port_check_in()`.
|
| - //! \param[in] right_type The right type to furnish the parent with.
|
| - static void RunClientInternal_SendCheckIn(const std::string& service_name,
|
| + //! \param[in] right_type The right type to furnish the server with.
|
| + //!
|
| + //! \return `true` on success, `false` on failure with a message logged.
|
| + static bool RunClientInternal_SendCheckIn(const std::string& service_name,
|
| child_port_token_t token,
|
| mach_port_t port,
|
| mach_msg_type_name_t right_type);
|
|
|
| - // Communicates the token from RunServer(), where it’s generated, to
|
| - // HandleChildPortCheckIn(), where it’s validated.
|
| - child_port_token_t token_;
|
| -
|
| - base::ScopedFD pipe_read_;
|
| - base::ScopedFD pipe_write_;
|
| -
|
| - // Communicates the port received from the client from
|
| - // HandleChildPortCheckIn(), where it’s received, to RunServer(), where it’s
|
| - // returned. This is strongly-owned, but ownership is transferred to
|
| - // RunServer()’s caller.
|
| - mach_port_t child_port_;
|
| -
|
| - // Communicates that a check-in with a valid token was received by
|
| - // HandleChildPortCheckIn(), and that the value of child_port_ should be
|
| - // returned to RunServer()’s caller.
|
| - bool checked_in_;
|
| + base::ScopedFD client_read_fd_;
|
| + base::ScopedFD server_write_fd_;
|
|
|
| friend class test::ChildPortHandshakeTest;
|
|
|
|
|