blob: ad4b15e5e5ad8c2316a6eedf61a1ed030ed5a5a4 [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 "ipcz/message.h"
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <utility>
#include "ipcz/driver_object.h"
#include "ipcz/driver_transport.h"
#include "ipcz/ipcz.h"
#include "third_party/abseil-cpp/absl/base/macros.h"
#include "third_party/abseil-cpp/absl/cleanup/cleanup.h"
#include "third_party/abseil-cpp/absl/container/inlined_vector.h"
#include "third_party/abseil-cpp/absl/types/span.h"
#include "util/safe_math.h"
namespace ipcz {
namespace {
// Helper to transform a driver object attached to `message` into its serialized
// form within the message by running it through the driver's serializer.
//
// Metadata is placed into a DriverObjectData structure at `data_offset` bytes
// from the begining of the message. Serialized data bytes are stored in an
// array appended to `message` and referenced by the DriverObjectData, and any
// transmissible handles emitted by the driver are appended to
// `transmissible_handles`, with relevant index and count also stashed in the
// DriverObjectData.
bool SerializeDriverObject(
DriverObject object,
const DriverTransport& transport,
Message& message,
internal::DriverObjectData& data,
absl::InlinedVector<IpczDriverHandle, 2>& transmissible_handles) {
if (!object.is_valid()) {
// This is not a valid driver handle and it cannot be serialized.
data.num_driver_handles = 0;
return false;
}
uint32_t driver_data_array = 0;
DriverObject::SerializedDimensions dimensions =
object.GetSerializedDimensions(transport);
if (dimensions.num_bytes > 0) {
driver_data_array = message.AllocateArray<uint8_t>(dimensions.num_bytes);
}
const uint32_t first_handle =
static_cast<uint32_t>(transmissible_handles.size());
absl::Span<uint8_t> driver_data =
message.GetArrayView<uint8_t>(driver_data_array);
data.driver_data_array = driver_data_array;
data.num_driver_handles = dimensions.num_driver_handles;
data.first_driver_handle = first_handle;
transmissible_handles.resize(transmissible_handles.size() +
dimensions.num_driver_handles);
auto handles_view = absl::MakeSpan(transmissible_handles);
if (!object.Serialize(
transport, driver_data,
handles_view.subspan(first_handle, dimensions.num_driver_handles))) {
return false;
}
return true;
}
// Returns `true` if and only if it will be safe to use GetArrayView() to access
// the contents of a serialized array beginning at `array_offset` bytes from
// the start of `message`, where each element is `element_size` bytes wide.
bool IsArrayValid(Message& message,
uint32_t array_offset,
size_t element_size) {
if (array_offset == 0) {
return true;
}
const absl::Span<uint8_t> data = message.data_view();
if (array_offset >= data.size()) {
return false;
}
size_t bytes_available = data.size() - array_offset;
if (bytes_available < sizeof(internal::ArrayHeader)) {
return false;
}
auto& header = *reinterpret_cast<internal::ArrayHeader*>(&data[array_offset]);
if (bytes_available < header.num_bytes ||
header.num_bytes < sizeof(internal::ArrayHeader)) {
return false;
}
size_t max_num_elements =
(header.num_bytes - sizeof(internal::ArrayHeader)) / element_size;
if (header.num_elements > max_num_elements) {
return false;
}
return true;
}
// Deserializes a driver object encoded within `message`, returning the object
// on success and marking its constituent handles as consumed. On failure, an
// invalid DriverObject is returned.
DriverObject DeserializeDriverObject(
Message& message,
const internal::DriverObjectData& object_data,
absl::Span<const IpczDriverHandle> handles,
absl::Span<bool> is_handle_consumed,
const DriverTransport& transport) {
if (!IsArrayValid(message, object_data.driver_data_array, sizeof(uint8_t))) {
return {};
}
auto driver_data =
message.GetArrayView<uint8_t>(object_data.driver_data_array);
if (object_data.num_driver_handles > handles.size()) {
return {};
}
if (handles.size() - object_data.num_driver_handles <
object_data.first_driver_handle) {
return {};
}
for (auto i = object_data.first_driver_handle;
i < object_data.first_driver_handle + object_data.num_driver_handles;
++i) {
is_handle_consumed[i] = true;
}
return DriverObject::Deserialize(
transport, driver_data,
handles.subspan(object_data.first_driver_handle,
object_data.num_driver_handles));
}
bool IsAligned(size_t n) {
return n % 8 == 0;
}
} // namespace
Message::ReceivedDataBuffer::ReceivedDataBuffer() = default;
// NOTE: This malloc'd buffer is intentionally NOT zero-initialized, because we
// will fully overwrite its contents.
Message::ReceivedDataBuffer::ReceivedDataBuffer(size_t size)
: data_(static_cast<uint8_t*>(malloc(size))), size_(size) {}
Message::ReceivedDataBuffer::ReceivedDataBuffer(ReceivedDataBuffer&& other)
: data_(std::move(other.data_)), size_(std::exchange(other.size_, 0)) {}
Message::ReceivedDataBuffer& Message::ReceivedDataBuffer::operator=(
ReceivedDataBuffer&& other) {
data_ = std::move(other.data_);
size_ = std::exchange(other.size_, 0);
return *this;
}
Message::ReceivedDataBuffer::~ReceivedDataBuffer() = default;
Message::Message() = default;
Message::Message(uint8_t message_id, size_t params_size)
: inlined_data_(sizeof(internal::MessageHeader) + params_size),
data_(absl::MakeSpan(*inlined_data_)) {
internal::MessageHeader& h = header();
h.size = sizeof(h);
h.version = 0;
h.message_id = message_id;
h.driver_object_data_array = 0;
ABSL_ASSERT(IsAligned(inlined_data_->size()));
}
Message::~Message() = default;
uint32_t Message::AllocateGenericArray(size_t element_size,
size_t num_elements) {
if (num_elements == 0) {
return 0;
}
size_t offset = Align(data_.size());
size_t num_bytes = Align(CheckAdd(sizeof(internal::ArrayHeader),
CheckMul(element_size, num_elements)));
ABSL_ASSERT(inlined_data_);
inlined_data_->resize(CheckAdd(offset, num_bytes));
data_ = absl::MakeSpan(*inlined_data_);
auto& header = *reinterpret_cast<internal::ArrayHeader*>(&data_[offset]);
header.num_bytes = checked_cast<uint32_t>(num_bytes);
header.num_elements = checked_cast<uint32_t>(num_elements);
return offset;
}
uint32_t Message::AppendDriverObject(DriverObject object) {
if (!object.is_valid()) {
return internal::kInvalidDriverObjectIndex;
}
const uint32_t index = checked_cast<uint32_t>(driver_objects_.size());
driver_objects_.push_back(std::move(object));
return index;
}
internal::DriverObjectArrayData Message::AppendDriverObjects(
absl::Span<DriverObject> objects) {
const internal::DriverObjectArrayData data = {
.first_object_index = checked_cast<uint32_t>(driver_objects_.size()),
.num_objects = checked_cast<uint32_t>(objects.size()),
};
driver_objects_.reserve(driver_objects_.size() + objects.size());
for (auto& object : objects) {
ABSL_ASSERT(object.is_valid());
driver_objects_.push_back(std::move(object));
}
return data;
}
DriverObject Message::TakeDriverObject(uint32_t index) {
if (index == internal::kInvalidDriverObjectIndex) {
return {};
}
// Note that `index` has already been validated by now.
ABSL_HARDENING_ASSERT(index < driver_objects_.size());
return std::move(driver_objects_[index]);
}
absl::Span<DriverObject> Message::GetDriverObjectArrayView(
const internal::DriverObjectArrayData& data) {
return absl::MakeSpan(driver_objects_)
.subspan(data.first_object_index, data.num_objects);
}
bool Message::CanTransmitOn(const DriverTransport& transport) {
for (DriverObject& object : driver_objects_) {
if (!object.CanTransmitOn(transport)) {
return false;
}
}
return true;
}
bool Message::Serialize(const DriverTransport& transport) {
ABSL_ASSERT(CanTransmitOn(transport));
if (driver_objects_.empty()) {
return true;
}
const uint32_t array_offset =
AllocateArray<internal::DriverObjectData>(driver_objects_.size());
header().driver_object_data_array = array_offset;
// NOTE: In Chromium, a vast majority of IPC messages have 0, 1, or 2 OS
// handles attached. Since these objects are small, we inline some storage on
// the stack to avoid some heap allocation in the most common cases.
absl::InlinedVector<IpczDriverHandle, 2> transmissible_handles;
bool ok = true;
for (size_t i = 0; i < driver_objects().size(); ++i) {
internal::DriverObjectData data = {};
ok &= SerializeDriverObject(std::move(driver_objects()[i]), transport,
*this, data, transmissible_handles);
GetArrayView<internal::DriverObjectData>(array_offset)[i] = data;
}
if (ok) {
transmissible_driver_handles_ = std::move(transmissible_handles);
return true;
}
return false;
}
bool Message::DeserializeUnknownType(const DriverTransport::RawMessage& message,
const DriverTransport& transport) {
// Ensure that upon return we explicitly close any handles that weren't
// consumed by some object deserialization below.
absl::InlinedVector<bool, 8> is_handle_consumed(message.handles.size());
const IpczDriver* driver = transport.driver_object().driver();
const absl::Cleanup close_unused_handles = [&message, &is_handle_consumed,
driver] {
for (size_t i = 0; i < message.handles.size(); ++i) {
if (!is_handle_consumed[i]) {
driver->Close(message.handles[i], IPCZ_NO_FLAGS, nullptr);
}
}
};
if (!CopyDataAndValidateHeader(message.data)) {
return false;
}
// Validate and deserialize the DriverObject array.
const uint32_t driver_object_array_offset = header().driver_object_data_array;
bool all_driver_objects_ok = true;
if (driver_object_array_offset > 0) {
if (!IsArrayValid(*this, driver_object_array_offset,
sizeof(internal::DriverObjectData))) {
// The header specified an invalid DriverObjectData array offset, or the
// array itself was invalid or out-of-bounds.
return false;
}
auto driver_object_data =
GetArrayView<internal::DriverObjectData>(driver_object_array_offset);
driver_objects_.reserve(driver_object_data.size());
for (const internal::DriverObjectData& object_data : driver_object_data) {
DriverObject object = DeserializeDriverObject(
*this, object_data, message.handles,
absl::MakeSpan(is_handle_consumed), transport);
if (object.is_valid()) {
driver_objects_.push_back(std::move(object));
} else {
// We don't fail immediately so we can try to deserialize the remaining
// objects anyway, since doing so may free additional resources.
all_driver_objects_ok = false;
}
}
}
return all_driver_objects_ok;
}
Message::ReceivedDataBuffer Message::TakeReceivedData() && {
ABSL_ASSERT(received_data_.has_value());
ReceivedDataBuffer buffer(std::move(*received_data_));
received_data_.reset();
data_ = {};
return buffer;
}
void Message::SetEnvelope(DriverObject envelope) {
envelope_ = std::move(envelope);
}
DriverObject Message::TakeEnvelope() {
return std::move(envelope_);
}
bool Message::CopyDataAndValidateHeader(absl::Span<const uint8_t> data) {
// Copy the data into a local message object to avoid any TOCTOU issues in
// case `data` is in unsafe shared memory.
received_data_.emplace(data.size());
memcpy(received_data_->data(), data.data(), data.size());
data_ = received_data_->bytes();
// The message must at least be large enough to encode a v0 MessageHeader.
if (data_.size() < sizeof(internal::MessageHeaderV0)) {
return false;
}
// Version 0 header must match MsesageHeaderV0 size exactly. Newer unknown
// versions must not be smaller than that.
const auto& header =
*reinterpret_cast<const internal::MessageHeaderV0*>(data_.data());
if (header.version == 0) {
if (header.size != sizeof(internal::MessageHeaderV0)) {
return false;
}
} else {
if (header.size < sizeof(internal::MessageHeaderV0)) {
return false;
}
}
// The header's stated size (and thus the start of the parameter payload)
// must not run over the edge of the message and must be 8-byte-aligned.
if (header.size > data_.size() || !IsAligned(header.size)) {
return false;
}
return true;
}
bool Message::ValidateParameters(
size_t params_size,
absl::Span<const internal::VersionMetadata> versions) {
// Validate parameter data. There must be at least enough bytes following the
// header to encode a StructHeader and to account for all parameter data for
// some known version of the message.
const size_t minimum_size =
static_cast<size_t>(header().size) + sizeof(internal::StructHeader);
if (data_.size() < minimum_size) {
return false;
}
absl::Span<uint8_t> params_data = params_data_view();
if (params_data.size() < sizeof(internal::StructHeader)) {
return false;
}
auto& params_header =
*reinterpret_cast<internal::StructHeader*>(params_data.data());
// The param struct's header claims to consist of more data than is present in
// the message. Not good.
if (params_data.size() < params_header.size) {
return false;
}
// Parameter struct sizes must be 8-byte-aligned.
if (!IsAligned(params_header.size)) {
return false;
}
// NOTE: In Chromium, a vast majority of IPC messages have 0, 1, or 2 OS
// handles attached. Since these objects are small, we inline some storage on
// the stack to avoid some heap allocation in the most common cases.
absl::InlinedVector<bool, 2> is_object_claimed(driver_objects_.size());
// Finally, validate each parameter and claim driver objects. We track the
// index of every object claimed by a parameter to ensure that no object is
// claimed more than once.
//
// It is not an error for some objects to go unclaimed, as they may have been
// provided for fields from a newer version of the message that isn't known to
// this receipient.
//
// NOTE: All VersionMetadata and ParamMetadata structures are preprocessor-
// generated constants used to reflect message layouts. They are not received
// over the wire and do not require validation themselves.
for (const internal::VersionMetadata& version : versions) {
if (version.offset >= params_header.size ||
version.offset + version.size > params_header.size) {
// It's not an error to fall short of any version above 0. Higher-
// versioned fields are inaccessible to message consumers in this case.
return &version != &versions[0];
}
for (const internal::ParamMetadata& param : version.params) {
const size_t offset = version.offset + param.offset;
if (param.array_element_size > 0) {
const uint32_t array_offset =
*reinterpret_cast<uint32_t*>(&params_data[offset]);
if (!IsArrayValid(*this, array_offset, param.array_element_size)) {
return false;
}
}
switch (param.type) {
case internal::ParamType::kDriverObject: {
const uint32_t index = GetParamValueAt<uint32_t>(offset);
if (index != internal::kInvalidDriverObjectIndex) {
if (index >= is_object_claimed.size() || is_object_claimed[index]) {
return false;
}
is_object_claimed[index] = true;
}
break;
}
case internal::ParamType::kDriverObjectArray: {
const internal::DriverObjectArrayData array_data =
GetParamValueAt<internal::DriverObjectArrayData>(offset);
const size_t begin = array_data.first_object_index;
if (begin > is_object_claimed.size()) {
return false;
}
const size_t max_num_objects = is_object_claimed.size() - begin;
if (array_data.num_objects > max_num_objects) {
return false;
}
for (size_t i = begin; i < begin + array_data.num_objects; ++i) {
if (is_object_claimed[i]) {
return false;
}
is_object_claimed[i] = true;
}
break;
}
default:
break;
}
}
}
return true;
}
bool Message::DeserializeFromTransport(
size_t params_size,
absl::Span<const internal::VersionMetadata> versions,
const DriverTransport::RawMessage& message,
const DriverTransport& transport) {
if (!DeserializeUnknownType(message, transport)) {
return false;
}
return ValidateParameters(params_size, versions);
}
bool Message::DeserializeFromRelay(
size_t params_size,
absl::Span<const internal::VersionMetadata> versions,
absl::Span<const uint8_t> data,
absl::Span<DriverObject> objects) {
if (!CopyDataAndValidateHeader(data)) {
return false;
}
driver_objects_.resize(objects.size());
std::move(objects.begin(), objects.end(), driver_objects_.begin());
return ValidateParameters(params_size, versions);
}
} // namespace ipcz