blob: 4e4bcb017feb6b95ed4601e38cf2566d7155548b [file] [log] [blame]
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/351564777): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif
#include "mojo/core/channel.h"
#include <stddef.h>
#include <string.h>
#include <algorithm>
#include <cstdint>
#include <limits>
#include <utility>
#include "base/check_op.h"
#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/metrics/histogram_macros.h"
#include "base/numerics/safe_math.h"
#include "base/process/current_process.h"
#include "base/process/process_handle.h"
#include "base/rand_util.h"
#include "base/task/single_thread_task_runner.h"
#include "base/time/time.h"
#include "base/trace_event/typed_macros.h"
#include "build/build_config.h"
#include "mojo/core/configuration.h"
#include "mojo/core/embedder/features.h"
#include "mojo/core/ipcz_driver/envelope.h"
#if BUILDFLAG(MOJO_USE_APPLE_CHANNEL)
#include "base/apple/mach_logging.h"
#elif BUILDFLAG(IS_WIN)
#include "base/win/win_util.h"
#endif
namespace mojo::core {
namespace {
std::atomic_bool g_use_trivial_messages{false};
// TODO(crbug.com/40824727): Consider asking the memory allocator for a suitable
// size.
constexpr int kGrowthFactor = 2;
static_assert(
IsAlignedForChannelMessage(sizeof(Channel::Message::LegacyHeader)),
"Invalid LegacyHeader size.");
static_assert(IsAlignedForChannelMessage(sizeof(Channel::Message::Header)),
"Invalid Header size.");
static_assert(sizeof(Channel::Message::LegacyHeader) == 8,
"LegacyHeader must be 8 bytes on ChromeOS and Android");
static_assert(offsetof(Channel::Message::LegacyHeader, num_bytes) ==
offsetof(Channel::Message::Header, num_bytes),
"num_bytes should be at the same offset in both Header structs.");
static_assert(offsetof(Channel::Message::LegacyHeader, message_type) ==
offsetof(Channel::Message::Header, message_type),
"message_type should be at the same offset in both Header "
"structs.");
const size_t kReadBufferSize = 4096;
const size_t kMaxUnusedReadBufferCapacity = 4096;
#if BUILDFLAG(IS_FUCHSIA)
// Fuchsia: The zx_channel_write() API supports up to 64 handles.
const size_t kMaxAttachedHandles = 64;
#else
// Linux: The platform imposes a limit of 253 handles per sendmsg().
const size_t kMaxAttachedHandles = 253;
#endif // BUILDFLAG(IS_FUCHSIA)
static_assert(alignof(std::max_align_t) >= kChannelMessageAlignment, "");
Channel::AlignedBuffer MakeAlignedBuffer(size_t size) {
// Generic allocators (such as malloc) return a pointer that is suitably
// aligned for storing any type of object with a fundamental alignment
// requirement. Buffers have no additional alignment requirement beyond that.
auto buffer = Channel::AlignedBuffer::Uninit(size);
// Even though the allocator is configured in such a way that it crashes
// rather than return nullptr, ASAN and friends don't know about that. This
// CHECK() prevents Clusterfuzz from complaining. crbug.com/1180576.
CHECK(buffer.data());
return buffer;
}
// The type of message always used by a Channel which backs an ipcz transport.
// Most of the inherited Message interface is unused since it's only called by
// the original Mojo Core implementation.
struct IpczMessage : public Channel::Message {
IpczMessage(base::span<const uint8_t> data,
std::vector<PlatformHandle> handles) {
size_ = sizeof(IpczHeader) + data.size();
data_ = Channel::AlignedBuffer::Uninit(size_);
IpczHeader& header = *reinterpret_cast<IpczHeader*>(data_.data());
header.size = sizeof(IpczHeader);
DCHECK_LE(handles.size(), std::numeric_limits<uint16_t>::max());
DCHECK_LE(size_, std::numeric_limits<uint32_t>::max());
header.num_handles = static_cast<uint16_t>(handles.size());
header.num_bytes = static_cast<uint32_t>(size_);
header.v2.creation_timeticks_us =
(base::TimeTicks::Now() - base::TimeTicks()).InMicroseconds();
data_.subspan(sizeof(IpczHeader)).copy_prefix_from(base::as_chars(data));
handles_.reserve(handles.size());
for (PlatformHandle& handle : handles) {
handles_.emplace_back(std::move(handle));
}
}
~IpczMessage() override = default;
// Channel::Message:
void SetHandles(std::vector<PlatformHandle>) override { NOTREACHED(); }
void SetHandles(std::vector<PlatformHandleInTransit>) override {
NOTREACHED();
}
std::vector<PlatformHandleInTransit> TakeHandles() override {
return std::move(handles_);
}
base::span<const char> data_span() const override { return data_; }
base::span<char> mutable_data_span() override { NOTREACHED(); }
size_t capacity() const override { return size_; }
bool ExtendPayload(size_t) override { NOTREACHED(); }
private:
Channel::AlignedBuffer data_;
std::vector<PlatformHandleInTransit> handles_;
};
// A complex message can be large or contain file handles.
struct ComplexMessage : public Channel::Message {
ComplexMessage() = default;
ComplexMessage(size_t capacity,
size_t max_handles,
size_t payload_size,
MessageType message_type);
ComplexMessage(const ComplexMessage&) = delete;
ComplexMessage& operator=(const ComplexMessage&) = delete;
~ComplexMessage() override = default;
// Message impl:
void SetHandles(std::vector<PlatformHandle> new_handles) override;
void SetHandles(std::vector<PlatformHandleInTransit> new_handles) override;
std::vector<PlatformHandleInTransit> TakeHandles() override;
base::span<const char> data_span() const override { return data_; }
base::span<char> mutable_data_span() override { return data_.as_span(); }
size_t capacity() const override;
bool ExtendPayload(size_t new_payload_size) override;
private:
friend struct Channel::Message;
// The message data buffer.
Channel::AlignedBuffer data_;
// The capacity of the buffer at |data_|.
size_t capacity_ = 0;
// Maximum number of handles which may be attached to this message.
size_t max_handles_ = 0;
std::vector<PlatformHandleInTransit> handle_vector_;
#if BUILDFLAG(IS_WIN)
// On Windows, handles are serialised into the extra header section.
raw_ptr<HandleEntry, AllowPtrArithmetic> handles_ = nullptr;
#elif BUILDFLAG(MOJO_USE_APPLE_CHANNEL)
// On OSX, handles are serialised into the extra header section.
raw_ptr<MachPortsExtraHeader, AllowPtrArithmetic> mach_ports_header_ =
nullptr;
#endif
};
// A Message with fixed capacity for payload and no support for carrying
// handles. Allocated instead of IpczMessage for small messages to reduce the
// number of memory allocations.
struct TrivialMessage : public Channel::Message {
TrivialMessage(const TrivialMessage&) = delete;
TrivialMessage& operator=(const TrivialMessage&) = delete;
~TrivialMessage() override = default;
// TryConstruct should be used to build a TrivialMessage. Returns nullptr
// if |data| is too large to fit.
static Channel::MessagePtr TryConstruct(base::span<const uint8_t> data);
// Message impl:
base::span<const char> data_span() const override {
return base::as_chars(base::span(trivial_data_));
}
base::span<char> mutable_data_span() override {
return base::as_writable_chars(base::span(trivial_data_));
}
bool is_legacy_message() const override { return false; }
size_t capacity() const override;
// ExtendPayload is not used with ipcz. NOTREACHED allows not worrying about
// zero-fill for the hypothetical extended part.
bool ExtendPayload(size_t new_payload_size) override { NOTREACHED(); }
// Not supported by this class. Not used with ipcz. Implemented as NOTREACHED
// to match IpczMessage.
void SetHandles(std::vector<PlatformHandle> new_handles) override {
NOTREACHED();
}
void SetHandles(std::vector<PlatformHandleInTransit> new_handles) override {
NOTREACHED();
}
std::vector<PlatformHandleInTransit> TakeHandles() override;
// The choice of 248 as message size is to allow using the fullness of size
// class in PartitionAlloc (256) minus the space that can be reserved for
// MiraclePtr (8).
static constexpr size_t kIntendedMessageSize = 248;
private:
TrivialMessage() = default;
alignas(sizeof(void*)) uint8_t
trivial_data_[kIntendedMessageSize - sizeof(Channel::Message)];
static constexpr size_t kInternalCapacity = sizeof(trivial_data_);
};
static_assert(sizeof(TrivialMessage) == TrivialMessage::kIntendedMessageSize,
"The TrivialMessage is of wrong size");
bool ShouldRecordSubsampledHistograms() {
return base::ShouldRecordSubsampledMetric(0.001);
}
} // namespace
// static
Channel::MessagePtr Channel::Message::CreateMessage(size_t payload_size,
size_t max_handles) {
return CreateMessage(payload_size, payload_size, max_handles);
}
// static
Channel::MessagePtr Channel::Message::CreateMessage(size_t payload_size,
size_t max_handles,
MessageType message_type) {
return CreateMessage(payload_size, payload_size, max_handles, message_type);
}
// static
Channel::MessagePtr Channel::Message::CreateMessage(size_t capacity,
size_t payload_size,
size_t max_handles) {
#if defined(MOJO_CORE_LEGACY_PROTOCOL)
return CreateMessage(capacity, payload_size, max_handles,
Message::MessageType::NORMAL_LEGACY);
#else
return CreateMessage(capacity, payload_size, max_handles,
Message::MessageType::NORMAL);
#endif
}
// static
Channel::MessagePtr Channel::Message::CreateMessage(size_t capacity,
size_t payload_size,
size_t max_handles,
MessageType message_type) {
return base::WrapUnique<Channel::Message>(
new ComplexMessage(capacity, payload_size, max_handles, message_type));
}
// static
Channel::MessagePtr Channel::Message::CreateIpczMessage(
base::span<const uint8_t> data,
std::vector<PlatformHandle> handles) {
if (g_use_trivial_messages && handles.size() == 0) {
auto msg = TrivialMessage::TryConstruct(data);
if (msg) {
return msg;
}
}
return std::make_unique<IpczMessage>(data, std::move(handles));
}
// static
void Channel::set_use_trivial_messages(bool use_trivial_messages) {
g_use_trivial_messages = use_trivial_messages;
}
// static
Channel::MessagePtr Channel::Message::CreateRawForFuzzing(
base::span<const unsigned char> data) {
auto message = std::make_unique<ComplexMessage>();
message->size_ = data.size();
if (data.size()) {
message->data_ = Channel::AlignedBuffer::CopiedFrom(base::as_chars(data));
}
return base::WrapUnique<Channel::Message>(message.release());
}
// static
Channel::MessagePtr Channel::Message::Deserialize(
const void* data,
size_t data_num_bytes,
HandlePolicy handle_policy,
base::ProcessHandle from_process) {
if (data_num_bytes < sizeof(LegacyHeader))
return nullptr;
const LegacyHeader* legacy_header =
reinterpret_cast<const LegacyHeader*>(data);
if (legacy_header->num_bytes != data_num_bytes) {
DLOG(ERROR) << "Decoding invalid message: " << legacy_header->num_bytes
<< " != " << data_num_bytes;
return nullptr;
}
// If a message isn't explicitly identified as type NORMAL_LEGACY, it is
// expected to have a full-size header.
const Header* header = nullptr;
if (legacy_header->message_type != MessageType::NORMAL_LEGACY)
header = reinterpret_cast<const Header*>(data);
uint32_t extra_header_size = 0;
auto data_span = UNSAFE_TODO(
base::span<const char>(static_cast<const char*>(data), data_num_bytes));
base::span<const char> payload_span{};
if (!header) {
payload_span = data_span.subspan(sizeof(LegacyHeader),
data_num_bytes - sizeof(LegacyHeader));
} else {
if (header->num_bytes < header->num_header_bytes ||
header->num_header_bytes < sizeof(Header)) {
DLOG(ERROR) << "Decoding invalid message: " << header->num_bytes << " < "
<< header->num_header_bytes;
return nullptr;
}
extra_header_size = header->num_header_bytes - sizeof(Header);
payload_span = data_span.subspan(header->num_header_bytes,
data_num_bytes - header->num_header_bytes);
}
if (!IsAlignedForChannelMessage(extra_header_size)) {
// Well-formed messages always have any extra header bytes aligned to a
// |kChannelMessageAlignment| boundary.
DLOG(ERROR) << "Invalid extra header size";
return nullptr;
}
#if BUILDFLAG(IS_WIN)
uint32_t max_handles = extra_header_size / sizeof(HandleEntry);
#elif BUILDFLAG(IS_FUCHSIA)
uint32_t max_handles = extra_header_size / sizeof(HandleInfoEntry);
#elif BUILDFLAG(MOJO_USE_APPLE_CHANNEL)
if (extra_header_size > 0 &&
extra_header_size < sizeof(MachPortsExtraHeader)) {
DLOG(ERROR) << "Decoding invalid message: " << extra_header_size << " < "
<< sizeof(MachPortsExtraHeader);
return nullptr;
}
uint32_t max_handles =
extra_header_size == 0
? 0
: (extra_header_size - sizeof(MachPortsExtraHeader)) /
sizeof(MachPortsEntry);
#else
const uint32_t max_handles = 0;
// No extra header expected. Fail if this is detected.
if (extra_header_size > 0) {
DLOG(ERROR) << "Decoding invalid message: unexpected extra_header_size > 0";
return nullptr;
}
#endif // BUILDFLAG(IS_WIN)
const uint16_t num_handles =
header ? header->num_handles : legacy_header->num_handles;
if (num_handles > max_handles || max_handles > kMaxAttachedHandles) {
DLOG(ERROR) << "Decoding invalid message: " << num_handles << " > "
<< max_handles;
return nullptr;
}
if (num_handles > 0 && handle_policy == HandlePolicy::kRejectHandles) {
DLOG(ERROR) << "Rejecting message with unexpected handle attachments.";
return nullptr;
}
MessagePtr message = CreateMessage(payload_span.size(), max_handles,
legacy_header->message_type);
DCHECK_EQ(message->data_num_bytes(), data_num_bytes);
// Copy all payload bytes.
if (!payload_span.empty()) {
message->mutable_payload_span().copy_prefix_from(payload_span);
}
if (header) {
DCHECK_EQ(message->extra_header_size(), extra_header_size);
DCHECK_EQ(message->header()->num_header_bytes, header->num_header_bytes);
if (message->extra_header_size()) {
// Copy extra header bytes.
message->mutable_extra_header_span().copy_prefix_from(
data_span.subspan(sizeof(Header), message->extra_header_size()));
}
message->header()->num_handles = header->num_handles;
} else {
message->legacy_header()->num_handles = legacy_header->num_handles;
}
#if BUILDFLAG(IS_WIN)
std::vector<PlatformHandleInTransit> handles(num_handles);
for (size_t i = 0; i < num_handles; i++) {
HANDLE handle = base::win::Uint32ToHandle(
static_cast<ComplexMessage*>(message.get())->handles_[i].handle);
if (PlatformHandleInTransit::IsPseudoHandle(handle))
return nullptr;
if (from_process == base::kNullProcessHandle) {
handles[i] = PlatformHandleInTransit(
PlatformHandle(base::win::ScopedHandle(handle)));
} else {
handles[i] = PlatformHandleInTransit(
PlatformHandleInTransit::TakeIncomingRemoteHandle(handle,
from_process));
}
}
message->SetHandles(std::move(handles));
#endif
return message;
}
// static
void Channel::Message::ExtendPayload(MessagePtr& message,
size_t new_payload_size) {
if (message->ExtendPayload(new_payload_size)) {
return;
}
// ComplexMessage will never fail to extend the payload; therefore, if we do
// fail it's because the message is a TrivialMessage which has run out of
// space. In which case we will upgrade the message type to a ComplexMessage.
size_t capacity_without_header = message->capacity();
auto m = base::WrapUnique<Channel::Message>(
new ComplexMessage(new_payload_size, new_payload_size, 0,
message->legacy_header()->message_type));
m->mutable_payload_span().copy_prefix_from(
message->payload_span().first(capacity_without_header));
message.swap(m);
}
const void* Channel::Message::extra_header() const {
DCHECK(!is_legacy_message());
return data_span().subspan(sizeof(Header)).data();
}
void* Channel::Message::mutable_extra_header() {
return mutable_extra_header_span().data();
}
base::span<char> Channel::Message::mutable_extra_header_span() {
DCHECK(!is_legacy_message());
return mutable_data_span().subspan(sizeof(Header));
}
size_t Channel::Message::extra_header_size() const {
return header()->num_header_bytes - sizeof(Header);
}
void* Channel::Message::mutable_payload() {
return mutable_payload_span().data();
}
base::span<char> Channel::Message::mutable_payload_span() {
if (is_legacy_message()) {
return mutable_data_span().subspan(sizeof(LegacyHeader));
}
return mutable_data_span().subspan(header()->num_header_bytes);
}
const void* Channel::Message::payload() const {
return payload_span().data();
}
base::span<const char> Channel::Message::payload_span() const {
if (is_legacy_message()) {
return data_span().subspan(sizeof(LegacyHeader));
}
return data_span().subspan(header()->num_header_bytes);
}
size_t Channel::Message::payload_size() const {
if (is_legacy_message())
return legacy_header()->num_bytes - sizeof(LegacyHeader);
return size_ - header()->num_header_bytes;
}
size_t Channel::Message::num_handles() const {
return is_legacy_message() ? legacy_header()->num_handles
: header()->num_handles;
}
bool Channel::Message::has_handles() const {
return (is_legacy_message() ? legacy_header()->num_handles
: header()->num_handles) > 0;
}
bool Channel::Message::is_legacy_message() const {
return legacy_header()->message_type == MessageType::NORMAL_LEGACY;
}
Channel::Message::LegacyHeader* Channel::Message::legacy_header() {
return reinterpret_cast<LegacyHeader*>(mutable_data());
}
const Channel::Message::LegacyHeader* Channel::Message::legacy_header() const {
return reinterpret_cast<const LegacyHeader*>(data());
}
Channel::Message::Header* Channel::Message::header() {
DCHECK(!is_legacy_message());
return reinterpret_cast<Header*>(mutable_data());
}
const Channel::Message::Header* Channel::Message::header() const {
DCHECK(!is_legacy_message());
return reinterpret_cast<const Header*>(data());
}
ComplexMessage::ComplexMessage(size_t capacity,
size_t payload_size,
size_t max_handles,
MessageType message_type)
: max_handles_(max_handles) {
DCHECK_GE(capacity, payload_size);
DCHECK_LE(max_handles_, kMaxAttachedHandles);
const bool is_legacy_message = (message_type == MessageType::NORMAL_LEGACY);
size_t extra_header_size = 0;
#if BUILDFLAG(IS_WIN)
// On Windows we serialize HANDLEs into the extra header space.
extra_header_size = max_handles_ * sizeof(HandleEntry);
#elif BUILDFLAG(IS_FUCHSIA)
// On Fuchsia we serialize handle types into the extra header space.
extra_header_size = max_handles_ * sizeof(HandleInfoEntry);
#elif BUILDFLAG(MOJO_USE_APPLE_CHANNEL)
// On OSX, some of the platform handles may be mach ports, which are
// serialised into the message buffer. Since there could be a mix of fds and
// mach ports, we store the mach ports as an <index, port> pair (of uint32_t),
// so that the original ordering of handles can be re-created.
if (max_handles) {
extra_header_size =
sizeof(MachPortsExtraHeader) + (max_handles * sizeof(MachPortsEntry));
}
#endif
// Pad extra header data to be aliged to |kChannelMessageAlignment| bytes.
if (!IsAlignedForChannelMessage(extra_header_size)) {
extra_header_size += kChannelMessageAlignment -
(extra_header_size % kChannelMessageAlignment);
}
DCHECK(IsAlignedForChannelMessage(extra_header_size));
const size_t header_size =
is_legacy_message ? sizeof(LegacyHeader) : sizeof(Header);
DCHECK(extra_header_size == 0 || !is_legacy_message);
capacity_ = header_size + extra_header_size + capacity;
size_ = header_size + extra_header_size + payload_size;
data_ = MakeAlignedBuffer(capacity_);
// Only zero out the header and not the payload. Since the payload is going to
// be memcpy'd, zeroing the payload is unnecessary work and a significant
// performance issue when dealing with large messages. Any sanitizer errors
// complaining about an uninitialized read in the payload area should be
// treated as an error and fixed.
std::ranges::fill(mutable_data_span().first(header_size + extra_header_size),
0);
DCHECK(base::IsValueInRangeForNumericType<uint32_t>(size_));
legacy_header()->num_bytes = static_cast<uint32_t>(size_);
DCHECK(base::IsValueInRangeForNumericType<uint16_t>(header_size +
extra_header_size));
legacy_header()->message_type = message_type;
if (is_legacy_message) {
legacy_header()->num_handles = static_cast<uint16_t>(max_handles);
} else {
header()->num_header_bytes =
static_cast<uint16_t>(header_size + extra_header_size);
}
if (max_handles_ > 0) {
#if BUILDFLAG(IS_WIN)
handles_ = reinterpret_cast<HandleEntry*>(mutable_extra_header());
// Initialize all handles to invalid values.
for (size_t i = 0; i < max_handles_; ++i)
handles_[i].handle = base::win::HandleToUint32(INVALID_HANDLE_VALUE);
#elif BUILDFLAG(MOJO_USE_APPLE_CHANNEL)
mach_ports_header_ =
reinterpret_cast<MachPortsExtraHeader*>(mutable_extra_header());
mach_ports_header_->num_ports = 0;
// Initialize all handles to invalid values.
for (size_t i = 0; i < max_handles_; ++i) {
mach_ports_header_->entries[i] = {0};
}
#endif
}
}
size_t ComplexMessage::capacity() const {
if (is_legacy_message())
return capacity_ - sizeof(LegacyHeader);
return capacity_ - header()->num_header_bytes;
}
bool ComplexMessage::ExtendPayload(size_t new_payload_size) {
size_t capacity_without_header = capacity();
size_t header_size = capacity_ - capacity_without_header;
if (new_payload_size > capacity_without_header) {
size_t new_capacity =
std::max(static_cast<size_t>(capacity_without_header * kGrowthFactor),
new_payload_size) +
header_size;
Channel::AlignedBuffer new_data = MakeAlignedBuffer(new_capacity);
new_data.copy_prefix_from(data_);
data_ = std::move(new_data);
capacity_ = new_capacity;
if (max_handles_ > 0) {
// We also need to update the cached extra header addresses in case the
// payload buffer has been relocated.
#if BUILDFLAG(IS_WIN)
handles_ = reinterpret_cast<HandleEntry*>(mutable_extra_header());
#elif BUILDFLAG(MOJO_USE_APPLE_CHANNEL)
mach_ports_header_ =
reinterpret_cast<MachPortsExtraHeader*>(mutable_extra_header());
#endif
}
}
size_ = header_size + new_payload_size;
DCHECK(base::IsValueInRangeForNumericType<uint32_t>(size_));
legacy_header()->num_bytes = static_cast<uint32_t>(size_);
return true;
}
void ComplexMessage::SetHandles(std::vector<PlatformHandle> new_handles) {
std::vector<PlatformHandleInTransit> handles;
handles.reserve(new_handles.size());
for (auto& h : new_handles) {
handles.emplace_back(PlatformHandleInTransit(std::move(h)));
}
SetHandles(std::move(handles));
}
void ComplexMessage::SetHandles(
std::vector<PlatformHandleInTransit> new_handles) {
if (is_legacy_message()) {
// Old semantics for ChromeOS and Android
if (legacy_header()->num_handles == 0) {
CHECK(new_handles.empty());
return;
}
CHECK_EQ(new_handles.size(), legacy_header()->num_handles);
std::swap(handle_vector_, new_handles);
return;
}
if (max_handles_ == 0) {
CHECK(new_handles.empty());
return;
}
CHECK_LE(new_handles.size(), max_handles_);
header()->num_handles = static_cast<uint16_t>(new_handles.size());
std::swap(handle_vector_, new_handles);
#if BUILDFLAG(IS_WIN)
memset(handles_, 0, extra_header_size());
for (size_t i = 0; i < handle_vector_.size(); i++) {
HANDLE handle = handle_vector_[i].remote_handle();
if (handle == INVALID_HANDLE_VALUE)
handle = handle_vector_[i].handle().GetHandle().Get();
handles_[i].handle = base::win::HandleToUint32(handle);
}
#endif // BUILDFLAG(IS_WIN)
#if BUILDFLAG(MOJO_USE_APPLE_CHANNEL)
if (mach_ports_header_) {
for (size_t i = 0; i < max_handles_; ++i) {
mach_ports_header_->entries[i] = {0};
}
for (size_t i = 0; i < handle_vector_.size(); i++) {
mach_ports_header_->entries[i].type =
static_cast<uint8_t>(handle_vector_[i].handle().type());
}
mach_ports_header_->num_ports = handle_vector_.size();
}
#endif
}
std::vector<PlatformHandleInTransit> ComplexMessage::TakeHandles() {
return std::move(handle_vector_);
}
// static
Channel::MessagePtr TrivialMessage::TryConstruct(
base::span<const uint8_t> data) {
const size_t header_size = sizeof(IpczHeader);
size_t size = header_size + data.size();
DCHECK(base::IsValueInRangeForNumericType<uint32_t>(size));
if (size > kInternalCapacity) {
return nullptr;
}
auto message = base::WrapUnique(new TrivialMessage);
std::ranges::fill(message->mutable_data_span().first(sizeof(IpczHeader)), 0);
IpczHeader& header = *reinterpret_cast<IpczHeader*>(message->trivial_data_);
header.size = sizeof(IpczHeader);
header.num_handles = 0;
header.num_bytes = size;
header.v2.creation_timeticks_us =
(base::TimeTicks::Now() - base::TimeTicks()).InMicroseconds();
message->size_ = size;
base::span(message->trivial_data_)
.subspan(sizeof(IpczHeader))
.copy_prefix_from(data);
return message;
}
size_t TrivialMessage::capacity() const {
return kInternalCapacity - sizeof(IpczHeader);
}
std::vector<PlatformHandleInTransit> TrivialMessage::TakeHandles() {
return std::vector<PlatformHandleInTransit>();
}
// Helper class for managing a Channel's read buffer allocations. This maintains
// a single contiguous buffer with the layout:
//
// [discarded bytes][occupied bytes][unoccupied bytes]
//
// The Reserve() method ensures that a certain capacity of unoccupied bytes are
// available. It does not claim that capacity and only allocates new capacity
// when strictly necessary.
//
// Claim() marks unoccupied bytes as occupied.
//
// Discard() marks occupied bytes as discarded, signifying that their contents
// can be forgotten or overwritten.
//
// Realign() moves occupied bytes to the front of the buffer so that those
// occupied bytes are properly aligned.
//
// The most common Channel behavior in practice should result in very few
// allocations and copies, as memory is claimed and discarded shortly after
// being reserved, and future reservations will immediately reuse discarded
// memory.
class Channel::ReadBuffer {
public:
ReadBuffer() {
size_ = kReadBufferSize;
data_ = MakeAlignedBuffer(size_);
}
ReadBuffer(const ReadBuffer&) = delete;
ReadBuffer& operator=(const ReadBuffer&) = delete;
~ReadBuffer() { DCHECK(data_.data()); }
const char* occupied_bytes() const {
return data_.subspan(num_discarded_bytes_).data();
}
size_t num_occupied_bytes() const {
return num_occupied_bytes_ - num_discarded_bytes_;
}
// Ensures the ReadBuffer has enough contiguous space allocated to hold
// |num_bytes| more bytes; returns the address of the first available byte.
char* Reserve(size_t num_bytes) {
if (num_occupied_bytes_ + num_bytes > size_) {
size_ = std::max(static_cast<size_t>(size_ * kGrowthFactor),
num_occupied_bytes_ + num_bytes);
AlignedBuffer new_data = MakeAlignedBuffer(size_);
new_data.copy_prefix_from(data_.first(num_occupied_bytes_));
data_ = std::move(new_data);
}
return data_.subspan(num_occupied_bytes_).data();
}
// Marks the first |num_bytes| unoccupied bytes as occupied.
void Claim(size_t num_bytes) {
DCHECK_LE(num_occupied_bytes_ + num_bytes, size_);
num_occupied_bytes_ += num_bytes;
}
// Marks the first |num_bytes| occupied bytes as discarded. This may result in
// shrinkage of the internal buffer, and it is not safe to assume the result
// of a previous Reserve() call is still valid after this.
void Discard(size_t num_bytes) {
DCHECK_LE(num_discarded_bytes_ + num_bytes, num_occupied_bytes_);
num_discarded_bytes_ += num_bytes;
if (num_discarded_bytes_ == num_occupied_bytes_) {
// We can just reuse the buffer from the beginning in this common case.
num_discarded_bytes_ = 0;
num_occupied_bytes_ = 0;
}
if (num_discarded_bytes_ > kMaxUnusedReadBufferCapacity) {
// In the uncommon case that we have a lot of discarded data at the
// front of the buffer, simply move remaining data to a smaller buffer.
size_t num_preserved_bytes = num_occupied_bytes_ - num_discarded_bytes_;
size_ = std::max(num_preserved_bytes, kReadBufferSize);
AlignedBuffer new_data = MakeAlignedBuffer(size_);
new_data.copy_prefix_from(
data_.subspan(num_discarded_bytes_, num_preserved_bytes));
data_ = std::move(new_data);
num_discarded_bytes_ = 0;
num_occupied_bytes_ = num_preserved_bytes;
}
if (num_occupied_bytes_ == 0 && size_ > kMaxUnusedReadBufferCapacity) {
// Opportunistically shrink the read buffer back down to a small size if
// it's grown very large. We only do this if there are no remaining
// unconsumed bytes in the buffer to avoid copies in most the common
// cases.
size_ = kMaxUnusedReadBufferCapacity;
data_ = MakeAlignedBuffer(size_);
}
}
void Realign() {
size_t num_bytes = num_occupied_bytes();
auto new_data = MakeAlignedBuffer(data_.size());
new_data.copy_prefix_from(data_.subspan(num_discarded_bytes_, num_bytes));
data_ = std::move(new_data);
num_discarded_bytes_ = 0;
num_occupied_bytes_ = num_bytes;
}
private:
AlignedBuffer data_;
// The total size of the allocated buffer.
size_t size_ = 0;
// The number of discarded bytes at the beginning of the allocated buffer.
size_t num_discarded_bytes_ = 0;
// The total number of occupied bytes, including discarded bytes.
size_t num_occupied_bytes_ = 0;
};
bool Channel::Delegate::IsIpczTransport() const {
return false;
}
void Channel::Delegate::OnChannelDestroyed() {}
Channel::Channel(Delegate* delegate,
HandlePolicy handle_policy,
DispatchBufferPolicy buffer_policy)
: is_for_ipcz_(delegate && delegate->IsIpczTransport()),
delegate_(delegate),
handle_policy_(handle_policy),
read_buffer_(buffer_policy == DispatchBufferPolicy::kManaged
? new ReadBuffer
: nullptr) {}
Channel::~Channel() {
if (is_for_ipcz()) {
DCHECK(delegate_);
delegate_->OnChannelDestroyed();
}
}
// static
scoped_refptr<Channel> Channel::CreateForIpczDriver(
Delegate* delegate,
PlatformChannelEndpoint endpoint,
scoped_refptr<base::SingleThreadTaskRunner> io_task_runner) {
return Create(delegate, ConnectionParams{std::move(endpoint)},
HandlePolicy::kAcceptHandles, std::move(io_task_runner));
}
void Channel::ShutDown() {
ShutDownImpl();
if (!is_for_ipcz()) {
// When Channel is used for an ipcz transport, we leave `delegate_` intact
// so the Channel can notify once it's finally being destroyed.
delegate_ = nullptr;
}
}
char* Channel::GetReadBuffer(size_t* buffer_capacity) {
DCHECK(read_buffer_);
size_t required_capacity = *buffer_capacity;
if (!required_capacity)
required_capacity = kReadBufferSize;
*buffer_capacity = required_capacity;
return read_buffer_->Reserve(required_capacity);
}
bool Channel::OnReadComplete(size_t bytes_read, size_t* next_read_size_hint) {
DCHECK(read_buffer_);
*next_read_size_hint = kReadBufferSize;
read_buffer_->Claim(bytes_read);
const size_t header_size = is_for_ipcz_ ? sizeof(Message::IpczHeader)
: sizeof(Message::LegacyHeader);
while (read_buffer_->num_occupied_bytes() >= header_size) {
// Ensure the occupied data is properly aligned. If it isn't, a SIGBUS could
// happen on architectures that don't allow misaligned words access (i.e.
// anything other than x86). Only re-align when necessary to avoid copies.
if (!IsAlignedForChannelMessage(
reinterpret_cast<uintptr_t>(read_buffer_->occupied_bytes()))) {
read_buffer_->Realign();
}
DispatchResult result =
TryDispatchMessage(base::span(read_buffer_->occupied_bytes(),
read_buffer_->num_occupied_bytes()),
next_read_size_hint);
if (result == DispatchResult::kOK) {
if (ShouldRecordSubsampledHistograms()) {
RecordReceivedMessageProcessType();
}
read_buffer_->Discard(*next_read_size_hint);
*next_read_size_hint = 0;
} else if (result == DispatchResult::kNotEnoughData) {
return true;
} else if (result == DispatchResult::kMissingHandles) {
break;
} else if (result == DispatchResult::kError) {
return false;
}
}
return true;
}
Channel::DispatchResult Channel::TryDispatchMessage(
base::span<const char> buffer,
size_t* size_hint) {
return TryDispatchMessage(buffer, std::nullopt, nullptr, size_hint);
}
Channel::DispatchResult Channel::TryDispatchMessage(
base::span<const char> buffer,
std::optional<std::vector<PlatformHandle>> received_handles,
scoped_refptr<ipcz_driver::Envelope> envelope,
size_t* size_hint) {
TRACE_EVENT(TRACE_DISABLED_BY_DEFAULT("toplevel.ipc"),
"Mojo dispatch message");
if (is_for_ipcz_) {
// This has already been validated.
DCHECK_GE(buffer.size(), Message::kMinIpczHeaderSize);
const auto& header =
*reinterpret_cast<const Message::IpczHeader*>(buffer.data());
const size_t header_size = header.size;
const size_t num_bytes = header.num_bytes;
const size_t num_handles = header.num_handles;
if (header_size < Message::kMinIpczHeaderSize || num_bytes < header_size) {
return DispatchResult::kError;
}
if (buffer.size() < num_bytes) {
*size_hint = num_bytes - buffer.size();
return DispatchResult::kNotEnoughData;
}
std::vector<PlatformHandle> handles;
if (num_handles > 0) {
if (handle_policy_ == HandlePolicy::kRejectHandles) {
return DispatchResult::kError;
}
if (received_handles) {
handles = std::move(*received_handles);
} else if (!GetReadPlatformHandlesForIpcz(num_handles, handles)) {
return DispatchResult::kError;
}
if (handles.size() < num_handles) {
return DispatchResult::kMissingHandles;
}
}
if (ShouldRecordSubsampledHistograms() && Message::IsAtLeastV2(header)) {
base::TimeTicks creation_time =
base::TimeTicks() +
base::Microseconds(header.v2.creation_timeticks_us);
UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES(
"Mojo.Channel.WriteToReadLatencyUs",
base::TimeTicks::Now() - creation_time, base::Microseconds(1),
base::Seconds(1), 100);
}
auto data = buffer.first(num_bytes).subspan(header_size);
delegate_->OnChannelMessage(data.data(), data.size(), std::move(handles),
std::move(envelope));
*size_hint = num_bytes;
return DispatchResult::kOK;
}
// We have at least enough data available for a LegacyHeader.
const Message::LegacyHeader* legacy_header =
reinterpret_cast<const Message::LegacyHeader*>(buffer.data());
if (legacy_header->num_bytes < sizeof(Message::LegacyHeader)) {
LOG(ERROR) << "Invalid message size: " << legacy_header->num_bytes;
return DispatchResult::kError;
}
if (buffer.size() < legacy_header->num_bytes) {
// Not enough data available to read the full message. Hint to the
// implementation that it should try reading the full size of the message.
*size_hint = legacy_header->num_bytes - buffer.size();
return DispatchResult::kNotEnoughData;
}
const Message::Header* header = nullptr;
if (legacy_header->message_type != Message::MessageType::NORMAL_LEGACY) {
header = reinterpret_cast<const Message::Header*>(legacy_header);
}
size_t extra_header_size = 0;
const void* extra_header = nullptr;
size_t payload_size = 0;
void* payload = nullptr;
if (header) {
if (header->num_header_bytes < sizeof(Message::Header) ||
header->num_header_bytes > header->num_bytes) {
LOG(ERROR) << "Invalid message header size: " << header->num_header_bytes;
return DispatchResult::kError;
}
extra_header_size = header->num_header_bytes - sizeof(Message::Header);
extra_header = extra_header_size ? header + 1 : nullptr;
payload_size = header->num_bytes - header->num_header_bytes;
payload = payload_size
? reinterpret_cast<Message::Header*>(const_cast<char*>(
buffer.subspan(header->num_header_bytes).data()))
: nullptr;
} else {
payload_size = legacy_header->num_bytes - sizeof(Message::LegacyHeader);
payload = payload_size
? const_cast<Message::LegacyHeader*>(&legacy_header[1])
: nullptr;
}
const uint16_t num_handles =
header ? header->num_handles : legacy_header->num_handles;
std::vector<PlatformHandle> handles;
if (num_handles > 0) {
if (handle_policy_ == HandlePolicy::kRejectHandles) {
return DispatchResult::kError;
}
if (received_handles) {
handles = std::move(*received_handles);
} else if (!GetReadPlatformHandles(payload, payload_size, num_handles,
extra_header, extra_header_size,
&handles)) {
return DispatchResult::kError;
}
if (handles.size() < num_handles) {
// Not enough handles available for this message.
return DispatchResult::kMissingHandles;
}
}
// We've got a complete message! Dispatch it and try another.
if (legacy_header->message_type != Message::MessageType::NORMAL_LEGACY &&
legacy_header->message_type != Message::MessageType::NORMAL) {
if (!OnControlMessage(legacy_header->message_type, payload, payload_size,
std::move(handles))) {
return DispatchResult::kError;
}
} else if (delegate_) {
delegate_->OnChannelMessage(payload, payload_size, std::move(handles),
std::move(envelope));
}
*size_hint = legacy_header->num_bytes;
return DispatchResult::kOK;
}
void Channel::OnError(Error error) {
if (delegate_)
delegate_->OnChannelError(error);
}
bool Channel::OnControlMessage(Message::MessageType message_type,
const void* payload,
size_t payload_size,
std::vector<PlatformHandle> handles) {
return false;
}
// Currently only CrOs, Linux, and Android support upgrades.
#if !(BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_ANDROID))
// static
MOJO_SYSTEM_IMPL_EXPORT bool Channel::SupportsChannelUpgrade() {
return false;
}
MOJO_SYSTEM_IMPL_EXPORT void Channel::OfferChannelUpgrade() {
NOTREACHED();
}
#endif
void Channel::RecordSentMessageMetrics(size_t payload_size) {
if (ShouldRecordSubsampledHistograms()) {
UMA_HISTOGRAM_COUNTS_100000("Mojo.Channel.WriteMessageSize", payload_size);
RecordSentMessageProcessType();
}
}
// static
void Channel::RecordReceivedMessageProcessType() {
UMA_HISTOGRAM_ENUMERATION(
"Mojo.Channel.WriteReceiveMessageProcessType",
base::CurrentProcess::GetInstance().GetShortType({}));
}
// static
void Channel::RecordSentMessageProcessType() {
UMA_HISTOGRAM_ENUMERATION(
"Mojo.Channel.WriteSendMessageProcessType",
base::CurrentProcess::GetInstance().GetShortType({}));
}
} // namespace mojo::core