summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--base/base.gypi2
-rw-r--r--base/mach_ipc_mac.h (renamed from chrome/common/mach_ipc_mac.h)13
-rw-r--r--base/mach_ipc_mac.mm (renamed from chrome/common/mach_ipc_mac.mm)2
-rw-r--r--base/process_util.h15
-rw-r--r--base/process_util_posix.cc144
-rw-r--r--chrome/browser/child_process_launcher.cc22
-rw-r--r--chrome/browser/mach_broker_mac.cc73
-rw-r--r--chrome/browser/mach_broker_mac.h16
8 files changed, 273 insertions, 14 deletions
diff --git a/base/base.gypi b/base/base.gypi
index fc7c811..f7584c4 100644
--- a/base/base.gypi
+++ b/base/base.gypi
@@ -103,6 +103,8 @@
'logging.h',
'mac_util.h',
'mac_util.mm',
+ 'mach_ipc_mac.h',
+ 'mach_ipc_mac.mm',
'memory_debug.cc',
'memory_debug.h',
'message_loop.cc',
diff --git a/chrome/common/mach_ipc_mac.h b/base/mach_ipc_mac.h
index 42b9c65..7e9839c 100644
--- a/chrome/common/mach_ipc_mac.h
+++ b/base/mach_ipc_mac.h
@@ -258,7 +258,7 @@ class MachReceiveMessage : public MachMessage {
//==============================================================================
class MachSendMessage : public MachMessage {
public:
- MachSendMessage(int32_t message_id);
+ explicit MachSendMessage(int32_t message_id);
MachSendMessage(void *storage, size_t storage_length, int32_t message_id);
private:
@@ -272,11 +272,11 @@ class MachSendMessage : public MachMessage {
class ReceivePort {
public:
// Creates a new mach port for receiving messages and registers a name for it
- ReceivePort(const char *receive_port_name);
+ explicit ReceivePort(const char *receive_port_name);
// Given an already existing mach port, use it. We take ownership of the
// port and deallocate it in our destructor.
- ReceivePort(mach_port_t receive_port);
+ explicit ReceivePort(mach_port_t receive_port);
// Create a new mach port for receiving messages
ReceivePort();
@@ -302,11 +302,12 @@ class ReceivePort {
class MachPortSender {
public:
// get a port with send rights corresponding to a named registered service
- MachPortSender(const char *receive_port_name);
+ explicit MachPortSender(const char *receive_port_name);
- // Given an already existing mach port, use it.
- MachPortSender(mach_port_t send_port);
+ // Given an already existing mach port, use it. Does not take ownership of
+ // |send_port|.
+ explicit MachPortSender(mach_port_t send_port);
kern_return_t SendMessage(MachSendMessage &message,
mach_msg_timeout_t timeout);
diff --git a/chrome/common/mach_ipc_mac.mm b/base/mach_ipc_mac.mm
index 7693d9a..fc78bcc 100644
--- a/chrome/common/mach_ipc_mac.mm
+++ b/base/mach_ipc_mac.mm
@@ -2,7 +2,7 @@
// 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/mach_ipc_mac.h"
#import <Foundation/Foundation.h>
diff --git a/base/process_util.h b/base/process_util.h
index cec37bd..eb21188 100644
--- a/base/process_util.h
+++ b/base/process_util.h
@@ -17,6 +17,7 @@
// kinfo_proc is defined in <sys/sysctl.h>, but this forward declaration
// is sufficient for the vector<kinfo_proc> below.
struct kinfo_proc;
+#include <mach/mach.h>
#elif defined(OS_POSIX)
#include <dirent.h>
#include <limits.h>
@@ -155,13 +156,25 @@ bool LaunchApp(const std::vector<std::string>& argv,
const file_handle_mapping_vector& fds_to_remap,
bool wait, ProcessHandle* process_handle);
-// Similar to above, but also (un)set environment variables in child process
+// Similar to the above, but also (un)set environment variables in child process
// through |environ|.
typedef std::vector<std::pair<std::string, std::string> > environment_vector;
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);
+
+#if defined(OS_MACOSX)
+// Similar to the above, but also returns the new process's task_t if
+// |task_handle| is not NULL. If |task_handle| is not NULL, the caller is
+// responsible for calling |mach_port_deallocate()| on the returned handle.
+bool LaunchAppAndGetTask(const std::vector<std::string>& argv,
+ const environment_vector& environ,
+ const file_handle_mapping_vector& fds_to_remap,
+ bool wait,
+ task_t* task_handle,
+ ProcessHandle* process_handle);
+#endif // defined(OS_MACOSX)
#endif // defined(OS_POSIX)
// Executes the application specified by cl. This function delegates to one
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;
diff --git a/chrome/browser/child_process_launcher.cc b/chrome/browser/child_process_launcher.cc
index 1ab66c6..91bc2e6 100644
--- a/chrome/browser/child_process_launcher.cc
+++ b/chrome/browser/child_process_launcher.cc
@@ -23,6 +23,10 @@
#include "chrome/browser/renderer_host/render_sandbox_host_linux.h"
#endif
+#if defined(OS_MACOSX)
+#include "chrome/browser/mach_broker_mac.h"
+#endif
+
#if defined(OS_POSIX)
#include "base/global_descriptors_posix.h"
#endif
@@ -161,10 +165,24 @@ class ChildProcessLauncher::Context
#endif // defined(OS_LINUX)
// Actually launch the app.
- if (!base::LaunchApp(cmd_line->argv(), env, fds_to_map, false, &handle))
+ bool launched;
+#if defined(OS_MACOSX)
+ task_t child_task;
+ launched = base::LaunchAppAndGetTask(
+ cmd_line->argv(), env, fds_to_map, false, &child_task, &handle);
+ if (launched && child_task != MACH_PORT_NULL) {
+ MachBroker::instance()->RegisterPid(
+ handle,
+ MachBroker::MachInfo().SetTask(child_task));
+ }
+#else
+ launched = base::LaunchApp(cmd_line->argv(), env, fds_to_map,
+ /* wait= */false, &handle);
+#endif
+ if (!launched)
handle = base::kNullProcessHandle;
}
-#endif
+#endif // else defined(OS_POSIX)
ChromeThread::PostTask(
client_thread_id_, FROM_HERE,
diff --git a/chrome/browser/mach_broker_mac.cc b/chrome/browser/mach_broker_mac.cc
index 59f7728..6ae3391 100644
--- a/chrome/browser/mach_broker_mac.cc
+++ b/chrome/browser/mach_broker_mac.cc
@@ -5,6 +5,44 @@
#include "chrome/browser/mach_broker_mac.h"
#include "base/logging.h"
+#include "chrome/browser/extensions/extension_host.h"
+#include "chrome/browser/renderer_host/render_process_host.h"
+#include "chrome/common/child_process_info.h"
+#include "chrome/common/notification_service.h"
+
+// Required because notifications happen on the UI thread.
+class RegisterNotificationTask : public Task {
+ public:
+ RegisterNotificationTask(
+ MachBroker* broker)
+ : broker_(broker) { }
+
+ virtual void Run() {
+ broker_->registrar_.Add(broker_,
+ NotificationType::RENDERER_PROCESS_CLOSED,
+ NotificationService::AllSources());
+ broker_->registrar_.Add(broker_,
+ NotificationType::RENDERER_PROCESS_TERMINATED,
+ NotificationService::AllSources());
+ broker_->registrar_.Add(broker_,
+ NotificationType::CHILD_PROCESS_CRASHED,
+ NotificationService::AllSources());
+ broker_->registrar_.Add(broker_,
+ NotificationType::CHILD_PROCESS_HOST_DISCONNECTED,
+ NotificationService::AllSources());
+ broker_->registrar_.Add(broker_,
+ NotificationType::EXTENSION_PROCESS_TERMINATED,
+ NotificationService::AllSources());
+ }
+
+ private:
+ MachBroker* broker_;
+};
+
+MachBroker::MachBroker() {
+ ChromeThread::PostTask(
+ ChromeThread::UI, FROM_HERE, new RegisterNotificationTask(this));
+}
// Returns the global MachBroker.
MachBroker* MachBroker::instance() {
@@ -22,7 +60,16 @@ void MachBroker::RegisterPid(
// Removes all mappings belonging to |pid| from the broker.
void MachBroker::Invalidate(base::ProcessHandle pid) {
AutoLock lock(lock_);
- mach_map_.erase(pid);
+ MachBroker::MachMap::iterator it = mach_map_.find(pid);
+ if (it == mach_map_.end())
+ return;
+
+ kern_return_t kr = mach_port_deallocate(mach_task_self(),
+ it->second.mach_task_);
+ LOG_IF(WARNING, kr != KERN_SUCCESS)
+ << "Failed to mach_port_deallocate mach task " << it->second.mach_task_
+ << ", error " << kr;
+ mach_map_.erase(it);
}
// Returns the mach task belonging to |pid|.
@@ -33,3 +80,27 @@ mach_port_t MachBroker::TaskForPid(base::ProcessHandle pid) const {
return MACH_PORT_NULL;
return it->second.mach_task_;
}
+
+void MachBroker::Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details) {
+ base::ProcessHandle handle = 0;
+ switch (type.value) {
+ case NotificationType::RENDERER_PROCESS_CLOSED:
+ case NotificationType::RENDERER_PROCESS_TERMINATED:
+ handle = Source<RenderProcessHost>(source)->GetHandle();
+ break;
+ case NotificationType::EXTENSION_PROCESS_TERMINATED:
+ handle =
+ Details<ExtensionHost>(details)->render_process_host()->GetHandle();
+ break;
+ case NotificationType::CHILD_PROCESS_CRASHED:
+ case NotificationType::CHILD_PROCESS_HOST_DISCONNECTED:
+ handle = Details<ChildProcessInfo>(details)->handle();
+ break;
+ default:
+ NOTREACHED() << "Unexpected notification";
+ break;
+ }
+ Invalidate(handle);
+}
diff --git a/chrome/browser/mach_broker_mac.h b/chrome/browser/mach_broker_mac.h
index 67f4fec..1827fc9 100644
--- a/chrome/browser/mach_broker_mac.h
+++ b/chrome/browser/mach_broker_mac.h
@@ -13,6 +13,7 @@
#include "base/process.h"
#include "base/process_util.h"
#include "base/singleton.h"
+#include "chrome/common/notification_registrar.h"
// On OS X, the mach_port_t of a process is required to collect metrics about
// the process. Running |task_for_pid()| is only allowed for privileged code.
@@ -28,7 +29,8 @@
//
// Since this data arrives over a separate channel, it is not available
// immediately after a child process has been started.
-class MachBroker : public base::ProcessMetrics::PortProvider {
+class MachBroker : public base::ProcessMetrics::PortProvider,
+ public NotificationObserver {
public:
// Returns the global MachBroker.
static MachBroker* instance();
@@ -53,9 +55,18 @@ class MachBroker : public base::ProcessMetrics::PortProvider {
// Implement |ProcessMetrics::PortProvider|.
virtual mach_port_t TaskForPid(base::ProcessHandle process) const;
+ // Implement |NotificationObserver|.
+ virtual void Observe(NotificationType type,
+ const NotificationSource& source,
+ const NotificationDetails& details);
private:
// Private constructor.
- MachBroker() {}
+ MachBroker();
+
+ // Used to register for notifications received by NotificationObserver.
+ // Accessed only on the UI thread.
+ NotificationRegistrar registrar_;
+
friend struct DefaultSingletonTraits<MachBroker>;
friend class MachBrokerTest;
@@ -66,6 +77,7 @@ class MachBroker : public base::ProcessMetrics::PortProvider {
// Mutex that guards |mach_map_|.
mutable Lock lock_;
+ friend class RegisterNotificationTask;
DISALLOW_COPY_AND_ASSIGN(MachBroker);
};