// Copyright 2014 The Crashpad Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. #ifndef CRASHPAD_UTIL_MACH_CHILD_PORT_HANDSHAKE_H_ #define CRASHPAD_UTIL_MACH_CHILD_PORT_HANDSHAKE_H_ #include #include #include #include "base/files/scoped_file.h" #include "util/mach/child_port_types.h" namespace crashpad { namespace test { namespace { class ChildPortHandshakeTest; } // namespace } // namespace test //! \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 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 inherited port in module initializers. //! //! 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 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 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 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 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 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.get()); //! //! 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.get()); //! 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::apple::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::apple::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.get(), //! MACH_MSG_TYPE_MOVE_RECEIVE)) { //! std::ignore = 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::apple::ScopedMachReceiveRight receive_right( //! ChildPortHandshake::RunServerForFD( //! std::move(server_write_fd), //! ChildPortHandshake::PortRightType::kReceiveRight)); //! } //! \endcode class ChildPortHandshake { public: //! \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(const ChildPortHandshake&) = delete; ChildPortHandshake& operator=(const ChildPortHandshake&) = delete; ~ChildPortHandshake(); //! \brief Obtains the “read” side of the pipe, to be used by the client. //! //! This file descriptor must be passed to RunClientForFD(). //! //! \return The file descriptor that the client should read from. 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. //! //! 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. //! - Simultaneously receives messages on its Mach server and monitors the //! pipe for end-of-file. This is a blocking operation. //! - When a Mach message is received, calls HandleChildPortCheckIn() to //! 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. 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. //! //! \param[in] server_write_fd The write side of the pipe shared with the //! client process. This function takes ownership of this file descriptor, //! and will close it prior to returning. //! \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. //! - Obtains a send right to the server by calling `bootstrap_look_up()`. //! - Sends a check-in message to the server by calling //! `child_port_check_in()`, providing the token and the user-supplied port //! right. //! - Deallocates the send right to the server, and closes the pipe. //! //! There is no return value because `child_port_check_in()` is a MIG //! `simpleroutine`, and the server does not send a reply. This allows //! check-in to occur without blocking to wait for a reply. //! //! \param[in] client_read_fd The “read” side of the pipe shared with the //! server 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 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_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. //! //! \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); //! \brief Runs the check-in 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 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 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); base::ScopedFD client_read_fd_; base::ScopedFD server_write_fd_; friend class test::ChildPortHandshakeTest; }; } // namespace crashpad #endif // CRASHPAD_UTIL_MACH_CHILD_PORT_HANDSHAKE_H_