[go: nahoru, domu]

blob: 421ca1e61f76caa0bb32c937e03890baf93078db [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "mojo/core/ipcz_driver/transport.h"
#include <cstring>
#include <queue>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "base/containers/span.h"
#include "base/files/file.h"
#include "base/files/scoped_temp_dir.h"
#include "base/memory/raw_ref.h"
#include "base/memory/scoped_refptr.h"
#include "base/ranges/algorithm.h"
#include "base/synchronization/condition_variable.h"
#include "base/synchronization/lock.h"
#include "base/synchronization/waitable_event.h"
#include "base/test/gtest_util.h"
#include "base/test/task_environment.h"
#include "build/build_config.h"
#include "mojo/core/ipcz_driver/driver.h"
#include "mojo/core/ipcz_driver/shared_buffer.h"
#include "mojo/core/ipcz_driver/transmissible_platform_handle.h"
#include "mojo/core/ipcz_driver/wrapped_platform_handle.h"
#include "mojo/core/test/mojo_test_base.h"
#include "mojo/public/c/system/platform_handle.h"
#include "mojo/public/cpp/platform/platform_channel.h"
#include "mojo/public/cpp/platform/platform_handle.h"
#include "mojo/public/cpp/system/platform_handle.h"
namespace mojo::core::ipcz_driver {
namespace {
struct TestMessage {
TestMessage() = default;
explicit TestMessage(std::string_view str,
base::span<IpczDriverHandle> handles = {})
: bytes(str.begin(), str.end()),
handles(handles.begin(), handles.end()) {}
std::string as_string() const {
return {reinterpret_cast<const char*>(bytes.data()), bytes.size()};
}
void Transmit(Transport& transmitter) {
transmitter.Transmit(base::make_span(bytes), base::make_span(handles));
}
std::vector<uint8_t> bytes;
std::vector<IpczDriverHandle> handles;
};
// These tests use Mojo and Mojo's existing multiprocess test facilities to set
// up a multiprocess environment and send an initial transport handle to the
// child process.
class MojoIpczTransportTest : public test::MojoTestBase {
protected:
// Creates a new ad hoc ipcz Transport object from a new PlatformChannel. One
// end of the channel is returned as a Transport while the other is sent over
// `pipe` to `process`.
static scoped_refptr<Transport> CreateAndSendTransport(
MojoHandle pipe,
const base::Process& process,
bool untrusted = false) {
PlatformChannel channel;
MojoHandle transport_for_client =
WrapPlatformHandle(channel.TakeRemoteEndpoint().TakePlatformHandle())
.release()
.value();
WriteMessageWithHandles(pipe, "", &transport_for_client, 1);
return Transport::Create(
{.source = Transport::kBroker, .destination = Transport::kNonBroker},
channel.TakeLocalEndpoint(), process.Duplicate(), untrusted);
}
// Retrieves a PlatformChannel endpoint from `pipe` and returns a newly
// constructed Transport over it.
static scoped_refptr<Transport> ReceiveTransport(MojoHandle pipe) {
MojoHandle transport_for_client;
ReadMessageWithHandles(pipe, &transport_for_client, 1);
PlatformHandle handle =
UnwrapPlatformHandle(ScopedHandle(Handle(transport_for_client)));
return Transport::Create(
{.source = Transport::kNonBroker, .destination = Transport::kBroker},
PlatformChannelEndpoint(std::move(handle)));
}
static TestMessage SerializeObjectFor(Transport& transmitter,
scoped_refptr<ObjectBase> object) {
size_t num_bytes = 0;
size_t num_handles = 0;
EXPECT_EQ(IPCZ_RESULT_RESOURCE_EXHAUSTED,
transmitter.SerializeObject(*object, nullptr, &num_bytes, nullptr,
&num_handles));
TestMessage message;
message.bytes.resize(num_bytes);
message.handles.resize(num_handles);
EXPECT_EQ(IPCZ_RESULT_OK, transmitter.SerializeObject(
*object, message.bytes.data(), &num_bytes,
message.handles.data(), &num_handles));
return message;
}
template <typename T>
static scoped_refptr<T> DeserializeObjectFrom(Transport& receiver,
const TestMessage& message) {
scoped_refptr<ObjectBase> object;
const IpczResult result =
receiver.DeserializeObject(base::make_span(message.bytes),
base::make_span(message.handles), object);
CHECK_EQ(result, IPCZ_RESULT_OK);
CHECK_EQ(object->type(), T::object_type());
return base::WrapRefCounted(static_cast<T*>(object.get()));
}
static TestMessage SerializeFileFor(Transport& transmitter, base::File file) {
auto wrapper = base::MakeRefCounted<WrappedPlatformHandle>(
PlatformHandle(base::ScopedPlatformFile(file.TakePlatformFile())));
return SerializeObjectFor(transmitter, std::move(wrapper));
}
static base::File DeserializeFileFrom(Transport& receiver,
const TestMessage& message) {
scoped_refptr<WrappedPlatformHandle> wrapper =
DeserializeObjectFrom<WrappedPlatformHandle>(receiver, message);
CHECK(wrapper);
#if BUILDFLAG(IS_WIN)
return base::File(wrapper->TakeHandle().TakeHandle());
#elif BUILDFLAG(IS_POSIX) || BUILDFLAG(IS_FUCHSIA)
return base::File(wrapper->TakeHandle().TakeFD());
#endif
}
static TestMessage SerializeRegionFor(Transport& transmitter,
base::UnsafeSharedMemoryRegion region) {
auto handle = base::UnsafeSharedMemoryRegion::TakeHandleForSerialization(
std::move(region));
return SerializeObjectFor(
transmitter, base::MakeRefCounted<SharedBuffer>(std::move(handle)));
}
base::UnsafeSharedMemoryRegion BufferObjectToRegion(
scoped_refptr<SharedBuffer> buffer) {
return base::UnsafeSharedMemoryRegion::Deserialize(
std::move(buffer->region()));
}
};
// TransportListener provides a convenient way for tests to listen to incoming
// events on a Transport.
class TransportListener {
public:
explicit TransportListener(Transport& transport) : transport_(transport) {
transport_->Activate(reinterpret_cast<IpczHandle>(this),
&TransportListener::OnActivity);
}
~TransportListener() {
transport_->Deactivate();
deactivation_event_.Wait();
}
TestMessage WaitForNextMessage() {
base::AutoLock lock(lock_);
while (messages_.empty()) {
have_messages_.Wait();
}
TestMessage message = std::move(messages_.front());
messages_.pop();
return message;
}
void WaitForDisconnect() { disconnect_event_.Wait(); }
private:
static IpczResult OnActivity(IpczHandle transport,
const void* data,
size_t num_bytes,
const IpczDriverHandle* handles,
size_t num_handles,
IpczTransportActivityFlags flags,
const void*) {
auto* listener = reinterpret_cast<TransportListener*>(transport);
auto bytes = base::make_span(static_cast<const uint8_t*>(data), num_bytes);
listener->HandleActivity(bytes, base::make_span(handles, num_handles),
flags);
return IPCZ_RESULT_OK;
}
void HandleActivity(base::span<const uint8_t> bytes,
base::span<const IpczDriverHandle> handles,
IpczTransportActivityFlags flags) {
if (flags & IPCZ_TRANSPORT_ACTIVITY_ERROR) {
disconnect_event_.Signal();
return;
}
if (flags & IPCZ_TRANSPORT_ACTIVITY_DEACTIVATED) {
deactivation_event_.Signal();
return;
}
TestMessage message;
message.bytes.resize(bytes.size());
message.handles.resize(handles.size());
base::ranges::copy(bytes, message.bytes.begin());
base::ranges::copy(handles, message.handles.begin());
base::AutoLock lock(lock_);
messages_.push(std::move(message));
have_messages_.Signal();
}
const raw_ref<Transport> transport_;
base::Lock lock_;
base::ConditionVariable have_messages_{&lock_};
std::queue<TestMessage> messages_ GUARDED_BY(lock_);
base::WaitableEvent disconnect_event_;
base::WaitableEvent deactivation_event_;
};
constexpr std::string_view kMessage1 = "we are messages";
constexpr std::string_view kMessage2 = "tremendous messages";
constexpr std::string_view kMessage3 = "the very best messages";
constexpr std::string_view kMessage4 = "everyone says so";
DEFINE_TEST_CLIENT_TEST_WITH_PIPE(BasicTransmitClient,
MojoIpczTransportTest,
h) {
scoped_refptr<Transport> transport = ReceiveTransport(h);
TransportListener listener(*transport);
TestMessage(kMessage3).Transmit(*transport);
TestMessage(kMessage4).Transmit(*transport);
EXPECT_EQ(kMessage1, listener.WaitForNextMessage().as_string());
EXPECT_EQ(kMessage2, listener.WaitForNextMessage().as_string());
EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h));
}
TEST_F(MojoIpczTransportTest, BasicTransmit) {
RunTestClientWithController("BasicTransmitClient", [&](ClientController& c) {
scoped_refptr<Transport> transport =
CreateAndSendTransport(c.pipe(), c.process());
TransportListener listener(*transport);
TestMessage(kMessage1).Transmit(*transport);
TestMessage(kMessage2).Transmit(*transport);
EXPECT_EQ(kMessage3, listener.WaitForNextMessage().as_string());
EXPECT_EQ(kMessage4, listener.WaitForNextMessage().as_string());
listener.WaitForDisconnect();
});
}
// Transport on Windows does not support out-of-band handle transfer, so this
// test is impossible there. Windows handle transmission is instead covered by
// tests which more broadly cover driver object serialization.
#if !BUILDFLAG(IS_WIN)
IpczDriverHandle MakeHandleFromEndpoint(PlatformChannelEndpoint endpoint) {
return TransmissiblePlatformHandle::ReleaseAsHandle(
base::MakeRefCounted<TransmissiblePlatformHandle>(
endpoint.TakePlatformHandle()));
}
scoped_refptr<Transport> MakeTransportFromMessage(const TestMessage& message) {
CHECK_EQ(message.handles.size(), 1u);
auto handle = TransmissiblePlatformHandle::TakeFromHandle(message.handles[0]);
CHECK(handle);
return Transport::Create(
{.source = Transport::kNonBroker, .destination = Transport::kBroker},
PlatformChannelEndpoint(handle->TakeHandle()));
}
DEFINE_TEST_CLIENT_TEST_WITH_PIPE(TransmitHandleClient,
MojoIpczTransportTest,
h) {
scoped_refptr<Transport> transport = ReceiveTransport(h);
scoped_refptr<Transport> new_transport1;
scoped_refptr<Transport> new_transport2;
{
TransportListener listener(*transport);
new_transport1 = MakeTransportFromMessage(listener.WaitForNextMessage());
new_transport2 = MakeTransportFromMessage(listener.WaitForNextMessage());
}
TransportListener listener1(*new_transport1);
TransportListener listener2(*new_transport2);
TestMessage(kMessage3).Transmit(*new_transport1);
TestMessage(kMessage4).Transmit(*new_transport2);
EXPECT_EQ(kMessage1, listener1.WaitForNextMessage().as_string());
EXPECT_EQ(kMessage2, listener2.WaitForNextMessage().as_string());
EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h));
}
TEST_F(MojoIpczTransportTest, TransmitHandle) {
RunTestClientWithController("TransmitHandleClient", [&](ClientController& c) {
scoped_refptr<Transport> transport =
CreateAndSendTransport(c.pipe(), c.process());
// The PlatformHandle backing a PlatformChannelEndpoint is already
// transmissible on all applicable platforms, so we can conveniently test
// handle transmission without depending on driver object serialization.
PlatformChannel channel1;
auto new_transport1 = Transport::Create(
{.source = Transport::kBroker, .destination = Transport::kNonBroker},
channel1.TakeLocalEndpoint(), c.process().Duplicate());
PlatformChannel channel2;
auto new_transport2 = Transport::Create(
{.source = Transport::kBroker, .destination = Transport::kNonBroker},
channel2.TakeLocalEndpoint(), c.process().Duplicate());
IpczDriverHandle handle1 =
MakeHandleFromEndpoint(channel1.TakeRemoteEndpoint());
IpczDriverHandle handle2 =
MakeHandleFromEndpoint(channel2.TakeRemoteEndpoint());
{
TransportListener listener(*transport);
TestMessage("!", {&handle1, 1u}).Transmit(*transport);
TestMessage("!", {&handle2, 1u}).Transmit(*transport);
listener.WaitForDisconnect();
}
TransportListener listener1(*new_transport1);
TransportListener listener2(*new_transport2);
TestMessage(kMessage1).Transmit(*new_transport1);
TestMessage(kMessage2).Transmit(*new_transport2);
EXPECT_EQ(kMessage3, listener1.WaitForNextMessage().as_string());
EXPECT_EQ(kMessage4, listener2.WaitForNextMessage().as_string());
listener1.WaitForDisconnect();
listener2.WaitForDisconnect();
});
}
#endif // !BUILDFLAG(IS_WIN)
DEFINE_TEST_CLIENT_TEST_WITH_PIPE(TransmitSerializedTransportClient,
MojoIpczTransportTest,
h) {
scoped_refptr<Transport> transport = ReceiveTransport(h);
scoped_refptr<Transport> new_transport;
{
TransportListener listener(*transport);
new_transport = DeserializeObjectFrom<Transport>(
*transport, listener.WaitForNextMessage());
}
TransportListener listener(*new_transport);
TestMessage(kMessage3).Transmit(*new_transport);
TestMessage(kMessage4).Transmit(*new_transport);
EXPECT_EQ(kMessage1, listener.WaitForNextMessage().as_string());
EXPECT_EQ(kMessage2, listener.WaitForNextMessage().as_string());
EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h));
}
TEST_F(MojoIpczTransportTest, TransmitSerializedTransport) {
RunTestClientWithController(
"TransmitSerializedTransportClient", [&](ClientController& c) {
scoped_refptr<Transport> transport =
CreateAndSendTransport(c.pipe(), c.process());
auto [our_new_transport, their_new_transport] =
Transport::CreatePair(Transport::kBroker, Transport::kNonBroker);
{
TransportListener listener(*transport);
SerializeObjectFor(*transport, std::move(their_new_transport))
.Transmit(*transport);
listener.WaitForDisconnect();
}
TransportListener listener(*our_new_transport);
TestMessage(kMessage1).Transmit(*our_new_transport);
TestMessage(kMessage2).Transmit(*our_new_transport);
EXPECT_EQ(kMessage3, listener.WaitForNextMessage().as_string());
EXPECT_EQ(kMessage4, listener.WaitForNextMessage().as_string());
listener.WaitForDisconnect();
});
}
DEFINE_TEST_CLIENT_TEST_WITH_PIPE(TransmitFileClient,
MojoIpczTransportTest,
h) {
scoped_refptr<Transport> transport = ReceiveTransport(h);
TransportListener listener(*transport);
base::File file =
DeserializeFileFrom(*transport, listener.WaitForNextMessage());
std::vector<char> data(file.GetLength());
file.Read(0, data.data(), data.size());
EXPECT_EQ(kMessage1, std::string(data.begin(), data.end()));
EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h));
}
class MojoIpczTransportSecurityTest
: public MojoIpczTransportTest,
public ::testing::WithParamInterface<
std::tuple</*enforcement_enabled=*/bool,
/*add_no_execute_flags=*/bool>> {
protected:
bool IsEnforcementEnabled() {
// Enforcement only happens on Windows.
#if BUILDFLAG(IS_WIN)
return std::get<0>(GetParam());
#else
return false;
#endif
}
bool ShouldMarkNoExecute() { return std::get<1>(GetParam()); }
};
TEST_P(MojoIpczTransportSecurityTest, TransmitFile) {
RunTestClientWithController("TransmitFileClient", [&](ClientController& c) {
scoped_refptr<Transport> transport =
CreateAndSendTransport(c.pipe(), c.process(), IsEnforcementEnabled());
base::ScopedTempDir temp_dir;
CHECK(temp_dir.CreateUniqueTempDir());
int32_t flags = base::File::FLAG_CREATE | base::File::FLAG_READ |
base::File::FLAG_WRITE;
if (ShouldMarkNoExecute()) {
flags = base::File::AddFlagsForPassingToUntrustedProcess(flags);
}
base::File new_file(temp_dir.GetPath().AppendASCII("testfile"), flags);
new_file.Write(0, kMessage1.data(), kMessage1.size());
TransportListener listener(*transport);
if (IsEnforcementEnabled() && !ShouldMarkNoExecute()) {
EXPECT_DCHECK_DEATH_WITH(
{
SerializeFileFor(*transport, std::move(new_file))
.Transmit(*transport);
},
"Transfer of writable handle to executable file to an untrusted "
"process");
// In this case, the message was never sent, because either DCHECK was
// disabled so SerializeFileFor was never called, or the transport crashed
// the process. In either case, the client is sitting there waiting for a
// file to arrive, so send a read-only version to complete the test.
base::File read_only_file =
base::File(temp_dir.GetPath().AppendASCII("testfile"),
base::File::FLAG_OPEN | base::File::FLAG_READ);
SerializeFileFor(*transport, std::move(read_only_file))
.Transmit(*transport);
} else {
SerializeFileFor(*transport, std::move(new_file)).Transmit(*transport);
}
listener.WaitForDisconnect();
});
}
INSTANTIATE_TEST_SUITE_P(
All,
MojoIpczTransportSecurityTest,
testing::Combine(/*enforcement_enabled=*/testing::Bool(),
/*add_no_execute_flags=*/testing::Bool()));
constexpr std::string_view kMemoryMessage = "mojo wuz here";
DEFINE_TEST_CLIENT_TEST_WITH_PIPE(TransmitMemoryClient,
MojoIpczTransportTest,
h) {
scoped_refptr<Transport> transport = ReceiveTransport(h);
TransportListener listener(*transport);
const TestMessage message = listener.WaitForNextMessage();
auto region = base::UnsafeSharedMemoryRegion::Deserialize(std::move(
DeserializeObjectFrom<SharedBuffer>(*transport, message)->region()));
EXPECT_EQ(kMemoryMessage.size(), region.GetSize());
auto mapping = region.Map();
auto contents = std::string_view(static_cast<const char*>(mapping.memory()),
kMemoryMessage.size());
EXPECT_EQ(kMemoryMessage, contents);
EXPECT_EQ(MOJO_RESULT_OK, MojoClose(h));
}
TEST_F(MojoIpczTransportTest, TransmitMemory) {
RunTestClientWithController("TransmitMemoryClient", [&](ClientController& c) {
scoped_refptr<Transport> transport =
CreateAndSendTransport(c.pipe(), c.process());
auto region = base::UnsafeSharedMemoryRegion::Create(kMemoryMessage.size());
auto mapping = region.Map();
memcpy(mapping.memory(), kMemoryMessage.data(), kMemoryMessage.size());
auto buffer = SharedBuffer::MakeForRegion(std::move(region));
TransportListener listener(*transport);
SerializeObjectFor(*transport, std::move(buffer)).Transmit(*transport);
listener.WaitForDisconnect();
});
}
} // namespace
} // namespace mojo::core::ipcz_driver