Index: base/process_util_posix.cc |
diff --git a/base/process_util_posix.cc b/base/process_util_posix.cc |
index 21626732b8c23eac4a1d59bfea816dca70c35fc8..a771fa25293cc810026157c129b03a04931cfd80 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; |