diff options
Diffstat (limited to 'base/process_util_posix.cc')
-rw-r--r-- | base/process_util_posix.cc | 144 |
1 files changed, 143 insertions, 1 deletions
diff --git a/base/process_util_posix.cc b/base/process_util_posix.cc index 21626732..a771fa2 100644 --- a/base/process_util_posix.cc +++ b/base/process_util_posix.cc @@ -21,11 +21,16 @@ #include "base/logging.h" #include "base/platform_thread.h" #include "base/process_util.h" +#include "base/rand_util.h" #include "base/scoped_ptr.h" #include "base/sys_info.h" #include "base/time.h" #include "base/waitable_event.h" +#if defined(OS_MACOSX) +#include "base/mach_ipc_mac.h" +#endif + const int kMicrosecondsPerSecond = 1000000; namespace base { @@ -280,11 +285,148 @@ void SetAllFDsToCloseOnExec() { } } +#if defined(OS_MACOSX) +static std::string MachErrorCode(kern_return_t err) { + return StringPrintf("0x%x %s", err, mach_error_string(err)); +} + +// Forks the current process and returns the child's |task_t| in the parent +// process. +static pid_t fork_and_get_task(task_t* child_task) { + const int kTimeoutMs = 100; + kern_return_t err; + + // Put a random number into the channel name, so that a compromised renderer + // can't pretend being the child that's forked off. + std::string mach_connection_name = StringPrintf( + "com.google.Chrome.samplingfork.%p.%d", + child_task, base::RandInt(0, std::numeric_limits<int>::max())); + ReceivePort parent_recv_port(mach_connection_name.c_str()); + + // Error handling philosophy: If Mach IPC fails, don't touch |child_task| but + // return a valid pid. If IPC fails in the child, the parent will have to wait + // until kTimeoutMs is over. This is not optimal, but I've never seen it + // happen, and stuff should still mostly work. + pid_t pid = fork(); + switch (pid) { + case -1: + return pid; + case 0: { // child + ReceivePort child_recv_port; + + MachSendMessage child_message(/* id= */0); + if (!child_message.AddDescriptor(mach_task_self())) { + LOG(ERROR) << "child AddDescriptor(mach_task_self()) failed."; + return pid; + } + mach_port_t raw_child_recv_port = child_recv_port.GetPort(); + if (!child_message.AddDescriptor(raw_child_recv_port)) { + LOG(ERROR) << "child AddDescriptor(" << raw_child_recv_port + << ") failed."; + return pid; + } + + MachPortSender child_sender(mach_connection_name.c_str()); + err = child_sender.SendMessage(child_message, kTimeoutMs); + if (err != KERN_SUCCESS) { + LOG(ERROR) << "child SendMessage() failed: " << MachErrorCode(err); + return pid; + } + + MachReceiveMessage parent_message; + err = child_recv_port.WaitForMessage(&parent_message, kTimeoutMs); + if (err != KERN_SUCCESS) { + LOG(ERROR) << "child WaitForMessage() failed: " << MachErrorCode(err); + return pid; + } + + if (parent_message.GetTranslatedPort(0) == MACH_PORT_NULL) { + LOG(ERROR) << "child GetTranslatedPort(0) failed."; + return pid; + } + err = task_set_bootstrap_port(mach_task_self(), + parent_message.GetTranslatedPort(0)); + if (err != KERN_SUCCESS) { + LOG(ERROR) << "child task_set_bootstrap_port() failed: " + << MachErrorCode(err); + return pid; + } + break; + } + default: { // parent + MachReceiveMessage child_message; + err = parent_recv_port.WaitForMessage(&child_message, kTimeoutMs); + if (err != KERN_SUCCESS) { + LOG(ERROR) << "parent WaitForMessage() failed: " << MachErrorCode(err); + return pid; + } + + if (child_message.GetTranslatedPort(0) == MACH_PORT_NULL) { + LOG(ERROR) << "parent GetTranslatedPort(0) failed."; + return pid; + } + *child_task = child_message.GetTranslatedPort(0); + + if (child_message.GetTranslatedPort(1) == MACH_PORT_NULL) { + LOG(ERROR) << "parent GetTranslatedPort(1) failed."; + return pid; + } + MachPortSender parent_sender(child_message.GetTranslatedPort(1)); + + MachSendMessage parent_message(/* id= */0); + if (!parent_message.AddDescriptor(bootstrap_port)) { + LOG(ERROR) << "parent AddDescriptor(" << bootstrap_port << ") failed."; + return pid; + } + + err = parent_sender.SendMessage(parent_message, kTimeoutMs); + if (err != KERN_SUCCESS) { + LOG(ERROR) << "parent SendMessage() failed: " << MachErrorCode(err); + return pid; + } + break; + } + } + return pid; +} + bool LaunchApp(const std::vector<std::string>& argv, const environment_vector& environ, const file_handle_mapping_vector& fds_to_remap, bool wait, ProcessHandle* process_handle) { - pid_t pid = fork(); + return LaunchAppAndGetTask( + argv, environ, fds_to_remap, wait, NULL, process_handle); +} +#endif // defined(OS_MACOSX) + +#if defined(OS_MACOSX) +bool LaunchAppAndGetTask( +#else +bool LaunchApp( +#endif + const std::vector<std::string>& argv, + const environment_vector& environ, + const file_handle_mapping_vector& fds_to_remap, + bool wait, +#if defined(OS_MACOSX) + task_t* task_handle, +#endif + ProcessHandle* process_handle) { + pid_t pid; +#if defined(OS_MACOSX) + if (task_handle == NULL) { + pid = fork(); + } else { + // On OS X, the task_t for a process is needed for several reasons. Sadly, + // the function task_for_pid() requires privileges a normal user doesn't + // have. Instead, a short-lived Mach IPC connection is opened between parent + // and child, and the child sends its task_t to the parent at fork time. + *task_handle = MACH_PORT_NULL; + pid = fork_and_get_task(task_handle); + } +#else + pid = fork(); +#endif if (pid < 0) return false; |