[go: nahoru, domu]

blob: fa572f7f22a7647e3b4cdf80a0af95bd01a313c6 [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 <utility>
#include <vector>
#include "base/check_op.h"
#include "base/containers/stack_container.h"
#include "base/functional/overloaded.h"
#include "base/memory/scoped_refptr.h"
#include "base/numerics/safe_conversions.h"
#include "base/process/process.h"
#include "base/task/single_thread_task_runner.h"
#include "build/build_config.h"
#include "mojo/core/core.h"
#include "mojo/core/ipcz_driver/data_pipe.h"
#include "mojo/core/ipcz_driver/invitation.h"
#include "mojo/core/ipcz_driver/object.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/public/cpp/platform/platform_channel.h"
#include "mojo/public/cpp/platform/platform_channel_endpoint.h"
#include "mojo/public/cpp/platform/platform_handle.h"
#include "third_party/abseil-cpp/absl/types/variant.h"
#include "third_party/ipcz/include/ipcz/ipcz.h"
#if BUILDFLAG(IS_WIN)
#include "mojo/public/cpp/platform/platform_handle_security_util_win.h"
#endif
namespace mojo::core::ipcz_driver {
namespace {
#if BUILDFLAG(IS_WIN)
// Objects encode their Windows handles as 64-bit HANDLE values within messages.
//
// Windows does not provide any out-of-band mechanism for transmitting objects
// between processes (such as sendmsg() ancillary data on POSIX). Instead there
// is only the DuplicateHandle() API which allows processes to clone object
// handles to or from each others' processes. Therefore every handle
// transmission requires at least one end of the Transport to have sufficient
// access rights and resources to use DuplicateHandle() in reference to handles
// in the other endpoint's process. Handles may be transmitted either as valid
// values from the sender's own process; or as valid values in the recipient's
// process, already duplicated there by the sender.
//
// In general senders always send handles owned by the recipient if capable of
// doing so. If sent handles are owned by the sender, they can only be decoded
// if the recipient itself is sufficiently privileged and capable to duplicate
// handles from the sending process.
//
// This enumeration indicates whether handle values encoded by a serialized
// object belong to the sender or the recipient.
enum HandleOwner : uint8_t {
// Encoded HANDLEs belong to the sending process. For the recipient to use
// these, they must have a handle to the sending process with access rights to
// duplicate handles from there.
kSender,
// Encoded HANDLEs belong to the recipient's process. The recipient can use
// these handles as-is. Only brokers should be trusted to send handles that
// already belong to the recipient.
kRecipient,
};
// HANDLE value size varies by architecture. We always encode them with 64 bits.
using HandleData = uint64_t;
HandleData HandleToData(HANDLE handle) {
return static_cast<HandleData>(reinterpret_cast<uintptr_t>(handle));
}
HANDLE DataToHandle(HandleData data) {
return reinterpret_cast<HANDLE>(static_cast<uintptr_t>(data));
}
#endif
// Header serialized at the beginning of all mojo-ipcz driver objects.
struct IPCZ_ALIGN(8) ObjectHeader {
// The size of this header in bytes. Used for versioning.
uint32_t size;
// Identifies the type of object serialized.
ObjectBase::Type type;
#if BUILDFLAG(IS_WIN)
// On Windows only, platform handles are serialized as part of object data.
// This identifies how many packed HANDLE values immediately follow this
// header and precede the actual object data.
uint32_t num_handles;
// Indicates how the handles are encoded for this object.
HandleOwner handle_owner;
// Padding for 8-byte size alignment.
uint8_t reserved[3];
#endif
};
// Header for a serialized Transport object.
struct IPCZ_ALIGN(8) TransportHeader {
// Indicates what type of destination the other end of this serialized
// transport is connected to.
Transport::EndpointType destination_type;
// Indicates whether the remote process on the other end of this transport
// is the same process sending this object.
bool is_same_remote_process;
// See notes on equivalent fields defined on Transport. Note that serialized
// transports endpoints with `is_peer_trusted` set to true can only be
// accepted from transports which are themselves trusted.
bool is_peer_trusted;
bool is_trusted_by_peer;
};
#if BUILDFLAG(IS_WIN)
// Encodes a Windows HANDLE value for transmission within a serialized driver
// object payload. See documentation on HandleOwner above for general notes
// about how handles are communicated over IPC on Windows. Returns true on
// success, with the encoded handle value in `out_handle_data`. Returns false if
// handle duplication failed.
bool EncodeHandle(PlatformHandle& handle,
const base::Process& remote_process,
HandleOwner handle_owner,
HandleData& out_handle_data,
bool is_remote_process_untrusted) {
DCHECK(handle.is_valid());
if (handle_owner == HandleOwner::kSender) {
// Nothing to do when sending handles that belong to us. The recipient must
// be sufficiently privileged and equipped to duplicate such handles to
// itself.
out_handle_data = HandleToData(handle.ReleaseHandle());
return true;
}
// To encode a handle that already belongs to the recipient, we must first
// duplicate the handle to the recipient's process. Note that it is invalid to
// call EncodeHandle() to encode kRecipient handles without providing a valid
// handle to the remote process.
DCHECK_EQ(handle_owner, HandleOwner::kRecipient);
DCHECK(remote_process.IsValid());
#if BUILDFLAG(IS_WIN)
if (is_remote_process_untrusted) {
DcheckIfFileHandleIsUnsafe(handle.GetHandle().get());
}
#endif
HANDLE new_handle;
if (!::DuplicateHandle(::GetCurrentProcess(), handle.ReleaseHandle(),
remote_process.Handle(), &new_handle, 0, FALSE,
DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE)) {
return false;
}
out_handle_data = HandleToData(new_handle);
return true;
}
// Decodes a Windows HANDLE value from a transmission containing a serialized
// driver object. See documentation on HandleOwner above for general notes about
// how handles are communicated over IPC on Windows.
PlatformHandle DecodeHandle(HandleData data,
const base::Process& remote_process,
HandleOwner handle_owner,
Transport& from_transport) {
const HANDLE handle = DataToHandle(data);
if (handle_owner == HandleOwner::kRecipient) {
if (from_transport.destination_type() != Transport::kBroker &&
!from_transport.is_peer_trusted()) {
// Do not trust non-broker endpoints to send handles which already belong
// to us, unless the transport is explicitly marked as trustworthy (e.g.
// is connected to a known elevated process.)
return PlatformHandle();
}
return PlatformHandle(base::win::ScopedHandle(handle));
}
if (!remote_process.IsValid()) {
return PlatformHandle();
}
HANDLE local_dupe = INVALID_HANDLE_VALUE;
::DuplicateHandle(remote_process.Handle(), handle, ::GetCurrentProcess(),
&local_dupe, 0, FALSE,
DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE);
return PlatformHandle(base::win::ScopedHandle(local_dupe));
}
#endif // BUILDFLAG(IS_WIN)
scoped_refptr<base::SingleThreadTaskRunner>& GetIOTaskRunnerStorage() {
static base::NoDestructor<scoped_refptr<base::SingleThreadTaskRunner>> runner;
return *runner;
}
} // namespace
Transport::Transport(EndpointTypes endpoint_types,
Channel::Endpoint endpoint,
base::Process remote_process,
bool is_remote_process_untrusted)
: endpoint_types_(endpoint_types),
remote_process_(std::move(remote_process)),
#if BUILDFLAG(IS_WIN)
is_remote_process_untrusted_(is_remote_process_untrusted),
#endif
inactive_endpoint_(std::move(endpoint)) {
}
// static
scoped_refptr<Transport> Transport::Create(EndpointTypes endpoint_types,
Channel::Endpoint endpoint,
base::Process remote_process,
bool is_remote_process_untrusted) {
return base::MakeRefCounted<Transport>(endpoint_types, std::move(endpoint),
std::move(remote_process),
is_remote_process_untrusted);
}
// static
std::pair<scoped_refptr<Transport>, scoped_refptr<Transport>>
Transport::CreatePair(EndpointType first_type, EndpointType second_type) {
PlatformChannel channel;
auto one = Create({.source = first_type, .destination = second_type},
channel.TakeLocalEndpoint());
auto two = Create({.source = second_type, .destination = first_type},
channel.TakeRemoteEndpoint());
return {one, two};
}
Transport::~Transport() {
if (error_handler_) {
const MojoProcessErrorDetails details{
.struct_size = sizeof(details),
.error_message_length = 0,
.error_message = nullptr,
.flags = MOJO_PROCESS_ERROR_FLAG_DISCONNECTED,
};
error_handler_(error_handler_context_, &details);
}
}
// static
void Transport::SetIOTaskRunner(
scoped_refptr<base::SingleThreadTaskRunner> runner) {
GetIOTaskRunnerStorage() = std::move(runner);
}
// static
const scoped_refptr<base::SingleThreadTaskRunner>&
Transport::GetIOTaskRunner() {
return GetIOTaskRunnerStorage();
}
void Transport::ReportBadActivity(const std::string& error_message) {
if (!error_handler_) {
Invitation::InvokeDefaultProcessErrorHandler(error_message);
return;
}
const MojoProcessErrorDetails details{
.struct_size = sizeof(details),
.error_message_length =
base::checked_cast<uint32_t>(error_message.size()),
.error_message = error_message.c_str(),
.flags = MOJO_PROCESS_ERROR_FLAG_NONE,
};
error_handler_(error_handler_context_, &details);
}
bool Transport::Activate(IpczHandle transport,
IpczTransportActivityHandler activity_handler) {
scoped_refptr<Channel> channel;
std::vector<PendingTransmission> pending_transmissions;
{
base::AutoLock lock(lock_);
if (channel_ || !IsEndpointValid()) {
return false;
}
ipcz_transport_ = transport;
activity_handler_ = activity_handler;
self_reference_for_channel_ = base::WrapRefCounted(this);
channel_ = Channel::CreateForIpczDriver(this, std::move(inactive_endpoint_),
GetIOTaskRunner());
channel_->Start();
if (leak_channel_on_shutdown_) {
GetIOTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(
[](scoped_refptr<Channel> channel) { channel->LeakHandle(); },
channel_));
}
if (!pending_transmissions_.empty()) {
pending_transmissions_.swap(pending_transmissions);
channel = channel_;
}
}
for (auto& transmission : pending_transmissions) {
channel->Write(Channel::Message::CreateIpczMessage(
base::make_span(transmission.bytes), std::move(transmission.handles)));
}
return true;
}
bool Transport::Deactivate() {
scoped_refptr<Channel> channel;
{
base::AutoLock lock(lock_);
if (!channel_) {
return false;
}
channel = std::move(channel_);
}
// This will post a task to the Channel's IO thread to complete shutdown. Once
// the last Channel reference is dropped, it will invoke OnChannelDestroyed()
// on this Transport. The Transport is kept alive in the meantime by its own
// retained `self_reference_for_channel_`.
channel->ShutDown();
return true;
}
bool Transport::Transmit(base::span<const uint8_t> data,
base::span<const IpczDriverHandle> handles) {
#if BUILDFLAG(IS_WIN)
// All Windows handles must be inlined as message data as part of object
// serialization, so the driver should never attempt to transmit handles
// out-of-band there.
DCHECK(handles.empty());
#endif
std::vector<PlatformHandle> platform_handles;
platform_handles.reserve(handles.size());
for (IpczDriverHandle handle : handles) {
auto transmissible_handle =
TransmissiblePlatformHandle::TakeFromHandle(handle);
DCHECK(transmissible_handle);
platform_handles.push_back(transmissible_handle->TakeHandle());
}
scoped_refptr<Channel> channel;
{
base::AutoLock lock(lock_);
if (IsEndpointValid()) {
PendingTransmission transmission;
transmission.bytes = std::vector<uint8_t>(data.begin(), data.end());
transmission.handles = std::move(platform_handles);
pending_transmissions_.push_back(std::move(transmission));
return true;
}
if (!channel_) {
return false;
}
channel = channel_;
}
channel->Write(
Channel::Message::CreateIpczMessage(data, std::move(platform_handles)));
return true;
}
IpczResult Transport::SerializeObject(ObjectBase& object,
void* data,
size_t* num_bytes,
IpczDriverHandle* handles,
size_t* num_handles) {
size_t object_num_bytes;
size_t object_num_handles;
if (!object.GetSerializedDimensions(*this, object_num_bytes,
object_num_handles)) {
return IPCZ_RESULT_INVALID_ARGUMENT;
}
if (object_num_handles > 0 && !CanTransmitHandles()) {
// Let ipcz know that it must relay this object through a broker instead of
// transmitting it over this transport.
return IPCZ_RESULT_PERMISSION_DENIED;
}
#if BUILDFLAG(IS_WIN)
const size_t required_num_bytes = sizeof(ObjectHeader) + object_num_bytes +
sizeof(HandleData) * object_num_handles;
const size_t required_num_handles = 0;
#else
const size_t required_num_bytes = sizeof(ObjectHeader) + object_num_bytes;
const size_t required_num_handles = object_num_handles;
#endif
const size_t data_capacity = num_bytes ? *num_bytes : 0;
const size_t handle_capacity = num_handles ? *num_handles : 0;
if (num_bytes) {
*num_bytes = required_num_bytes;
}
if (num_handles) {
*num_handles = required_num_handles;
}
if (data_capacity < required_num_bytes ||
handle_capacity < required_num_handles) {
return IPCZ_RESULT_RESOURCE_EXHAUSTED;
}
auto& header = *static_cast<ObjectHeader*>(data);
header.size = sizeof(header);
header.type = object.type();
#if BUILDFLAG(IS_WIN)
header.num_handles = object_num_handles;
header.reserved[0] = 0;
header.reserved[1] = 0;
header.reserved[2] = 0;
const HandleOwner handle_owner =
remote_process_.IsValid() &&
(source_type() == kBroker || is_trusted_by_peer())
? HandleOwner::kRecipient
: HandleOwner::kSender;
header.handle_owner = handle_owner;
auto handle_data = base::make_span(reinterpret_cast<HandleData*>(&header + 1),
object_num_handles);
auto object_data =
base::make_span(reinterpret_cast<uint8_t*>(&header + 1) +
object_num_handles * sizeof(HandleData),
object_num_bytes);
#else
auto object_data = base::make_span(reinterpret_cast<uint8_t*>(&header + 1),
object_num_bytes);
#endif
// A small amount of stack storage is reserved to avoid heap allocation in the
// most common cases.
base::StackVector<PlatformHandle, 2> platform_handles;
platform_handles->resize(object_num_handles);
if (!object.Serialize(*this, object_data,
base::make_span(platform_handles.container()))) {
return IPCZ_RESULT_INVALID_ARGUMENT;
}
bool ok = true;
for (size_t i = 0; i < object_num_handles; ++i) {
#if BUILDFLAG(IS_WIN)
ok &= EncodeHandle(platform_handles[i], remote_process_, handle_owner,
handle_data[i], is_remote_process_untrusted_);
#else
handles[i] = TransmissiblePlatformHandle::ReleaseAsHandle(
base::MakeRefCounted<TransmissiblePlatformHandle>(
std::move(platform_handles[i])));
#endif
}
return ok ? IPCZ_RESULT_OK : IPCZ_RESULT_INVALID_ARGUMENT;
}
IpczResult Transport::DeserializeObject(
base::span<const uint8_t> bytes,
base::span<const IpczDriverHandle> handles,
scoped_refptr<ObjectBase>& object) {
if (bytes.size() < sizeof(ObjectHeader)) {
return IPCZ_RESULT_INVALID_ARGUMENT;
}
const auto& header = *reinterpret_cast<const ObjectHeader*>(bytes.data());
const uint32_t header_size = header.size;
if (header_size < sizeof(ObjectHeader) || header_size > bytes.size()) {
return IPCZ_RESULT_INVALID_ARGUMENT;
}
#if BUILDFLAG(IS_WIN)
DCHECK(handles.empty());
size_t num_handles = header.num_handles;
const HandleOwner handle_owner = header.handle_owner;
size_t available_bytes = bytes.size() - header_size;
const size_t max_handles = available_bytes / sizeof(HandleData);
if (num_handles > max_handles) {
return IPCZ_RESULT_INVALID_ARGUMENT;
}
const size_t handle_data_size = num_handles * sizeof(HandleData);
auto handle_data = base::make_span(
reinterpret_cast<const HandleData*>(bytes.data() + header_size),
num_handles);
auto object_data = bytes.subspan(header_size + handle_data_size);
#else
auto object_data = bytes.subspan(header_size);
size_t num_handles = handles.size();
#endif
// A small amount of stack storage is reserved to avoid heap allocation in the
// most common cases.
base::StackVector<PlatformHandle, 2> platform_handles;
platform_handles->resize(num_handles);
for (size_t i = 0; i < num_handles; ++i) {
#if BUILDFLAG(IS_WIN)
platform_handles[i] =
DecodeHandle(handle_data[i], remote_process_, handle_owner, *this);
#else
platform_handles[i] =
TransmissiblePlatformHandle::TakeFromHandle(handles[i])->TakeHandle();
#endif
if (!platform_handles[i].is_valid()) {
return IPCZ_RESULT_INVALID_ARGUMENT;
}
}
auto object_handles = base::make_span(platform_handles.container());
switch (header.type) {
case ObjectBase::kTransport:
object = Deserialize(*this, object_data, object_handles);
break;
case ObjectBase::kSharedBuffer:
object = SharedBuffer::Deserialize(object_data, object_handles);
break;
case ObjectBase::kTransmissiblePlatformHandle:
object =
TransmissiblePlatformHandle::Deserialize(object_data, object_handles);
break;
case ObjectBase::kWrappedPlatformHandle:
object = WrappedPlatformHandle::Deserialize(object_data, object_handles);
break;
case ObjectBase::kDataPipe:
object = DataPipe::Deserialize(object_data, object_handles);
break;
default:
return IPCZ_RESULT_UNIMPLEMENTED;
}
if (!object) {
return IPCZ_RESULT_INVALID_ARGUMENT;
}
return IPCZ_RESULT_OK;
}
void Transport::Close() {
Deactivate();
}
bool Transport::IsSerializable() const {
return true;
}
bool Transport::GetSerializedDimensions(Transport& transmitter,
size_t& num_bytes,
size_t& num_handles) {
num_bytes = sizeof(TransportHeader);
#if BUILDFLAG(IS_WIN)
num_handles = ShouldSerializeProcessHandle(transmitter) ? 2 : 1;
#else
num_handles = 1;
#endif
return true;
}
bool Transport::Serialize(Transport& transmitter,
base::span<uint8_t> data,
base::span<PlatformHandle> handles) {
DCHECK_EQ(sizeof(TransportHeader), data.size());
auto& header = *reinterpret_cast<TransportHeader*>(data.data());
header.destination_type = destination_type();
header.is_same_remote_process = remote_process_.is_current();
header.is_peer_trusted = is_peer_trusted();
header.is_trusted_by_peer = is_trusted_by_peer();
#if BUILDFLAG(IS_WIN)
if (ShouldSerializeProcessHandle(transmitter)) {
DCHECK_EQ(handles.size(), 2u);
DCHECK(remote_process_.IsValid());
DCHECK(!remote_process_.is_current());
handles[1] = PlatformHandle(
base::win::ScopedHandle(remote_process_.Duplicate().Release()));
} else {
DCHECK_EQ(handles.size(), 1u);
}
#else
DCHECK_EQ(handles.size(), 1u);
#endif
DCHECK(IsEndpointValid());
DCHECK(absl::holds_alternative<PlatformChannelEndpoint>(inactive_endpoint_));
handles[0] = absl::get<PlatformChannelEndpoint>(inactive_endpoint_)
.TakePlatformHandle();
return true;
}
// static
scoped_refptr<Transport> Transport::Deserialize(
Transport& from_transport,
base::span<const uint8_t> data,
base::span<PlatformHandle> handles) {
if (data.size() < sizeof(TransportHeader) || handles.size() < 1) {
return nullptr;
}
base::Process process;
const auto& header = *reinterpret_cast<const TransportHeader*>(data.data());
#if BUILDFLAG(IS_WIN)
if (handles.size() >= 2 && from_transport.remote_process().IsValid()) {
process = base::Process(handles[1].ReleaseHandle());
}
#endif
const bool is_source_trusted = from_transport.is_peer_trusted() ||
from_transport.destination_type() == kBroker;
const bool is_new_peer_trusted = header.is_peer_trusted;
if (is_new_peer_trusted && !is_source_trusted) {
// Untrusted transports cannot send us trusted transports.
return nullptr;
}
if (header.is_same_remote_process &&
from_transport.remote_process().IsValid()) {
process = from_transport.remote_process().Duplicate();
}
auto transport = Create({.source = from_transport.source_type(),
.destination = header.destination_type},
PlatformChannelEndpoint(std::move(handles[0])),
std::move(process));
transport->set_is_peer_trusted(is_new_peer_trusted);
transport->set_is_trusted_by_peer(header.is_trusted_by_peer);
return transport;
}
bool Transport::IsIpczTransport() const {
return true;
}
void Transport::OnChannelMessage(const void* payload,
size_t payload_size,
std::vector<PlatformHandle> handles) {
std::vector<IpczDriverHandle> driver_handles(handles.size());
for (size_t i = 0; i < handles.size(); ++i) {
driver_handles[i] = TransmissiblePlatformHandle::ReleaseAsHandle(
base::MakeRefCounted<TransmissiblePlatformHandle>(
std::move(handles[i])));
}
const IpczResult result = activity_handler_(
ipcz_transport_, static_cast<const uint8_t*>(payload), payload_size,
driver_handles.data(), driver_handles.size(), IPCZ_NO_FLAGS, nullptr);
if (result != IPCZ_RESULT_OK && result != IPCZ_RESULT_UNIMPLEMENTED) {
OnChannelError(Channel::Error::kReceivedMalformedData);
}
}
void Transport::OnChannelError(Channel::Error error) {
activity_handler_(ipcz_transport_, nullptr, 0, nullptr, 0,
IPCZ_TRANSPORT_ACTIVITY_ERROR, nullptr);
}
void Transport::OnChannelDestroyed() {
activity_handler_(ipcz_transport_, nullptr, 0, nullptr, 0,
IPCZ_TRANSPORT_ACTIVITY_DEACTIVATED, nullptr);
// Drop our self-reference now that the Channel is definitely done calling us.
// May delete `this` as the stack unwinds.
scoped_refptr<Transport> self;
base::AutoLock lock(lock_);
self = std::move(self_reference_for_channel_);
}
bool Transport::IsEndpointValid() const {
return absl::visit(base::Overloaded{
[](const PlatformChannelEndpoint& endpoint) {
return endpoint.is_valid();
},
[](const PlatformChannelServerEndpoint& endpoint) {
return endpoint.is_valid();
},
},
inactive_endpoint_);
}
bool Transport::CanTransmitHandles() const {
#if BUILDFLAG(IS_WIN)
// On Windows, we can transmit handles only if at least one endpoint is a
// broker, or if we have a handle to the remote process.
return destination_type() == kBroker || source_type() == kBroker ||
(remote_process_.IsValid() && is_trusted_by_peer());
#else
return true;
#endif
}
bool Transport::ShouldSerializeProcessHandle(Transport& transmitter) const {
#if BUILDFLAG(IS_WIN)
return remote_process_.IsValid() && !remote_process_.is_current() &&
(transmitter.destination_type() == kBroker ||
transmitter.is_peer_trusted());
#else
// We have no need for the process handle on other platforms.
return false;
#endif
}
Transport::PendingTransmission::PendingTransmission() = default;
Transport::PendingTransmission::PendingTransmission(PendingTransmission&&) =
default;
Transport::PendingTransmission& Transport::PendingTransmission::operator=(
PendingTransmission&&) = default;
Transport::PendingTransmission::~PendingTransmission() = default;
} // namespace mojo::core::ipcz_driver