diff options
author | amistry <amistry@chromium.org> | 2016-03-03 18:04:49 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2016-03-04 02:06:59 +0000 |
commit | 11ca9a5d48580d5eb98fc4d915ce308d3faf3314 (patch) | |
tree | a2333b8282259f8454761ab9e49d067d1842a64a /base | |
parent | 26259c7a20085eda544f5301350c0c7f31737236 (diff) | |
download | chromium_src-11ca9a5d48580d5eb98fc4d915ce308d3faf3314.zip chromium_src-11ca9a5d48580d5eb98fc4d915ce308d3faf3314.tar.gz chromium_src-11ca9a5d48580d5eb98fc4d915ce308d3faf3314.tar.bz2 |
Move non-content specific parts of content::MachBroker into base::MachPortBroker.
This change only moves mach port functions into a new class,
base::MachPortBroker. There is no new functionality, or change to
existing behaviour.
BUG=582468
Review URL: https://codereview.chromium.org/1755973002
Cr-Commit-Position: refs/heads/master@{#379178}
Diffstat (limited to 'base')
-rw-r--r-- | base/BUILD.gn | 4 | ||||
-rw-r--r-- | base/base.gyp | 2 | ||||
-rw-r--r-- | base/base.gypi | 2 | ||||
-rw-r--r-- | base/mac/mach_port_broker.h | 108 | ||||
-rw-r--r-- | base/mac/mach_port_broker.mm | 186 | ||||
-rw-r--r-- | base/mac/mach_port_broker_unittest.cc | 107 |
6 files changed, 409 insertions, 0 deletions
diff --git a/base/BUILD.gn b/base/BUILD.gn index 6355bf7..61a36c8 100644 --- a/base/BUILD.gn +++ b/base/BUILD.gn @@ -413,6 +413,8 @@ component("base") { "mac/mac_util.mm", "mac/mach_logging.cc", "mac/mach_logging.h", + "mac/mach_port_broker.h", + "mac/mach_port_broker.mm", "mac/objc_property_releaser.h", "mac/objc_property_releaser.mm", "mac/os_crash_dumps.cc", @@ -1182,6 +1184,7 @@ component("base") { "trace_event/malloc_dump_provider.cc", "trace_event/malloc_dump_provider.h", ] + libs = [ "bsm" ] } # Mac or iOS. @@ -1626,6 +1629,7 @@ test("base_unittests") { "mac/foundation_util_unittest.mm", "mac/libdispatch_task_runner_unittest.cc", "mac/mac_util_unittest.mm", + "mac/mach_port_broker_unittest.cc", "mac/objc_property_releaser_unittest.mm", "mac/scoped_nsobject_unittest.mm", "mac/scoped_objc_class_swizzler_unittest.mm", diff --git a/base/base.gyp b/base/base.gyp index 2b93cda..fff51e9 100644 --- a/base/base.gyp +++ b/base/base.gyp @@ -202,6 +202,7 @@ '$(SDKROOT)/System/Library/Frameworks/Foundation.framework', '$(SDKROOT)/System/Library/Frameworks/IOKit.framework', '$(SDKROOT)/System/Library/Frameworks/Security.framework', + '$(SDKROOT)/usr/lib/libbsm.dylib', ], }, }], @@ -449,6 +450,7 @@ 'mac/foundation_util_unittest.mm', 'mac/libdispatch_task_runner_unittest.cc', 'mac/mac_util_unittest.mm', + 'mac/mach_port_broker_unittest.cc', 'mac/objc_property_releaser_unittest.mm', 'mac/scoped_nsobject_unittest.mm', 'mac/scoped_objc_class_swizzler_unittest.mm', diff --git a/base/base.gypi b/base/base.gypi index df5d196..9de683e 100644 --- a/base/base.gypi +++ b/base/base.gypi @@ -306,6 +306,8 @@ 'mac/mac_util.mm', 'mac/mach_logging.cc', 'mac/mach_logging.h', + 'mac/mach_port_broker.h', + 'mac/mach_port_broker.mm', 'mac/objc_property_releaser.h', 'mac/objc_property_releaser.mm', 'mac/os_crash_dumps.cc', diff --git a/base/mac/mach_port_broker.h b/base/mac/mach_port_broker.h new file mode 100644 index 0000000..ba08b6f --- /dev/null +++ b/base/mac/mach_port_broker.h @@ -0,0 +1,108 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef BASE_MAC_MACH_PORT_BROKER_H_ +#define BASE_MAC_MACH_PORT_BROKER_H_ + +#include <mach/mach.h> + +#include <map> +#include <string> + +#include "base/base_export.h" +#include "base/mac/dispatch_source_mach.h" +#include "base/mac/scoped_mach_port.h" +#include "base/macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/process/port_provider_mac.h" +#include "base/process/process_handle.h" +#include "base/synchronization/lock.h" + +namespace base { + +// On OS X, the task port of a process is required to collect metrics about the +// process, and to insert Mach ports into the process. Running |task_for_pid()| +// is only allowed for privileged code. However, a process has port rights to +// all its subprocesses, so let the child processes send their Mach port to the +// parent over IPC. +// +// Mach ports can only be sent over Mach IPC, not over the |socketpair()| that +// the regular IPC system uses. Hence, the child processes opens a Mach +// connection shortly after launching and ipc their mach data to the parent +// process. A single |MachPortBroker| with a given name is expected to exist in +// the parent process. +// +// Since this data arrives over a separate channel, it is not available +// immediately after a child process has been started. +class BASE_EXPORT MachPortBroker : public base::PortProvider { + public: + // For use in child processes. This will send the task port of the current + // process over Mach IPC to the port registered by name (via this class) in + // the parent process. Returns true if the message was sent successfully + // and false if otherwise. + static bool ChildSendTaskPortToParent(const std::string& name); + + // Returns the Mach port name to use when sending or receiving messages. + // Does the Right Thing in the browser and in child processes. + static std::string GetMachPortName(const std::string& name, bool is_child); + + MachPortBroker(const std::string& name); + ~MachPortBroker() override; + + // Performs any initialization work. + bool Init(); + + // Adds a placeholder to the map for the given pid with MACH_PORT_NULL. + // Callers are expected to later update the port with FinalizePid(). Callers + // MUST acquire the lock given by GetLock() before calling this method (and + // release the lock afterwards). + void AddPlaceholderForPid(base::ProcessHandle pid); + + // Removes |pid| from the task port map. Callers MUST acquire the lock given + // by GetLock() before calling this method (and release the lock afterwards). + void InvalidatePid(base::ProcessHandle pid); + + // The lock that protects this MachPortBroker object. Callers MUST acquire + // and release this lock around calls to AddPlaceholderForPid(), + // InvalidatePid(), and FinalizePid(); + base::Lock& GetLock() { return lock_; } + + // Implement |base::PortProvider|. + mach_port_t TaskForPid(base::ProcessHandle process) const override; + + private: + friend class MachPortBrokerTest; + + // Message handler that is invoked on |dispatch_source_| when an + // incoming message needs to be received. + void HandleRequest(); + + // Updates the mapping for |pid| to include the given |mach_info|. Does + // nothing if PlaceholderForPid() has not already been called for the given + // |pid|. Callers MUST acquire the lock given by GetLock() before calling + // this method (and release the lock afterwards). + void FinalizePid(base::ProcessHandle pid, mach_port_t task_port); + + // Name used to identify a particular port broker. + const std::string name_; + + // The Mach port on which the server listens. + base::mac::ScopedMachReceiveRight server_port_; + + // The dispatch source and queue on which Mach messages will be received. + scoped_ptr<base::DispatchSourceMach> dispatch_source_; + + // Stores mach info for every process in the broker. + typedef std::map<base::ProcessHandle, mach_port_t> MachMap; + MachMap mach_map_; + + // Mutex that guards |mach_map_|. + mutable base::Lock lock_; + + DISALLOW_COPY_AND_ASSIGN(MachPortBroker); +}; + +} // namespace base + +#endif // BASE_MAC_MACH_PORT_BROKER_H_ diff --git a/base/mac/mach_port_broker.mm b/base/mac/mach_port_broker.mm new file mode 100644 index 0000000..3d563ba --- /dev/null +++ b/base/mac/mach_port_broker.mm @@ -0,0 +1,186 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/mac/mach_port_broker.h" + +#include <bsm/libbsm.h> +#include <servers/bootstrap.h> + +#include "base/logging.h" +#include "base/mac/foundation_util.h" +#include "base/mac/mach_logging.h" +#include "base/strings/string_util.h" +#include "base/strings/stringprintf.h" + +namespace base { + +namespace { + +// Mach message structure used in the child as a sending message. +struct MachPortBroker_ChildSendMsg { + mach_msg_header_t header; + mach_msg_body_t body; + mach_msg_port_descriptor_t child_task_port; +}; + +// Complement to the ChildSendMsg, this is used in the parent for receiving +// a message. Contains a message trailer with audit information. +struct MachPortBroker_ParentRecvMsg : public MachPortBroker_ChildSendMsg { + mach_msg_audit_trailer_t trailer; +}; + +} // namespace + +// static +bool MachPortBroker::ChildSendTaskPortToParent(const std::string& name) { + // Look up the named MachPortBroker port that's been registered with the + // bootstrap server. + mach_port_t parent_port; + kern_return_t kr = bootstrap_look_up(bootstrap_port, + const_cast<char*>(GetMachPortName(name, true).c_str()), &parent_port); + if (kr != KERN_SUCCESS) { + BOOTSTRAP_LOG(ERROR, kr) << "bootstrap_look_up"; + return false; + } + base::mac::ScopedMachSendRight scoped_right(parent_port); + + // Create the check in message. This will copy a send right on this process' + // (the child's) task port and send it to the parent. + MachPortBroker_ChildSendMsg msg; + bzero(&msg, sizeof(msg)); + msg.header.msgh_bits = MACH_MSGH_BITS_REMOTE(MACH_MSG_TYPE_COPY_SEND) | + MACH_MSGH_BITS_COMPLEX; + msg.header.msgh_remote_port = parent_port; + msg.header.msgh_size = sizeof(msg); + msg.body.msgh_descriptor_count = 1; + msg.child_task_port.name = mach_task_self(); + msg.child_task_port.disposition = MACH_MSG_TYPE_PORT_SEND; + msg.child_task_port.type = MACH_MSG_PORT_DESCRIPTOR; + + kr = mach_msg(&msg.header, MACH_SEND_MSG | MACH_SEND_TIMEOUT, sizeof(msg), + 0, MACH_PORT_NULL, 100 /*milliseconds*/, MACH_PORT_NULL); + if (kr != KERN_SUCCESS) { + MACH_LOG(ERROR, kr) << "mach_msg"; + return false; + } + + return true; +} + +// static +std::string MachPortBroker::GetMachPortName(const std::string& name, + bool is_child) { + // In child processes, use the parent's pid. + const pid_t pid = is_child ? getppid() : getpid(); + return base::StringPrintf( + "%s.%s.%d", base::mac::BaseBundleID(), name.c_str(), pid); +} + +mach_port_t MachPortBroker::TaskForPid(base::ProcessHandle pid) const { + base::AutoLock lock(lock_); + MachPortBroker::MachMap::const_iterator it = mach_map_.find(pid); + if (it == mach_map_.end()) + return MACH_PORT_NULL; + return it->second; +} + +MachPortBroker::MachPortBroker(const std::string& name) : name_(name) {} + +MachPortBroker::~MachPortBroker() {} + +bool MachPortBroker::Init() { + DCHECK(server_port_.get() == MACH_PORT_NULL); + + // Check in with launchd and publish the service name. + mach_port_t port; + kern_return_t kr = bootstrap_check_in( + bootstrap_port, GetMachPortName(name_, false).c_str(), &port); + if (kr != KERN_SUCCESS) { + BOOTSTRAP_LOG(ERROR, kr) << "bootstrap_check_in"; + return false; + } + server_port_.reset(port); + + // Start the dispatch source. + std::string queue_name = + base::StringPrintf("%s.MachPortBroker", base::mac::BaseBundleID()); + dispatch_source_.reset(new base::DispatchSourceMach( + queue_name.c_str(), server_port_.get(), ^{ HandleRequest(); })); + dispatch_source_->Resume(); + + return true; +} + +void MachPortBroker::AddPlaceholderForPid(base::ProcessHandle pid) { + lock_.AssertAcquired(); + DCHECK_EQ(0u, mach_map_.count(pid)); + mach_map_[pid] = MACH_PORT_NULL; +} + +void MachPortBroker::InvalidatePid(base::ProcessHandle pid) { + lock_.AssertAcquired(); + + MachMap::iterator mach_it = mach_map_.find(pid); + if (mach_it != mach_map_.end()) { + kern_return_t kr = mach_port_deallocate(mach_task_self(), mach_it->second); + MACH_LOG_IF(WARNING, kr != KERN_SUCCESS, kr) << "mach_port_deallocate"; + mach_map_.erase(mach_it); + } +} + +void MachPortBroker::HandleRequest() { + MachPortBroker_ParentRecvMsg msg; + bzero(&msg, sizeof(msg)); + msg.header.msgh_size = sizeof(msg); + msg.header.msgh_local_port = server_port_.get(); + + const mach_msg_option_t options = MACH_RCV_MSG | + MACH_RCV_TRAILER_TYPE(MACH_RCV_TRAILER_AUDIT) | + MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AUDIT); + + kern_return_t kr = mach_msg(&msg.header, + options, + 0, + sizeof(msg), + server_port_.get(), + MACH_MSG_TIMEOUT_NONE, + MACH_PORT_NULL); + if (kr != KERN_SUCCESS) { + MACH_LOG(ERROR, kr) << "mach_msg"; + return; + } + + // Use the kernel audit information to make sure this message is from + // a task that this process spawned. The kernel audit token contains the + // unspoofable pid of the task that sent the message. + // + // TODO(rsesek): In the 10.7 SDK, there's audit_token_to_pid(). + pid_t child_pid; + audit_token_to_au32(msg.trailer.msgh_audit, + NULL, NULL, NULL, NULL, NULL, &child_pid, NULL, NULL); + + mach_port_t child_task_port = msg.child_task_port.name; + + // Take the lock and update the broker information. + base::AutoLock lock(lock_); + FinalizePid(child_pid, child_task_port); +} + +void MachPortBroker::FinalizePid(base::ProcessHandle pid, + mach_port_t task_port) { + lock_.AssertAcquired(); + + MachMap::iterator it = mach_map_.find(pid); + if (it == mach_map_.end()) { + // Do nothing for unknown pids. + LOG(ERROR) << "Unknown process " << pid << " is sending Mach IPC messages!"; + return; + } + + DCHECK(it->second == MACH_PORT_NULL); + if (it->second == MACH_PORT_NULL) + it->second = task_port; +} + +} // namespace base diff --git a/base/mac/mach_port_broker_unittest.cc b/base/mac/mach_port_broker_unittest.cc new file mode 100644 index 0000000..2188ea3 --- /dev/null +++ b/base/mac/mach_port_broker_unittest.cc @@ -0,0 +1,107 @@ +// Copyright 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/mac/mach_port_broker.h" + +#include "base/command_line.h" +#include "base/synchronization/lock.h" +#include "base/test/multiprocess_test.h" +#include "base/test/test_timeouts.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/multiprocess_func_list.h" + +namespace base { + +namespace { +const char kBootstrapPortName[] = "thisisatest"; +} + +class MachPortBrokerTest : public testing::Test { + public: + MachPortBrokerTest() : broker_(kBootstrapPortName) {} + + // Helper function to acquire/release locks and call |PlaceholderForPid()|. + void AddPlaceholderForPid(base::ProcessHandle pid) { + base::AutoLock lock(broker_.GetLock()); + broker_.AddPlaceholderForPid(pid); + } + + // Helper function to acquire/release locks and call |FinalizePid()|. + void FinalizePid(base::ProcessHandle pid, + mach_port_t task_port) { + base::AutoLock lock(broker_.GetLock()); + broker_.FinalizePid(pid, task_port); + } + + protected: + MachPortBroker broker_; +}; + +TEST_F(MachPortBrokerTest, Locks) { + // Acquire and release the locks. Nothing bad should happen. + base::AutoLock lock(broker_.GetLock()); +} + +TEST_F(MachPortBrokerTest, AddPlaceholderAndFinalize) { + // Add a placeholder for PID 1. + AddPlaceholderForPid(1); + EXPECT_EQ(0u, broker_.TaskForPid(1)); + + // Finalize PID 1. + FinalizePid(1, 100u); + EXPECT_EQ(100u, broker_.TaskForPid(1)); + + // Should be no entry for PID 2. + EXPECT_EQ(0u, broker_.TaskForPid(2)); +} + +TEST_F(MachPortBrokerTest, FinalizeUnknownPid) { + // Finalizing an entry for an unknown pid should not add it to the map. + FinalizePid(1u, 100u); + EXPECT_EQ(0u, broker_.TaskForPid(1u)); +} + +MULTIPROCESS_TEST_MAIN(MachPortBrokerTestChild) { + CHECK(base::MachPortBroker::ChildSendTaskPortToParent(kBootstrapPortName)); + return 0; +} + +TEST_F(MachPortBrokerTest, ReceivePortFromChild) { + ASSERT_TRUE(broker_.Init()); + CommandLine command_line( + base::GetMultiProcessTestChildBaseCommandLine()); + broker_.GetLock().Acquire(); + base::Process test_child_process = base::SpawnMultiProcessTestChild( + "MachPortBrokerTestChild", command_line, LaunchOptions()); + broker_.AddPlaceholderForPid(test_child_process.Handle()); + broker_.GetLock().Release(); + + int rv = -1; + ASSERT_TRUE(test_child_process.WaitForExitWithTimeout( + TestTimeouts::action_timeout(), &rv)); + EXPECT_EQ(0, rv); + + EXPECT_NE(static_cast<mach_port_t>(MACH_PORT_NULL), + broker_.TaskForPid(test_child_process.Handle())); +} + +TEST_F(MachPortBrokerTest, ReceivePortFromChildWithoutAdding) { + ASSERT_TRUE(broker_.Init()); + CommandLine command_line( + base::GetMultiProcessTestChildBaseCommandLine()); + broker_.GetLock().Acquire(); + base::Process test_child_process = base::SpawnMultiProcessTestChild( + "MachPortBrokerTestChild", command_line, LaunchOptions()); + broker_.GetLock().Release(); + + int rv = -1; + ASSERT_TRUE(test_child_process.WaitForExitWithTimeout( + TestTimeouts::action_timeout(), &rv)); + EXPECT_EQ(0, rv); + + EXPECT_EQ(static_cast<mach_port_t>(MACH_PORT_NULL), + broker_.TaskForPid(test_child_process.Handle())); +} + +} // namespace base |