blob: 4f655f496cd2421ea47420bb6e0524c15d6d77e9 [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 "reference_drivers/multiprocess_reference_driver.h"
#include <cstring>
#include <limits>
#include <memory>
#include <tuple>
#include <utility>
#include "ipcz/ipcz.h"
#include "reference_drivers/file_descriptor.h"
#include "reference_drivers/memfd_memory.h"
#include "reference_drivers/object.h"
#include "reference_drivers/random.h"
#include "reference_drivers/socket_transport.h"
#include "reference_drivers/wrapped_file_descriptor.h"
#include "third_party/abseil-cpp/absl/synchronization/mutex.h"
#include "util/ref_counted.h"
#include "util/safe_math.h"
namespace ipcz::reference_drivers {
namespace {
// A transport implementation based on a SocketTransport.
class MultiprocessTransport
: public ObjectImpl<MultiprocessTransport, Object::kTransport> {
public:
explicit MultiprocessTransport(Ref<SocketTransport> transport)
: transport_(std::move(transport)) {}
MultiprocessTransport(const MultiprocessTransport&) = delete;
MultiprocessTransport& operator=(const MultiprocessTransport&) = delete;
void Activate(IpczHandle transport,
IpczTransportActivityHandler activity_handler) {
was_activated_ = true;
ipcz_transport_ = transport;
activity_handler_ = activity_handler;
absl::MutexLock lock(&transport_mutex_);
transport_->Activate(
[transport = WrapRefCounted(this)](SocketTransport::Message message) {
return transport->OnMessage(message);
},
[transport = WrapRefCounted(this)]() { transport->OnError(); });
}
void Deactivate() {
Ref<SocketTransport> transport;
{
absl::MutexLock lock(&transport_mutex_);
transport = std::move(transport_);
}
transport->Deactivate([ipcz_transport = ipcz_transport_,
activity_handler = activity_handler_] {
if (activity_handler) {
activity_handler(ipcz_transport, nullptr, 0, nullptr, 0,
IPCZ_TRANSPORT_ACTIVITY_DEACTIVATED, nullptr);
}
});
}
IpczResult Transmit(absl::Span<const uint8_t> data,
absl::Span<const IpczDriverHandle> handles) {
std::vector<FileDescriptor> descriptors(handles.size());
for (size_t i = 0; i < handles.size(); ++i) {
ABSL_ASSERT(Object::FromHandle(handles[i])->type() ==
Object::kFileDescriptor);
descriptors[i] =
WrappedFileDescriptor::TakeFromHandle(handles[i])->TakeDescriptor();
}
{
absl::MutexLock lock(&transport_mutex_);
if (transport_) {
transport_->Send({data, absl::MakeSpan(descriptors)});
return IPCZ_RESULT_OK;
}
}
return IPCZ_RESULT_OK;
}
FileDescriptor TakeDescriptor() {
ABSL_ASSERT(!was_activated_);
absl::MutexLock lock(&transport_mutex_);
return transport_->TakeDescriptor();
}
private:
~MultiprocessTransport() override = default;
bool OnMessage(const SocketTransport::Message& message) {
std::vector<IpczDriverHandle> handles(message.descriptors.size());
for (size_t i = 0; i < handles.size(); ++i) {
handles[i] =
Object::ReleaseAsHandle(MakeRefCounted<WrappedFileDescriptor>(
std::move(message.descriptors[i])));
}
ABSL_ASSERT(activity_handler_);
IpczResult result = activity_handler_(
ipcz_transport_, message.data.data(), message.data.size(),
handles.data(), handles.size(), IPCZ_NO_FLAGS, nullptr);
return result == IPCZ_RESULT_OK || result == IPCZ_RESULT_UNIMPLEMENTED;
}
void OnError() {
activity_handler_(ipcz_transport_, nullptr, 0, nullptr, 0,
IPCZ_TRANSPORT_ACTIVITY_ERROR, nullptr);
}
IpczHandle ipcz_transport_ = IPCZ_INVALID_HANDLE;
IpczTransportActivityHandler activity_handler_;
bool was_activated_ = false;
absl::Mutex transport_mutex_;
Ref<SocketTransport> transport_ ABSL_GUARDED_BY(transport_mutex_);
};
class MultiprocessMemoryMapping
: public ObjectImpl<MultiprocessMemoryMapping, Object::kMapping> {
public:
explicit MultiprocessMemoryMapping(MemfdMemory::Mapping mapping)
: mapping_(std::move(mapping)) {}
void* address() const { return mapping_.base(); }
private:
~MultiprocessMemoryMapping() override = default;
const MemfdMemory::Mapping mapping_;
};
class MultiprocessMemory
: public ObjectImpl<MultiprocessMemory, Object::kMemory> {
public:
explicit MultiprocessMemory(size_t num_bytes) : memory_(num_bytes) {}
MultiprocessMemory(FileDescriptor descriptor, size_t num_bytes)
: memory_(std::move(descriptor), num_bytes) {}
size_t size() const { return memory_.size(); }
Ref<MultiprocessMemory> Clone() {
return MakeRefCounted<MultiprocessMemory>(memory_.Clone().TakeDescriptor(),
memory_.size());
}
Ref<MultiprocessMemoryMapping> Map() {
return MakeRefCounted<MultiprocessMemoryMapping>(memory_.Map());
}
FileDescriptor TakeDescriptor() { return memory_.TakeDescriptor(); }
private:
~MultiprocessMemory() override = default;
MemfdMemory memory_;
};
// Header at the start of every driver object serialized by this driver.
struct IPCZ_ALIGN(8) SerializedObjectHeader {
// Enumeration indicating which type of driver object this is.
Object::Type type;
// For a memory object, the size of the underlying region. Ignored otherwise.
uint32_t memory_size;
};
IpczResult IPCZ_API Close(IpczDriverHandle handle,
uint32_t flags,
const void* options) {
Ref<Object> object = Object::TakeFromHandle(handle);
if (!object) {
return IPCZ_RESULT_INVALID_ARGUMENT;
}
object->Close();
return IPCZ_RESULT_OK;
}
IpczResult IPCZ_API Serialize(IpczDriverHandle handle,
IpczDriverHandle transport,
uint32_t flags,
const void* options,
volatile void* data,
size_t* num_bytes,
IpczDriverHandle* handles,
size_t* num_handles) {
Object* object = Object::FromHandle(handle);
if (!object) {
return IPCZ_RESULT_INVALID_ARGUMENT;
}
// First compute the serialized dimensions.
size_t required_num_bytes = sizeof(SerializedObjectHeader);
size_t required_num_handles;
switch (object->type()) {
case Object::kTransport:
case Object::kMemory:
required_num_handles = 1;
break;
default:
return IPCZ_RESULT_INVALID_ARGUMENT;
}
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;
}
const bool need_more_space = data_capacity < required_num_bytes ||
handle_capacity < required_num_handles;
if (need_more_space) {
return IPCZ_RESULT_RESOURCE_EXHAUSTED;
}
auto& header = *reinterpret_cast<volatile SerializedObjectHeader*>(data);
header.type = object->type();
header.memory_size = 0;
switch (object->type()) {
case Object::kTransport:
handles[0] = WrappedFileDescriptor::Create(
MultiprocessTransport::TakeFromObject(object)->TakeDescriptor());
break;
case Object::kMemory: {
auto memory = MultiprocessMemory::TakeFromObject(object);
header.memory_size = checked_cast<uint32_t>(memory->size());
handles[0] = WrappedFileDescriptor::Create(memory->TakeDescriptor());
break;
}
default:
return IPCZ_RESULT_INVALID_ARGUMENT;
}
return IPCZ_RESULT_OK;
}
IpczResult IPCZ_API Deserialize(const volatile void* data,
size_t num_bytes,
const IpczDriverHandle* handles,
size_t num_handles,
IpczDriverHandle transport,
uint32_t flags,
const void* options,
IpczDriverHandle* driver_handle) {
const auto& header =
*static_cast<const volatile SerializedObjectHeader*>(data);
if (num_bytes < sizeof(header)) {
return IPCZ_RESULT_INVALID_ARGUMENT;
}
Ref<Object> object;
switch (header.type) {
case Object::kTransport:
if (num_handles == 1) {
object = MakeRefCounted<MultiprocessTransport>(
MakeRefCounted<SocketTransport>(
WrappedFileDescriptor::UnwrapHandle(handles[0])));
}
break;
case Object::kMemory:
if (num_handles == 1) {
object = MakeRefCounted<MultiprocessMemory>(
WrappedFileDescriptor::UnwrapHandle(handles[0]),
header.memory_size);
}
break;
default:
break;
}
if (!object) {
return IPCZ_RESULT_INVALID_ARGUMENT;
}
*driver_handle = Object::ReleaseAsHandle(std::move(object));
return IPCZ_RESULT_OK;
}
IpczResult IPCZ_API CreateTransports(IpczDriverHandle transport0,
IpczDriverHandle transport1,
uint32_t flags,
const void* options,
IpczDriverHandle* new_transport0,
IpczDriverHandle* new_transport1) {
auto [first_socket, second_socket] = SocketTransport::CreatePair();
auto first = MakeRefCounted<MultiprocessTransport>(std::move(first_socket));
auto second = MakeRefCounted<MultiprocessTransport>(std::move(second_socket));
*new_transport0 = Object::ReleaseAsHandle(std::move(first));
*new_transport1 = Object::ReleaseAsHandle(std::move(second));
return IPCZ_RESULT_OK;
}
IpczResult IPCZ_API
ActivateTransport(IpczDriverHandle transport,
IpczHandle listener,
IpczTransportActivityHandler activity_handler,
uint32_t flags,
const void* options) {
MultiprocessTransport::FromHandle(transport)->Activate(listener,
activity_handler);
return IPCZ_RESULT_OK;
}
IpczResult IPCZ_API DeactivateTransport(IpczDriverHandle transport,
uint32_t flags,
const void* options) {
MultiprocessTransport::FromHandle(transport)->Deactivate();
return IPCZ_RESULT_OK;
}
IpczResult IPCZ_API Transmit(IpczDriverHandle transport,
const void* data,
size_t num_bytes,
const IpczDriverHandle* handles,
size_t num_handles,
uint32_t flags,
const void* options) {
return MultiprocessTransport::FromHandle(transport)->Transmit(
absl::MakeSpan(static_cast<const uint8_t*>(data), num_bytes),
absl::MakeSpan(handles, num_handles));
}
IpczResult IPCZ_API ReportBadTransportActivity(IpczDriverHandle transport,
uintptr_t context,
uint32_t flags,
const void* options) {
return IPCZ_RESULT_OK;
}
IpczResult IPCZ_API AllocateSharedMemory(size_t num_bytes,
uint32_t flags,
const void* options,
IpczDriverHandle* driver_memory) {
auto memory =
MakeRefCounted<MultiprocessMemory>(static_cast<size_t>(num_bytes));
*driver_memory = Object::ReleaseAsHandle(std::move(memory));
return IPCZ_RESULT_OK;
}
IpczResult IPCZ_API DuplicateSharedMemory(IpczDriverHandle driver_memory,
uint32_t flags,
const void* options,
IpczDriverHandle* new_driver_memory) {
auto memory = MultiprocessMemory::FromHandle(driver_memory)->Clone();
*new_driver_memory = Object::ReleaseAsHandle(std::move(memory));
return IPCZ_RESULT_OK;
}
IpczResult GetSharedMemoryInfo(IpczDriverHandle driver_memory,
uint32_t flags,
const void* options,
IpczSharedMemoryInfo* info) {
Object* object = Object::FromHandle(driver_memory);
if (!object || object->type() != Object::kMemory || !info ||
info->size < sizeof(IpczSharedMemoryInfo)) {
return IPCZ_RESULT_INVALID_ARGUMENT;
}
info->region_num_bytes = static_cast<MultiprocessMemory*>(object)->size();
return IPCZ_RESULT_OK;
}
IpczResult IPCZ_API MapSharedMemory(IpczDriverHandle driver_memory,
uint32_t flags,
const void* options,
volatile void** address,
IpczDriverHandle* driver_mapping) {
auto mapping = MultiprocessMemory::FromHandle(driver_memory)->Map();
*address = mapping->address();
*driver_mapping = Object::ReleaseAsHandle(std::move(mapping));
return IPCZ_RESULT_OK;
}
IpczResult IPCZ_API GenerateRandomBytes(size_t num_bytes,
uint32_t flags,
const void* options,
void* buffer) {
RandomBytes(absl::MakeSpan(static_cast<uint8_t*>(buffer), num_bytes));
return IPCZ_RESULT_OK;
}
} // namespace
const IpczDriver kMultiprocessReferenceDriver = {
sizeof(kMultiprocessReferenceDriver),
Close,
Serialize,
Deserialize,
CreateTransports,
ActivateTransport,
DeactivateTransport,
Transmit,
ReportBadTransportActivity,
AllocateSharedMemory,
GetSharedMemoryInfo,
DuplicateSharedMemory,
MapSharedMemory,
GenerateRandomBytes,
};
IpczDriverHandle CreateMultiprocessTransport(Ref<SocketTransport> transport) {
return Object::ReleaseAsHandle(
MakeRefCounted<MultiprocessTransport>(std::move(transport)));
}
FileDescriptor TakeMultiprocessTransportDescriptor(IpczDriverHandle transport) {
Ref<MultiprocessTransport> released_transport =
MultiprocessTransport::TakeFromHandle(transport);
return released_transport->TakeDescriptor();
}
} // namespace ipcz::reference_drivers