blob: bf994b77db24927654950791c4e28db88417fa12 [file] [log] [blame]
// Copyright 2015 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 "build/build_config.h"
#include <fcntl.h>
#include <mach/mach_vm.h>
#include <stddef.h>
#include <sys/mman.h>
#include "base/command_line.h"
#include "base/files/file_util.h"
#include "base/files/scoped_file.h"
#include "base/files/scoped_temp_dir.h"
#include "base/mac/mac_util.h"
#include "base/mac/mach_logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/memory/shared_memory.h"
#include "base/strings/string_number_conversions.h"
#include "base/synchronization/spin_wait.h"
#include "base/time/time.h"
#include "ipc/attachment_broker_messages.h"
#include "ipc/attachment_broker_privileged_mac.h"
#include "ipc/attachment_broker_unprivileged_mac.h"
#include "ipc/ipc_listener.h"
#include "ipc/ipc_message.h"
#include "ipc/ipc_test_base.h"
#include "ipc/ipc_test_messages.h"
#include "ipc/test_util_mac.h"
namespace {
const char kDataBuffer1[] = "This is some test data to write to the file.";
const char kDataBuffer2[] = "The lazy dog and a fox.";
const char kDataBuffer3[] = "Two green bears but not a potato.";
const char kDataBuffer4[] = "Red potato is best potato.";
const std::string g_service_switch_name = "service_name";
const size_t g_large_message_size = 8 * 1024 * 1024;
const int g_large_message_count = 1000;
const size_t g_medium_message_size = 512 * 1024;
// Running the message loop is expected to increase the number of resident
// pages. The exact amount is non-deterministic, but for a simple test suite
// like this one, the increase is expected to be less than 1 MB.
const size_t g_expected_memory_increase = 1024 * 1024;
enum TestResult {
RESULT_UNKNOWN,
RESULT_SUCCESS,
RESULT_FAILURE,
};
mach_vm_size_t GetResidentSize() {
task_basic_info_64 info;
mach_msg_type_number_t count = TASK_BASIC_INFO_64_COUNT;
kern_return_t kr = task_info(mach_task_self(), TASK_BASIC_INFO_64,
reinterpret_cast<task_info_t>(&info), &count);
MACH_CHECK(kr == KERN_SUCCESS, kr) << "Couldn't get resident size.";
return info.resident_size;
}
base::mac::ScopedMachSendRight GetMachPortFromBrokeredAttachment(
const scoped_refptr<IPC::BrokerableAttachment>& attachment) {
if (attachment->GetType() !=
IPC::BrokerableAttachment::TYPE_BROKERABLE_ATTACHMENT) {
LOG(INFO) << "Attachment type not TYPE_BROKERABLE_ATTACHMENT.";
return base::mac::ScopedMachSendRight(MACH_PORT_NULL);
}
if (attachment->GetBrokerableType() != IPC::BrokerableAttachment::MACH_PORT) {
LOG(INFO) << "Brokerable type not MACH_PORT.";
return base::mac::ScopedMachSendRight(MACH_PORT_NULL);
}
IPC::internal::MachPortAttachmentMac* received_mach_port_attachment =
static_cast<IPC::internal::MachPortAttachmentMac*>(attachment.get());
base::mac::ScopedMachSendRight send_right(
received_mach_port_attachment->get_mach_port());
received_mach_port_attachment->reset_mach_port_ownership();
return send_right;
}
// Makes a Mach port backed SharedMemory region and fills it with |contents|.
scoped_ptr<base::SharedMemory> MakeSharedMemory(const std::string& contents) {
base::SharedMemoryHandle shm(contents.size());
if (!shm.IsValid()) {
LOG(ERROR) << "Failed to make SharedMemoryHandle.";
return nullptr;
}
scoped_ptr<base::SharedMemory> shared_memory(
new base::SharedMemory(shm, false));
shared_memory->Map(contents.size());
memcpy(shared_memory->memory(), contents.c_str(), contents.size());
return shared_memory;
}
// |message| must be deserializable as a TestSharedMemoryHandleMsg1.
base::SharedMemoryHandle GetSharedMemoryHandleFromMsg1(
const IPC::Message& message) {
// Expect a message with a brokered attachment.
if (!message.HasBrokerableAttachments()) {
LOG(ERROR) << "Message missing brokerable attachment.";
return base::SharedMemoryHandle();
}
TestSharedMemoryHandleMsg1::Schema::Param p;
if (!TestSharedMemoryHandleMsg1::Read(&message, &p)) {
LOG(ERROR) << "Failed to deserialize message.";
return base::SharedMemoryHandle();
}
return base::get<1>(p);
}
// |message| must be deserializable as a TestSharedMemoryHandleMsg2. Returns
// whether deserialization was successful. |handle1| and |handle2| are output
// variables populated on success.
bool GetSharedMemoryHandlesFromMsg2(const IPC::Message& message,
base::SharedMemoryHandle* handle1,
base::SharedMemoryHandle* handle2) {
// Expect a message with a brokered attachment.
if (!message.HasBrokerableAttachments()) {
LOG(ERROR) << "Message missing brokerable attachment.";
return false;
}
TestSharedMemoryHandleMsg2::Schema::Param p;
if (!TestSharedMemoryHandleMsg2::Read(&message, &p)) {
LOG(ERROR) << "Failed to deserialize message.";
return false;
}
*handle1 = base::get<0>(p);
*handle2 = base::get<1>(p);
return true;
}
// Returns |nullptr| on error.
scoped_ptr<base::SharedMemory> MapSharedMemoryHandle(
const base::SharedMemoryHandle& shm,
bool read_only) {
if (!shm.IsValid()) {
LOG(ERROR) << "Invalid SharedMemoryHandle";
return nullptr;
}
size_t size;
if (!shm.GetSize(&size)) {
LOG(ERROR) << "Couldn't get size of SharedMemoryHandle";
return nullptr;
}
scoped_ptr<base::SharedMemory> shared_memory(
new base::SharedMemory(shm, read_only));
shared_memory->Map(size);
return shared_memory;
}
// This method maps the SharedMemoryHandle, checks the contents, and then
// consumes a reference to the underlying Mach port.
bool CheckContentsOfSharedMemoryHandle(const base::SharedMemoryHandle& shm,
const std::string& contents) {
scoped_ptr<base::SharedMemory> shared_memory(
MapSharedMemoryHandle(shm, false));
if (memcmp(shared_memory->memory(), contents.c_str(), contents.size()) != 0) {
LOG(ERROR) << "Shared Memory contents not equivalent";
return false;
}
return true;
}
// This method mmaps the FileDescriptor, checks the contents, and then munmaps
// the FileDescriptor and closes the underlying fd.
bool CheckContentsOfFileDescriptor(const base::FileDescriptor& file_descriptor,
const std::string& contents) {
base::ScopedFD fd_closer(file_descriptor.fd);
lseek(file_descriptor.fd, 0, SEEK_SET);
scoped_ptr<char, base::FreeDeleter> buffer(
static_cast<char*>(malloc(contents.size())));
if (!base::ReadFromFD(file_descriptor.fd, buffer.get(), contents.size()))
return false;
int result = memcmp(buffer.get(), contents.c_str(), contents.size());
return result == 0;
}
// Open |fp| and populate it with |contents|.
base::FileDescriptor MakeFileDescriptor(const base::FilePath& fp,
const std::string& contents) {
int fd = open(fp.value().c_str(), O_RDWR, S_IWUSR | S_IRUSR);
base::ScopedFD fd_closer(fd);
if (fd <= 0) {
LOG(ERROR) << "Error opening file at: " << fp.value();
return base::FileDescriptor();
}
if (lseek(fd, 0, SEEK_SET) != 0) {
LOG(ERROR) << "Error changing offset";
return base::FileDescriptor();
}
if (write(fd, contents.c_str(), contents.size()) !=
static_cast<ssize_t>(contents.size())) {
LOG(ERROR) << "Error writing to file";
return base::FileDescriptor();
}
return base::FileDescriptor(fd_closer.release(), true);
}
// Maps both handles, then checks that their contents matches |contents|. Then
// checks that changes to one are reflected in the other. Then consumes
// references to both underlying Mach ports.
bool CheckContentsOfTwoEquivalentSharedMemoryHandles(
const base::SharedMemoryHandle& handle1,
const base::SharedMemoryHandle& handle2,
const std::string& contents) {
scoped_ptr<base::SharedMemory> shared_memory1(
MapSharedMemoryHandle(handle1, false));
scoped_ptr<base::SharedMemory> shared_memory2(
MapSharedMemoryHandle(handle2, false));
if (memcmp(shared_memory1->memory(), contents.c_str(), contents.size()) !=
0) {
LOG(ERROR) << "Incorrect contents in shared_memory1";
return false;
}
if (memcmp(shared_memory1->memory(), shared_memory2->memory(),
contents.size()) != 0) {
LOG(ERROR) << "Incorrect contents in shared_memory2";
return false;
}
// Updating shared_memory1 should update shared_memory2.
const char known_string[] = "string bean";
if (shared_memory1->mapped_size() < strlen(known_string) ||
shared_memory2->mapped_size() < strlen(known_string)) {
LOG(ERROR) << "Shared memory size is too small";
return false;
}
memcpy(shared_memory1->memory(), known_string, strlen(known_string));
if (memcmp(shared_memory1->memory(), shared_memory2->memory(),
strlen(known_string)) != 0) {
LOG(ERROR) << "Incorrect contents in shared_memory2";
return false;
}
return true;
}
// |message| must be deserializable as a TestSharedMemoryHandleMsg1. Returns
// whether the contents of the attached shared memory region matches |contents|.
// Consumes a reference to the underlying Mach port.
bool CheckContentsOfMessage1(const IPC::Message& message,
const std::string& contents) {
base::SharedMemoryHandle shm(GetSharedMemoryHandleFromMsg1(message));
return CheckContentsOfSharedMemoryHandle(shm, contents);
}
// Once the test is finished, send a control message to the parent process with
// the result. The message may require the runloop to be run before its
// dispatched.
void SendControlMessage(IPC::Sender* sender, bool success) {
IPC::Message* message = new IPC::Message(0, 2, IPC::Message::PRIORITY_NORMAL);
TestResult result = success ? RESULT_SUCCESS : RESULT_FAILURE;
message->WriteInt(result);
sender->Send(message);
}
// Records the most recently received brokerable attachment's id.
class AttachmentBrokerObserver : public IPC::AttachmentBroker::Observer {
public:
void ReceivedBrokerableAttachmentWithId(
const IPC::BrokerableAttachment::AttachmentId& id) override {
id_ = id;
}
IPC::BrokerableAttachment::AttachmentId* get_id() { return &id_; }
private:
IPC::BrokerableAttachment::AttachmentId id_;
};
// A broker which always sets the current process as the destination process
// for attachments.
class MockBroker : public IPC::AttachmentBrokerUnprivilegedMac {
public:
MockBroker() {}
~MockBroker() override {}
bool SendAttachmentToProcess(
const scoped_refptr<IPC::BrokerableAttachment>& attachment,
base::ProcessId destination_process) override {
return IPC::AttachmentBrokerUnprivilegedMac::SendAttachmentToProcess(
attachment, base::Process::Current().Pid());
}
};
// Forwards all messages to |listener_|. Quits the message loop after a
// message is received, or the channel has an error.
class ProxyListener : public IPC::Listener {
public:
ProxyListener() : listener_(nullptr), reason_(MESSAGE_RECEIVED) {}
~ProxyListener() override {}
// The reason for exiting the message loop.
enum Reason { MESSAGE_RECEIVED, CHANNEL_ERROR };
bool OnMessageReceived(const IPC::Message& message) override {
bool result = false;
if (listener_)
result = listener_->OnMessageReceived(message);
reason_ = MESSAGE_RECEIVED;
messages_.push_back(message);
base::MessageLoop::current()->QuitNow();
return result;
}
void OnChannelError() override {
reason_ = CHANNEL_ERROR;
base::MessageLoop::current()->QuitNow();
}
void set_listener(IPC::Listener* listener) { listener_ = listener; }
Reason get_reason() { return reason_; }
IPC::Message get_first_message() {
DCHECK(!messages_.empty());
return messages_[0];
}
void pop_first_message() {
DCHECK(!messages_.empty());
messages_.erase(messages_.begin());
}
bool has_message() { return !messages_.empty(); }
private:
IPC::Listener* listener_;
Reason reason_;
std::vector<IPC::Message> messages_;
};
// Waits for a result to be sent over the channel. Quits the message loop
// after a message is received, or the channel has an error.
class ResultListener : public IPC::Listener {
public:
ResultListener() : result_(RESULT_UNKNOWN) {}
~ResultListener() override {}
bool OnMessageReceived(const IPC::Message& message) override {
base::PickleIterator iter(message);
int result;
EXPECT_TRUE(iter.ReadInt(&result));
result_ = static_cast<TestResult>(result);
return true;
}
TestResult get_result() { return result_; }
private:
TestResult result_;
};
class MockPortProvider : public base::PortProvider {
public:
mach_port_t TaskForPid(base::ProcessHandle process) const override {
auto it = port_map_.find(process);
if (it != port_map_.end())
return it->second;
return MACH_PORT_NULL;
}
void InsertEntry(base::ProcessHandle process, mach_port_t task_port) {
port_map_[process] = task_port;
NotifyObservers(process);
}
void ClearPortMap() { port_map_.clear(); }
private:
std::map<base::ProcessHandle, mach_port_t> port_map_;
};
// End-to-end tests for the attachment brokering process on Mac.
// The parent process acts as an unprivileged process. The child process acts
// as the privileged process.
class IPCAttachmentBrokerMacTest : public IPCTestBase {
public:
IPCAttachmentBrokerMacTest() {}
~IPCAttachmentBrokerMacTest() override {}
base::CommandLine MakeCmdLine(const std::string& procname) override {
base::CommandLine command_line = IPCTestBase::MakeCmdLine(procname);
// Pass the service name to the child process.
command_line.AppendSwitchASCII(g_service_switch_name, service_name_);
return command_line;
}
// Takes ownership of |broker|. Has no effect if called after CommonSetUp().
void SetBroker(IPC::AttachmentBrokerUnprivilegedMac* broker) {
broker_.reset(broker);
}
// Mach Setup that needs to occur before child processes are forked.
void MachPreForkSetUp() {
service_name_ = IPC::CreateRandomServiceName();
server_port_.reset(IPC::BecomeMachServer(service_name_.c_str()).release());
}
// Mach Setup that needs to occur after child processes are forked.
void MachPostForkSetUp() {
client_port_.reset(IPC::ReceiveMachPort(server_port_.get()).release());
IPC::SendMachPort(
client_port_.get(), mach_task_self(), MACH_MSG_TYPE_COPY_SEND);
}
// Setup shared between tests.
void CommonSetUp(const char* name) {
Init(name);
MachPreForkSetUp();
if (!broker_.get())
SetBroker(new IPC::AttachmentBrokerUnprivilegedMac);
broker_->AddObserver(&observer_, task_runner());
CreateChannel(&proxy_listener_);
broker_->DesignateBrokerCommunicationChannel(channel());
ASSERT_TRUE(ConnectChannel());
ASSERT_TRUE(StartClient());
MachPostForkSetUp();
active_names_at_start_ = IPC::GetActiveNameCount();
get_proxy_listener()->set_listener(&result_listener_);
}
void CheckChildResult() {
ASSERT_EQ(ProxyListener::MESSAGE_RECEIVED,
get_proxy_listener()->get_reason());
ASSERT_EQ(get_result_listener()->get_result(), RESULT_SUCCESS);
}
void FinalCleanUp() {
// There should be no leaked names.
SPIN_FOR_TIMEDELTA_OR_UNTIL_TRUE(
base::TimeDelta::FromSeconds(10),
active_names_at_start_ == IPC::GetActiveNameCount());
EXPECT_EQ(active_names_at_start_, IPC::GetActiveNameCount());
// Close the channel so the client's OnChannelError() gets fired.
channel()->Close();
EXPECT_TRUE(WaitForClientShutdown());
DestroyChannel();
broker_.reset();
}
// Teardown shared between most tests.
void CommonTearDown() {
CheckChildResult();
FinalCleanUp();
}
// Makes a SharedMemory region, fills it with |contents|, sends the handle
// over Chrome IPC, and unmaps the region.
void SendMessage1(const std::string& contents) {
scoped_ptr<base::SharedMemory> shared_memory(MakeSharedMemory(contents));
IPC::Message* message =
new TestSharedMemoryHandleMsg1(100, shared_memory->handle(), 200);
sender()->Send(message);
}
ProxyListener* get_proxy_listener() { return &proxy_listener_; }
IPC::AttachmentBrokerUnprivilegedMac* get_broker() { return broker_.get(); }
AttachmentBrokerObserver* get_observer() { return &observer_; }
ResultListener* get_result_listener() { return &result_listener_; }
protected:
// The number of active names immediately after set up.
mach_msg_type_number_t active_names_at_start_;
private:
ProxyListener proxy_listener_;
scoped_ptr<IPC::AttachmentBrokerUnprivilegedMac> broker_;
AttachmentBrokerObserver observer_;
// A port on which the main process listens for mach messages from the child
// process.
base::mac::ScopedMachReceiveRight server_port_;
// A port on which the child process listens for mach messages from the main
// process.
base::mac::ScopedMachSendRight client_port_;
std::string service_name_;
ResultListener result_listener_;
};
// These objects are globally accessible, and are expected to outlive all IPC
// Channels.
struct ChildProcessGlobals {
MockPortProvider port_provider;
// The broker must be destroyed before the port_provider, so that the broker
// gets a chance to unregister itself as an observer. This doesn't matter
// outside of tests, since neither port_provider nor broker will ever be
// destroyed.
scoped_ptr<IPC::AttachmentBrokerPrivilegedMac> broker;
base::mac::ScopedMachSendRight server_task_port;
// Total resident memory before running the message loop.
mach_vm_size_t initial_resident_size;
// Whether to emit log statements while processing messages.
bool message_logging;
};
using OnMessageReceivedCallback = void (*)(IPC::Sender* sender,
const IPC::Message& message,
ChildProcessGlobals* globals);
// Sets up the Mach communication ports with the server. Returns a set of
// globals that must live at least as long as the test.
scoped_ptr<ChildProcessGlobals> CommonChildProcessSetUp() {
base::CommandLine cmd_line = *base::CommandLine::ForCurrentProcess();
std::string service_name =
cmd_line.GetSwitchValueASCII(g_service_switch_name);
base::mac::ScopedMachSendRight server_port(
IPC::LookupServer(service_name.c_str()));
base::mac::ScopedMachReceiveRight client_port(IPC::MakeReceivingPort());
// Send the port that this process is listening on to the server.
IPC::SendMachPort(
server_port.get(), client_port.get(), MACH_MSG_TYPE_MAKE_SEND);
// Receive the task port of the server process.
base::mac::ScopedMachSendRight server_task_port(
IPC::ReceiveMachPort(client_port.get()));
scoped_ptr<ChildProcessGlobals> globals(new ChildProcessGlobals);
globals->broker.reset(
new IPC::AttachmentBrokerPrivilegedMac(&globals->port_provider));
globals->port_provider.InsertEntry(getppid(), server_task_port.get());
globals->server_task_port.reset(server_task_port.release());
globals->message_logging = true;
return globals;
}
int CommonPrivilegedProcessMain(OnMessageReceivedCallback callback,
const char* channel_name) {
LOG(INFO) << "Privileged process start.";
scoped_ptr<ChildProcessGlobals> globals(CommonChildProcessSetUp());
mach_msg_type_number_t active_names_at_start = IPC::GetActiveNameCount();
base::MessageLoopForIO main_message_loop;
ProxyListener listener;
scoped_ptr<IPC::Channel> channel(IPC::Channel::CreateClient(
IPCTestBase::GetChannelName(channel_name), &listener));
globals->broker->RegisterCommunicationChannel(channel.get());
CHECK(channel->Connect());
globals->initial_resident_size = GetResidentSize();
while (true) {
if (globals->message_logging)
LOG(INFO) << "Privileged process spinning run loop.";
base::MessageLoop::current()->Run();
ProxyListener::Reason reason = listener.get_reason();
if (reason == ProxyListener::CHANNEL_ERROR)
break;
while (listener.has_message()) {
if (globals->message_logging)
LOG(INFO) << "Privileged process running callback.";
callback(channel.get(), listener.get_first_message(), globals.get());
if (globals->message_logging)
LOG(INFO) << "Privileged process finishing callback.";
listener.pop_first_message();
}
}
if (active_names_at_start != IPC::GetActiveNameCount()) {
LOG(INFO) << "Memory leak!.";
}
LOG(INFO) << "Privileged process end.";
return 0;
}
// An unprivileged process makes a shared memory region, and writes a string to
// it. The SharedMemoryHandle is sent to the privileged process using Chrome
// IPC. The privileged process checks that it received the same memory region.
TEST_F(IPCAttachmentBrokerMacTest, SendSharedMemoryHandle) {
// Mach-based SharedMemory isn't support on OSX 10.6.
if (base::mac::IsOSSnowLeopard())
return;
CommonSetUp("SendSharedMemoryHandle");
SendMessage1(kDataBuffer1);
base::MessageLoop::current()->Run();
CommonTearDown();
}
void SendSharedMemoryHandleCallback(IPC::Sender* sender,
const IPC::Message& message,
ChildProcessGlobals* globals) {
bool success = CheckContentsOfMessage1(message, kDataBuffer1);
SendControlMessage(sender, success);
}
MULTIPROCESS_IPC_TEST_CLIENT_MAIN(SendSharedMemoryHandle) {
return CommonPrivilegedProcessMain(&SendSharedMemoryHandleCallback,
"SendSharedMemoryHandle");
}
// Similar to SendSharedMemoryHandle, but sends a very long shared memory
// region.
TEST_F(IPCAttachmentBrokerMacTest, SendSharedMemoryHandleLong) {
// Mach-based SharedMemory isn't support on OSX 10.6.
if (base::mac::IsOSSnowLeopard())
return;
CommonSetUp("SendSharedMemoryHandleLong");
std::string buffer(1 << 23, 'a');
SendMessage1(buffer);
base::MessageLoop::current()->Run();
CommonTearDown();
}
void SendSharedMemoryHandleLongCallback(IPC::Sender* sender,
const IPC::Message& message,
ChildProcessGlobals* globals) {
std::string buffer(1 << 23, 'a');
bool success = CheckContentsOfMessage1(message, buffer);
SendControlMessage(sender, success);
}
MULTIPROCESS_IPC_TEST_CLIENT_MAIN(SendSharedMemoryHandleLong) {
return CommonPrivilegedProcessMain(&SendSharedMemoryHandleLongCallback,
"SendSharedMemoryHandleLong");
}
// Similar to SendSharedMemoryHandle, but sends two different shared memory
// regions in two messages.
TEST_F(IPCAttachmentBrokerMacTest, SendTwoMessagesDifferentSharedMemoryHandle) {
// Mach-based SharedMemory isn't support on OSX 10.6.
if (base::mac::IsOSSnowLeopard())
return;
CommonSetUp("SendTwoMessagesDifferentSharedMemoryHandle");
SendMessage1(kDataBuffer1);
SendMessage1(kDataBuffer2);
base::MessageLoop::current()->Run();
CommonTearDown();
}
void SendTwoMessagesDifferentSharedMemoryHandleCallback(
IPC::Sender* sender,
const IPC::Message& message,
ChildProcessGlobals* globals) {
static int count = 0;
static bool success = true;
++count;
if (count == 1) {
success &= CheckContentsOfMessage1(message, kDataBuffer1);
} else if (count == 2) {
success &= CheckContentsOfMessage1(message, kDataBuffer2);
SendControlMessage(sender, success);
}
}
MULTIPROCESS_IPC_TEST_CLIENT_MAIN(SendTwoMessagesDifferentSharedMemoryHandle) {
return CommonPrivilegedProcessMain(
&SendTwoMessagesDifferentSharedMemoryHandleCallback,
"SendTwoMessagesDifferentSharedMemoryHandle");
}
// Similar to SendSharedMemoryHandle, but sends the same shared memory region in
// two messages.
TEST_F(IPCAttachmentBrokerMacTest, SendTwoMessagesSameSharedMemoryHandle) {
// Mach-based SharedMemory isn't support on OSX 10.6.
if (base::mac::IsOSSnowLeopard())
return;
CommonSetUp("SendTwoMessagesSameSharedMemoryHandle");
{
scoped_ptr<base::SharedMemory> shared_memory(
MakeSharedMemory(kDataBuffer1));
for (int i = 0; i < 2; ++i) {
IPC::Message* message =
new TestSharedMemoryHandleMsg1(100, shared_memory->handle(), 200);
sender()->Send(message);
}
}
base::MessageLoop::current()->Run();
CommonTearDown();
}
void SendTwoMessagesSameSharedMemoryHandleCallback(
IPC::Sender* sender,
const IPC::Message& message,
ChildProcessGlobals* globals) {
static int count = 0;
static base::SharedMemoryHandle handle1;
++count;
if (count == 1) {
handle1 = GetSharedMemoryHandleFromMsg1(message);
} else if (count == 2) {
base::SharedMemoryHandle handle2(GetSharedMemoryHandleFromMsg1(message));
bool success = CheckContentsOfTwoEquivalentSharedMemoryHandles(
handle1, handle2, kDataBuffer1);
SendControlMessage(sender, success);
}
}
MULTIPROCESS_IPC_TEST_CLIENT_MAIN(SendTwoMessagesSameSharedMemoryHandle) {
return CommonPrivilegedProcessMain(
&SendTwoMessagesSameSharedMemoryHandleCallback,
"SendTwoMessagesSameSharedMemoryHandle");
}
// Similar to SendSharedMemoryHandle, but sends one message with two different
// memory regions.
TEST_F(IPCAttachmentBrokerMacTest,
SendOneMessageWithTwoDifferentSharedMemoryHandles) {
// Mach-based SharedMemory isn't support on OSX 10.6.
if (base::mac::IsOSSnowLeopard())
return;
CommonSetUp("SendOneMessageWithTwoDifferentSharedMemoryHandles");
{
scoped_ptr<base::SharedMemory> shared_memory1(
MakeSharedMemory(kDataBuffer1));
scoped_ptr<base::SharedMemory> shared_memory2(
MakeSharedMemory(kDataBuffer2));
IPC::Message* message = new TestSharedMemoryHandleMsg2(
shared_memory1->handle(), shared_memory2->handle());
sender()->Send(message);
}
base::MessageLoop::current()->Run();
CommonTearDown();
}
void SendOneMessageWithTwoDifferentSharedMemoryHandlesCallback(
IPC::Sender* sender,
const IPC::Message& message,
ChildProcessGlobals* globals) {
base::SharedMemoryHandle handle1;
base::SharedMemoryHandle handle2;
if (!GetSharedMemoryHandlesFromMsg2(message, &handle1, &handle2)) {
LOG(ERROR) << "Failed to deserialize message.";
SendControlMessage(sender, false);
return;
}
bool success = CheckContentsOfSharedMemoryHandle(handle1, kDataBuffer1) &&
CheckContentsOfSharedMemoryHandle(handle2, kDataBuffer2);
SendControlMessage(sender, success);
}
MULTIPROCESS_IPC_TEST_CLIENT_MAIN(
SendOneMessageWithTwoDifferentSharedMemoryHandles) {
return CommonPrivilegedProcessMain(
&SendOneMessageWithTwoDifferentSharedMemoryHandlesCallback,
"SendOneMessageWithTwoDifferentSharedMemoryHandles");
}
// Similar to SendSharedMemoryHandle, but sends one message that contains the
// same memory region twice.
TEST_F(IPCAttachmentBrokerMacTest,
SendOneMessageWithTwoSameSharedMemoryHandles) {
// Mach-based SharedMemory isn't support on OSX 10.6.
if (base::mac::IsOSSnowLeopard())
return;
CommonSetUp("SendOneMessageWithTwoSameSharedMemoryHandles");
{
scoped_ptr<base::SharedMemory> shared_memory(
MakeSharedMemory(kDataBuffer1));
IPC::Message* message = new TestSharedMemoryHandleMsg2(
shared_memory->handle(), shared_memory->handle());
sender()->Send(message);
}
base::MessageLoop::current()->Run();
CommonTearDown();
}
void SendOneMessageWithTwoSameSharedMemoryHandlesCallback(
IPC::Sender* sender,
const IPC::Message& message,
ChildProcessGlobals* globals) {
base::SharedMemoryHandle handle1;
base::SharedMemoryHandle handle2;
if (!GetSharedMemoryHandlesFromMsg2(message, &handle1, &handle2)) {
LOG(ERROR) << "Failed to deserialize message.";
SendControlMessage(sender, false);
return;
}
bool success = CheckContentsOfTwoEquivalentSharedMemoryHandles(
handle1, handle2, kDataBuffer1);
SendControlMessage(sender, success);
}
MULTIPROCESS_IPC_TEST_CLIENT_MAIN(
SendOneMessageWithTwoSameSharedMemoryHandles) {
return CommonPrivilegedProcessMain(
&SendOneMessageWithTwoSameSharedMemoryHandlesCallback,
"SendOneMessageWithTwoSameSharedMemoryHandles");
}
// Sends one message with two Posix FDs and two Mach ports.
TEST_F(IPCAttachmentBrokerMacTest, SendPosixFDAndMachPort) {
// Mach-based SharedMemory isn't support on OSX 10.6.
if (base::mac::IsOSSnowLeopard())
return;
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
base::FilePath fp1, fp2;
ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir.path(), &fp1));
ASSERT_TRUE(base::CreateTemporaryFileInDir(temp_dir.path(), &fp2));
CommonSetUp("SendPosixFDAndMachPort");
{
scoped_ptr<base::SharedMemory> shared_memory1(
MakeSharedMemory(kDataBuffer1));
scoped_ptr<base::SharedMemory> shared_memory2(
MakeSharedMemory(kDataBuffer2));
base::FileDescriptor file_descriptor1(
MakeFileDescriptor(fp1, kDataBuffer3));
base::FileDescriptor file_descriptor2(
MakeFileDescriptor(fp2, kDataBuffer4));
IPC::Message* message = new TestSharedMemoryHandleMsg3(
file_descriptor1, shared_memory1->handle(), file_descriptor2,
shared_memory2->handle());
sender()->Send(message);
}
base::MessageLoop::current()->Run();
CommonTearDown();
}
void SendPosixFDAndMachPortCallback(IPC::Sender* sender,
const IPC::Message& message,
ChildProcessGlobals* globals) {
TestSharedMemoryHandleMsg3::Schema::Param p;
if (!TestSharedMemoryHandleMsg3::Read(&message, &p)) {
LOG(ERROR) << "Failed to deserialize message.";
SendControlMessage(sender, false);
return;
}
base::SharedMemoryHandle handle1 = base::get<1>(p);
base::SharedMemoryHandle handle2 = base::get<3>(p);
bool success1 = CheckContentsOfSharedMemoryHandle(handle1, kDataBuffer1) &&
CheckContentsOfSharedMemoryHandle(handle2, kDataBuffer2);
if (!success1)
LOG(ERROR) << "SharedMemoryHandles have wrong contents.";
bool success2 =
CheckContentsOfFileDescriptor(base::get<0>(p), kDataBuffer3) &&
CheckContentsOfFileDescriptor(base::get<2>(p), kDataBuffer4);
if (!success2)
LOG(ERROR) << "FileDescriptors have wrong contents.";
SendControlMessage(sender, success1 && success2);
}
MULTIPROCESS_IPC_TEST_CLIENT_MAIN(SendPosixFDAndMachPort) {
return CommonPrivilegedProcessMain(&SendPosixFDAndMachPortCallback,
"SendPosixFDAndMachPort");
}
// Similar to SendHandle, except the attachment's destination process is this
// process. This is an unrealistic scenario, but simulates an unprivileged
// process sending an attachment to another unprivileged process.
TEST_F(IPCAttachmentBrokerMacTest, SendSharedMemoryHandleToSelf) {
// Mach-based SharedMemory isn't support on OSX 10.6.
if (base::mac::IsOSSnowLeopard())
return;
SetBroker(new MockBroker);
CommonSetUp("SendSharedMemoryHandleToSelf");
// Technically, the channel is an endpoint, but we need the proxy listener to
// receive the messages so that it can quit the message loop.
channel()->SetAttachmentBrokerEndpoint(false);
get_proxy_listener()->set_listener(get_broker());
{
scoped_ptr<base::SharedMemory> shared_memory(
MakeSharedMemory(kDataBuffer1));
mach_port_urefs_t ref_count = IPC::GetMachRefCount(
shared_memory->handle().GetMemoryObject(), MACH_PORT_RIGHT_SEND);
IPC::Message* message =
new TestSharedMemoryHandleMsg1(100, shared_memory->handle(), 200);
sender()->Send(message);
// Wait until the child process has sent this process a message.
base::MessageLoop::current()->Run();
// Wait for any asynchronous activity to complete.
base::MessageLoop::current()->RunUntilIdle();
// Get the received attachment.
IPC::BrokerableAttachment::AttachmentId* id = get_observer()->get_id();
ASSERT_TRUE(id);
scoped_refptr<IPC::BrokerableAttachment> received_attachment;
get_broker()->GetAttachmentWithId(*id, &received_attachment);
ASSERT_NE(received_attachment.get(), nullptr);
// Check that it's has the same name, but that the ref count has increased.
base::mac::ScopedMachSendRight memory_object(
GetMachPortFromBrokeredAttachment(received_attachment));
ASSERT_EQ(memory_object, shared_memory->handle().GetMemoryObject());
EXPECT_EQ(ref_count + 1,
IPC::GetMachRefCount(shared_memory->handle().GetMemoryObject(),
MACH_PORT_RIGHT_SEND));
}
FinalCleanUp();
}
void SendSharedMemoryHandleToSelfCallback(IPC::Sender* sender,
const IPC::Message&,
ChildProcessGlobals* globals) {
// Do nothing special. The default behavior already runs the
// AttachmentBrokerPrivilegedMac.
}
MULTIPROCESS_IPC_TEST_CLIENT_MAIN(SendSharedMemoryHandleToSelf) {
return CommonPrivilegedProcessMain(&SendSharedMemoryHandleToSelfCallback,
"SendSharedMemoryHandleToSelf");
}
// Similar to SendSharedMemoryHandle, but uses a ChannelProxy instead of a
// Channel.
TEST_F(IPCAttachmentBrokerMacTest, SendSharedMemoryHandleChannelProxy) {
// Mach-based SharedMemory isn't support on OSX 10.6.
if (base::mac::IsOSSnowLeopard())
return;
Init("SendSharedMemoryHandleChannelProxy");
MachPreForkSetUp();
SetBroker(new IPC::AttachmentBrokerUnprivilegedMac);
get_broker()->AddObserver(get_observer(), task_runner());
scoped_ptr<base::Thread> thread(
new base::Thread("ChannelProxyTestServerThread"));
base::Thread::Options options;
options.message_loop_type = base::MessageLoop::TYPE_IO;
thread->StartWithOptions(options);
CreateChannelProxy(get_proxy_listener(), thread->task_runner().get());
get_broker()->DesignateBrokerCommunicationChannel(channel_proxy());
ASSERT_TRUE(StartClient());
MachPostForkSetUp();
active_names_at_start_ = IPC::GetActiveNameCount();
get_proxy_listener()->set_listener(get_result_listener());
SendMessage1(kDataBuffer1);
base::MessageLoop::current()->Run();
CheckChildResult();
// There should be no leaked names.
EXPECT_EQ(active_names_at_start_, IPC::GetActiveNameCount());
// Close the channel so the client's OnChannelError() gets fired.
channel_proxy()->Close();
EXPECT_TRUE(WaitForClientShutdown());
DestroyChannelProxy();
}
void SendSharedMemoryHandleChannelProxyCallback(IPC::Sender* sender,
const IPC::Message& message,
ChildProcessGlobals* globals) {
bool success = CheckContentsOfMessage1(message, kDataBuffer1);
SendControlMessage(sender, success);
}
MULTIPROCESS_IPC_TEST_CLIENT_MAIN(SendSharedMemoryHandleChannelProxy) {
return CommonPrivilegedProcessMain(
&SendSharedMemoryHandleChannelProxyCallback,
"SendSharedMemoryHandleChannelProxy");
}
// Similar to SendSharedMemoryHandle, but first makes a copy of the handle using
// ShareToProcess().
TEST_F(IPCAttachmentBrokerMacTest, ShareToProcess) {
// Mach-based SharedMemory isn't support on OSX 10.6.
if (base::mac::IsOSSnowLeopard())
return;
CommonSetUp("ShareToProcess");
{
scoped_ptr<base::SharedMemory> shared_memory(
MakeSharedMemory(kDataBuffer1));
base::SharedMemoryHandle new_handle;
ASSERT_TRUE(shared_memory->ShareToProcess(0, &new_handle));
IPC::Message* message =
new TestSharedMemoryHandleMsg1(100, new_handle, 200);
sender()->Send(message);
}
base::MessageLoop::current()->Run();
CommonTearDown();
}
void ShareToProcessCallback(IPC::Sender* sender,
const IPC::Message& message,
ChildProcessGlobals* globals) {
bool success = CheckContentsOfMessage1(message, kDataBuffer1);
SendControlMessage(sender, success);
}
MULTIPROCESS_IPC_TEST_CLIENT_MAIN(ShareToProcess) {
return CommonPrivilegedProcessMain(&ShareToProcessCallback, "ShareToProcess");
}
// Similar to ShareToProcess, but instead shares the memory object only with
// read permissions.
TEST_F(IPCAttachmentBrokerMacTest, ShareReadOnlyToProcess) {
// Mach-based SharedMemory isn't support on OSX 10.6.
if (base::mac::IsOSSnowLeopard())
return;
CommonSetUp("ShareReadOnlyToProcess");
{
scoped_ptr<base::SharedMemory> shared_memory(
MakeSharedMemory(kDataBuffer1));
base::SharedMemoryHandle new_handle;
ASSERT_TRUE(shared_memory->ShareReadOnlyToProcess(0, &new_handle));
IPC::Message* message =
new TestSharedMemoryHandleMsg1(100, new_handle, 200);
sender()->Send(message);
}
base::MessageLoop::current()->Run();
CommonTearDown();
}
void ShareReadOnlyToProcessCallback(IPC::Sender* sender,
const IPC::Message& message,
ChildProcessGlobals* globals) {
base::SharedMemoryHandle shm(GetSharedMemoryHandleFromMsg1(message));
// Try to map the memory as writable.
scoped_ptr<base::SharedMemory> shared_memory(
MapSharedMemoryHandle(shm, false));
ASSERT_EQ(nullptr, shared_memory->memory());
// Now try as read-only.
scoped_ptr<base::SharedMemory> shared_memory2(
MapSharedMemoryHandle(shm.Duplicate(), true));
int current_prot, max_prot;
ASSERT_TRUE(IPC::GetMachProtections(shared_memory2->memory(),
shared_memory2->mapped_size(),
&current_prot, &max_prot));
ASSERT_EQ(VM_PROT_READ, current_prot);
ASSERT_EQ(VM_PROT_READ, max_prot);
bool success =
memcmp(shared_memory2->memory(), kDataBuffer1, strlen(kDataBuffer1)) == 0;
SendControlMessage(sender, success);
}
MULTIPROCESS_IPC_TEST_CLIENT_MAIN(ShareReadOnlyToProcess) {
return CommonPrivilegedProcessMain(&ShareReadOnlyToProcessCallback,
"ShareReadOnlyToProcess");
}
// Similar to SendSharedMemoryHandleToSelf, but the child process pretends to
// not have the task port for the parent process.
TEST_F(IPCAttachmentBrokerMacTest, SendSharedMemoryHandleToSelfDelayedPort) {
// Mach-based SharedMemory isn't support on OSX 10.6.
if (base::mac::IsOSSnowLeopard())
return;
SetBroker(new MockBroker);
CommonSetUp("SendSharedMemoryHandleToSelfDelayedPort");
// Technically, the channel is an endpoint, but we need the proxy listener to
// receive the messages so that it can quit the message loop.
channel()->SetAttachmentBrokerEndpoint(false);
get_proxy_listener()->set_listener(get_broker());
{
scoped_ptr<base::SharedMemory> shared_memory(
MakeSharedMemory(kDataBuffer1));
mach_port_urefs_t ref_count = IPC::GetMachRefCount(
shared_memory->handle().GetMemoryObject(), MACH_PORT_RIGHT_SEND);
std::vector<IPC::BrokerableAttachment::AttachmentId> ids;
const int kMessagesToTest = 3;
for (int i = 0; i < kMessagesToTest; ++i) {
base::SharedMemoryHandle h = shared_memory->handle().Duplicate();
ids.push_back(
IPC::BrokerableAttachment::AttachmentId::CreateIdWithRandomNonce());
IPC::internal::MachPortAttachmentMac::WireFormat wire_format(
h.GetMemoryObject(), getpid(), ids[i]);
sender()->Send(new AttachmentBrokerMsg_DuplicateMachPort(wire_format));
// Send a dummy message, which will trigger the callback handler in the
// child process.
sender()->Send(new TestSharedMemoryHandleMsg4(1));
}
int received_message_count = 0;
while (received_message_count < kMessagesToTest) {
// Wait until the child process has sent this process a message.
base::MessageLoop::current()->Run();
// Wait for any asynchronous activity to complete.
base::MessageLoop::current()->RunUntilIdle();
while (get_proxy_listener()->has_message()) {
get_proxy_listener()->pop_first_message();
received_message_count++;
}
}
for (int i = 0; i < kMessagesToTest; ++i) {
IPC::BrokerableAttachment::AttachmentId* id = &ids[i];
ASSERT_TRUE(id);
scoped_refptr<IPC::BrokerableAttachment> received_attachment;
get_broker()->GetAttachmentWithId(*id, &received_attachment);
ASSERT_NE(received_attachment.get(), nullptr);
base::mac::ScopedMachSendRight memory_object(
GetMachPortFromBrokeredAttachment(received_attachment));
ASSERT_EQ(shared_memory->handle().GetMemoryObject(), memory_object);
}
// Check that the ref count hasn't changed.
EXPECT_EQ(ref_count,
IPC::GetMachRefCount(shared_memory->handle().GetMemoryObject(),
MACH_PORT_RIGHT_SEND));
}
FinalCleanUp();
}
void SendSharedMemoryHandleToSelfDelayedPortCallback(
IPC::Sender* sender,
const IPC::Message& message,
ChildProcessGlobals* globals) {
static int i = 0;
static base::ProcessId pid = message.get_sender_pid();
static mach_port_t task_port = globals->port_provider.TaskForPid(pid);
++i;
if (i == 1) {
// Pretend to not have the task port for the parent.
globals->port_provider.ClearPortMap();
} else if (i == 2) {
// Intentionally do nothing.
} else if (i == 3) {
// Setting the task port should trigger callbacks, eventually resulting in
// multiple attachment broker messages.
globals->port_provider.InsertEntry(pid, task_port);
}
}
MULTIPROCESS_IPC_TEST_CLIENT_MAIN(SendSharedMemoryHandleToSelfDelayedPort) {
return CommonPrivilegedProcessMain(
&SendSharedMemoryHandleToSelfDelayedPortCallback,
"SendSharedMemoryHandleToSelfDelayedPort");
}
// Tests the memory usage characteristics of attachment brokering a single large
// message. This test has the *potential* to be flaky, since it compares
// resident memory at different points in time, and that measurement is
// non-deterministic.
TEST_F(IPCAttachmentBrokerMacTest, MemoryUsageLargeMessage) {
// Mach-based SharedMemory isn't support on OSX 10.6.
if (base::mac::IsOSSnowLeopard())
return;
CommonSetUp("MemoryUsageLargeMessage");
std::string test_string(g_large_message_size, 'a');
SendMessage1(test_string);
base::MessageLoop::current()->Run();
CommonTearDown();
}
void MemoryUsageLargeMessageCallback(IPC::Sender* sender,
const IPC::Message& message,
ChildProcessGlobals* globals) {
EXPECT_LE(GetResidentSize(),
globals->initial_resident_size + g_expected_memory_increase);
base::SharedMemoryHandle shm(GetSharedMemoryHandleFromMsg1(message));
scoped_ptr<base::SharedMemory> shared_memory(
MapSharedMemoryHandle(shm, false));
EXPECT_LE(GetResidentSize(),
globals->initial_resident_size + g_expected_memory_increase);
char* addr = static_cast<char*>(shared_memory->memory());
for (size_t i = 0; i < g_large_message_size; i += 1024) {
addr[i] = 'a';
}
EXPECT_GE(GetResidentSize(),
globals->initial_resident_size + g_large_message_size);
shared_memory.reset();
#if !defined(ADDRESS_SANITIZER) && !defined(LEAK_SANITIZER) && \
!defined(MEMORY_SANITIZER) && !defined(THREAD_SANITIZER) && \
!defined(UNDEFINED_SANITIZER)
// Under a sanitizer build, releasing memory does not necessarily reduce the
// amount of resident memory.
EXPECT_LE(GetResidentSize(),
globals->initial_resident_size + g_expected_memory_increase);
#endif
SendControlMessage(sender, true);
}
MULTIPROCESS_IPC_TEST_CLIENT_MAIN(MemoryUsageLargeMessage) {
return CommonPrivilegedProcessMain(&MemoryUsageLargeMessageCallback,
"MemoryUsageLargeMessage");
}
// Tests the memory usage characteristics of attachment brokering many small
// messages. This test has the *potential* to be flaky, since it compares
// resident memory at different points in time, and that measurement is
// non-deterministic.
TEST_F(IPCAttachmentBrokerMacTest, MemoryUsageManyMessages) {
// Mach-based SharedMemory isn't support on OSX 10.6.
if (base::mac::IsOSSnowLeopard())
return;
CommonSetUp("MemoryUsageManyMessages");
for (int i = 0; i < g_large_message_count; ++i) {
std::string message = base::IntToString(i);
message += '\0';
size_t end = message.size();
message.resize(g_medium_message_size);
std::fill(message.begin() + end, message.end(), 'a');
SendMessage1(message);
base::MessageLoop::current()->RunUntilIdle();
}
if (get_result_listener()->get_result() == RESULT_UNKNOWN)
base::MessageLoop::current()->Run();
CommonTearDown();
}
void MemoryUsageManyMessagesCallback(IPC::Sender* sender,
const IPC::Message& message,
ChildProcessGlobals* globals) {
static int message_index = 0;
{
// Map the shared memory, and make sure that its pages are counting towards
// resident size.
base::SharedMemoryHandle shm(GetSharedMemoryHandleFromMsg1(message));
scoped_ptr<base::SharedMemory> shared_memory(
MapSharedMemoryHandle(shm, false));
char* addr = static_cast<char*>(shared_memory->memory());
std::string message_string(addr);
int message_int;
ASSERT_TRUE(base::StringToInt(message_string, &message_int));
ASSERT_EQ(message_index, message_int);
for (size_t i = 0; i < g_medium_message_size; i += 1024) {
addr[i] = 'a';
}
}
++message_index;
if (message_index == 1) {
// Disable message logging, since it significantly contributes towards total
// memory usage.
LOG(INFO) << "Disable privileged process message logging.";
globals->message_logging = false;
}
if (message_index == g_large_message_count) {
size_t memory_increase_kb =
(GetResidentSize() - globals->initial_resident_size) / 1024;
LOG(INFO) << "Increase in memory usage in KB: " << memory_increase_kb;
#if defined(ADDRESS_SANITIZER) || defined(LEAK_SANITIZER) || \
defined(MEMORY_SANITIZER) || defined(THREAD_SANITIZER) || \
defined(UNDEFINED_SANITIZER)
// Under a sanitizer build, releasing memory does not necessarily reduce the
// amount of resident memory.
bool success = true;
#else
// The total increase in resident size should be less than 1MB. The exact
// amount is not deterministic.
bool success = memory_increase_kb < 1024;
#endif
SendControlMessage(sender, success);
}
}
MULTIPROCESS_IPC_TEST_CLIENT_MAIN(MemoryUsageManyMessages) {
return CommonPrivilegedProcessMain(&MemoryUsageManyMessagesCallback,
"MemoryUsageManyMessages");
}
} // namespace