blob: b46bbb798de71d2cd366418971713d4f73605a2f [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <lib/fidl/coding.h>
#include <lib/fidl/internal.h>
#include <lib/fidl/visitor.h>
#include <lib/fidl/walker.h>
#include <lib/stdcompat/variant.h>
#include <stdalign.h>
#include <zircon/assert.h>
#include <zircon/compiler.h>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#ifdef __Fuchsia__
#include <zircon/syscalls.h>
#endif
// TODO(kulakowski) Design zx_status_t error values.
namespace {
template <typename Byte>
struct DecodingPosition {
Byte* addr;
DecodingPosition operator+(uint32_t size) const { return DecodingPosition{addr + size}; }
DecodingPosition& operator+=(uint32_t size) {
addr += size;
return *this;
}
template <typename T, typename U = std::conditional_t<std::is_const<Byte>::value, const T, T>>
constexpr U* Get() const {
return reinterpret_cast<U*>(addr);
}
};
struct EnvelopeCheckpoint {
uint32_t num_bytes;
uint32_t num_handles;
};
zx_status_t channel_decode_process_handle(fidl_handle_t* handle,
fidl::internal::HandleAttributes attr,
uint32_t metadata_index, const void* metadata_array,
const char** error) {
fidl_channel_handle_metadata_t v =
reinterpret_cast<const fidl_channel_handle_metadata_t*>(metadata_array)[metadata_index];
return FidlEnsureHandleRights(handle, v.obj_type, v.rights, attr.obj_type, attr.rights, error);
}
const fidl::internal::CodingConfig default_channel_encoding_configuration{
.decode_process_handle = channel_decode_process_handle,
};
constexpr zx_rights_t subtract_rights(zx_rights_t minuend, zx_rights_t subtrahend) {
return minuend & ~subtrahend;
}
static_assert(subtract_rights(0b011, 0b101) == 0b010, "ensure rights subtraction works correctly");
enum class Mode { Decode, Validate };
template <Mode mode, typename T, typename U>
void AssignInDecode(T* ptr, U value) {
static_assert(mode == Mode::Decode, "only assign if decode");
*ptr = value;
}
template <Mode mode, typename T, typename U>
void AssignInDecode(const T* ptr, U value) {
static_assert(mode == Mode::Validate, "don't assign if validate");
// nothing in validate mode
}
template <Mode mode>
zx_status_t DecodeProcessHandle(const fidl::internal::CodingConfig& encoding_configuration,
fidl_handle_t* handle, fidl::internal::HandleAttributes attr,
uint32_t metadata_index, const void* metadata_array,
const char** error) {
static_assert(mode == Mode::Decode, "process handles during decode");
if (!encoding_configuration.decode_process_handle) {
return ZX_OK;
}
return encoding_configuration.decode_process_handle(handle, attr, metadata_index, metadata_array,
error);
}
template <Mode mode>
zx_status_t DecodeProcessHandle(const fidl::internal::CodingConfig& encoding_configuration,
const fidl_handle_t* handle, fidl::internal::HandleAttributes attr,
uint32_t metadata_index, const void* metadata_array,
const char** error) {
static_assert(mode == Mode::Validate, "never used during validate");
__builtin_unreachable();
}
void ConvertEnvelopeToDecodedRepresentation(const void* bytes_base_ptr,
fidl_envelope_v2_t envelope_copy,
const fidl_envelope_v2_t* envelope_ptr) {
// No conversion is needed for v2 validate.
}
void ConvertEnvelopeToDecodedRepresentation(const void* bytes_base_ptr,
fidl_envelope_v2_t envelope_copy,
fidl_envelope_v2_t* envelope_ptr) {
if ((envelope_copy.flags & FIDL_ENVELOPE_FLAGS_INLINING_MASK) != 0) {
fidl_envelope_v2_unknown_data_t unknown_data_envelope = {
.num_handles = envelope_copy.num_handles,
.flags = envelope_copy.flags,
};
memcpy(unknown_data_envelope.inline_value, envelope_ptr->inline_value,
sizeof(envelope_ptr->inline_value));
memcpy(envelope_ptr, &unknown_data_envelope, sizeof(unknown_data_envelope));
return;
}
uintptr_t data_ptr = *reinterpret_cast<uintptr_t*>(envelope_ptr);
uintptr_t base_ptr = reinterpret_cast<uintptr_t>(bytes_base_ptr);
uintptr_t offset = data_ptr - base_ptr;
ZX_ASSERT(offset <= std::numeric_limits<uint16_t>::max());
ZX_ASSERT(envelope_copy.num_bytes <= std::numeric_limits<uint16_t>::max());
fidl_envelope_v2_unknown_data_t unknown_data_envelope = {
.out_of_line =
{
.num_bytes = static_cast<uint16_t>(envelope_copy.num_bytes),
.offset = static_cast<uint16_t>(offset),
},
.num_handles = envelope_copy.num_handles,
.flags = envelope_copy.flags,
};
memcpy(envelope_ptr, &unknown_data_envelope, sizeof(unknown_data_envelope));
}
template <FidlWireFormatVersion WireFormatVersion, typename Byte>
using BaseVisitor =
fidl::Visitor<WireFormatVersion,
std::conditional_t<std::is_const<Byte>::value, fidl::NonMutatingVisitorTrait,
fidl::MutatingVisitorTrait>,
DecodingPosition<Byte>, EnvelopeCheckpoint>;
template <Mode mode, FidlWireFormatVersion WireFormatVersion, typename Byte>
class FidlDecoder final : public BaseVisitor<WireFormatVersion, Byte> {
public:
FidlDecoder(const fidl::internal::CodingConfig& encoding_configuration, Byte* bytes,
uint32_t num_bytes, const fidl_handle_t* handles, const void* handle_metadata,
uint32_t num_handles, uint32_t next_out_of_line, const char** out_error_msg,
bool hlcpp_mode)
: encoding_configuration_(encoding_configuration),
bytes_(bytes),
num_bytes_(num_bytes),
handles_(handles),
handle_metadata_(handle_metadata),
num_handles_(num_handles),
next_out_of_line_(next_out_of_line),
out_error_msg_(out_error_msg),
hlcpp_mode_(hlcpp_mode) {}
using Position = typename BaseVisitor<WireFormatVersion, Byte>::Position;
using Status = typename BaseVisitor<WireFormatVersion, Byte>::Status;
using PointeeType = typename BaseVisitor<WireFormatVersion, Byte>::PointeeType;
using ObjectPointerPointer = typename BaseVisitor<WireFormatVersion, Byte>::ObjectPointerPointer;
using HandlePointer = typename BaseVisitor<WireFormatVersion, Byte>::HandlePointer;
using CountPointer = typename BaseVisitor<WireFormatVersion, Byte>::CountPointer;
using EnvelopeType = typename BaseVisitor<WireFormatVersion, Byte>::EnvelopeType;
using EnvelopePointer = typename BaseVisitor<WireFormatVersion, Byte>::EnvelopePointer;
static constexpr bool kOnlyWalkResources = false;
static constexpr bool kContinueAfterConstraintViolation = false;
static constexpr bool kValidateEnvelopeInlineBit = true;
Status VisitAbsentPointerInNonNullableCollection(ObjectPointerPointer object_ptr_ptr) {
SetError("absent pointer disallowed in non-nullable collection");
return Status::kConstraintViolationError;
}
Status VisitPointer(Position ptr_position, PointeeType pointee_type,
ObjectPointerPointer object_ptr_ptr, uint32_t inline_size,
FidlMemcpyCompatibility pointee_memcpy_compatibility,
Position* out_position) {
if (unlikely(pointee_type != PointeeType::kEnvelope &&
reinterpret_cast<uintptr_t>(*object_ptr_ptr) != FIDL_ALLOC_PRESENT)) {
SetError("invalid presence marker");
return Status::kMemoryError;
}
uint32_t new_offset;
if (unlikely(!FidlAddOutOfLine(next_out_of_line_, inline_size, &new_offset))) {
SetError("overflow updating out-of-line offset");
return Status::kMemoryError;
}
if (unlikely(new_offset > num_bytes_)) {
SetError("message tried to access more than provided number of bytes");
return Status::kMemoryError;
}
{
if (inline_size % FIDL_ALIGNMENT != 0) {
// Validate the last 8-byte block.
const uint64_t* block_end = reinterpret_cast<const uint64_t*>(&bytes_[new_offset]) - 1;
uint64_t padding_len = new_offset - next_out_of_line_ - inline_size;
uint64_t padding_mask = ~0ull << (64 - 8 * padding_len);
auto status = ValidatePadding(block_end, padding_mask);
if (status != Status::kSuccess) {
return status;
}
}
}
if (unlikely(pointee_type == PointeeType::kString)) {
bool valid = fidl_validate_string(reinterpret_cast<const char*>(&bytes_[next_out_of_line_]),
inline_size);
if (!valid) {
SetError("encountered invalid UTF8 string");
return Status::kConstraintViolationError;
}
}
*out_position = Position{bytes_ + next_out_of_line_};
AssignInDecode<mode>(
object_ptr_ptr,
reinterpret_cast<std::remove_pointer_t<ObjectPointerPointer>>(&bytes_[next_out_of_line_]));
next_out_of_line_ = new_offset;
return Status::kSuccess;
}
Status VisitHandle(Position handle_position, HandlePointer handle,
zx_rights_t required_handle_rights, zx_obj_type_t required_handle_subtype) {
if (unlikely(*handle != FIDL_HANDLE_PRESENT)) {
SetError("message tried to decode a garbage handle");
return Status::kConstraintViolationError;
}
if (unlikely(handle_idx_ == num_handles_)) {
SetError("message decoded too many handles");
return Status::kConstraintViolationError;
}
if (mode == Mode::Validate) {
handle_idx_++;
return Status::kSuccess;
}
if (unlikely(handles_[handle_idx_] == ZX_HANDLE_INVALID)) {
SetError("invalid handle detected in handle table");
return Status::kConstraintViolationError;
}
if (mode == Mode::Decode) {
AssignInDecode<mode>(handle, handles_[handle_idx_]);
fidl::internal::HandleAttributes attr = {
.obj_type = required_handle_subtype,
.rights = required_handle_rights,
};
const char* error;
zx_status_t status = DecodeProcessHandle<mode>(encoding_configuration_, handle, attr,
handle_idx_, handle_metadata_, &error);
if (status != ZX_OK) {
SetError(error);
return Status::kConstraintViolationError;
}
}
handle_idx_++;
return Status::kSuccess;
}
Status VisitVectorOrStringCount(CountPointer ptr) { return Status::kSuccess; }
template <typename MaskType>
Status VisitInternalPadding(Position padding_position, MaskType mask) {
return ValidatePadding(padding_position.template Get<const MaskType>(), mask);
}
EnvelopeCheckpoint EnterEnvelope() {
return {
.num_bytes = next_out_of_line_,
.num_handles = handle_idx_,
};
}
Status LeaveEnvelope(EnvelopeType in_envelope, EnvelopePointer out_envelope,
EnvelopeCheckpoint prev_checkpoint) {
// Now that the envelope has been consumed, check the correctness of the envelope header.
uint32_t num_bytes = next_out_of_line_ - prev_checkpoint.num_bytes;
uint32_t num_handles = handle_idx_ - prev_checkpoint.num_handles;
if (unlikely(in_envelope.num_bytes != num_bytes)) {
SetError("Envelope num_bytes was mis-sized");
return Status::kConstraintViolationError;
}
if (unlikely(in_envelope.num_handles != num_handles)) {
SetError("Envelope num_handles was mis-sized");
return Status::kConstraintViolationError;
}
return Status::kSuccess;
}
Status LeaveInlinedEnvelope(EnvelopeType in_envelope, EnvelopePointer out_envelope,
EnvelopeCheckpoint prev_checkpoint) {
// Now that the envelope has been consumed, check the correctness of the envelope header.
uint32_t num_handles = handle_idx_ - prev_checkpoint.num_handles;
if (unlikely(in_envelope.num_handles != num_handles)) {
SetError("Envelope num_handles was mis-sized");
return Status::kConstraintViolationError;
}
return Status::kSuccess;
}
Status VisitUnknownEnvelope(EnvelopeType envelope_copy, EnvelopePointer envelope_ptr,
FidlIsResource is_resource) {
if (mode == Mode::Validate) {
handle_idx_ += envelope_copy.num_handles;
return Status::kSuccess;
}
if (hlcpp_mode_) {
ConvertEnvelopeToDecodedRepresentation(bytes_, envelope_copy, envelope_ptr);
}
// If we do not have the coding table for this payload,
// treat it as unknown and close its contained handles
if (unlikely(envelope_copy.num_handles > 0)) {
uint32_t total_unknown_handles;
if (add_overflow(unknown_handle_idx_, envelope_copy.num_handles, &total_unknown_handles)) {
SetError("number of unknown handles overflows");
return Status::kConstraintViolationError;
}
if (total_unknown_handles > ZX_CHANNEL_MAX_MSG_HANDLES) {
SetError("number of unknown handles exceeds unknown handle array size");
return Status::kConstraintViolationError;
}
uint32_t end_incoming_handle;
if (add_overflow(handle_idx_, envelope_copy.num_handles, &end_incoming_handle)) {
SetError("number of incoming handles overflows");
return Status::kConstraintViolationError;
}
if (end_incoming_handle > num_handles_) {
SetError("number of incoming handles exceeds incoming handle array size");
return Status::kConstraintViolationError;
}
// If hlcpp_mode_ is true, leave the unknown handles intact
// for something else to process (e.g. HLCPP Decode)
if (hlcpp_mode_ && is_resource == kFidlIsResource_Resource) {
handle_idx_ += envelope_copy.num_handles;
return Status::kSuccess;
}
memcpy(&unknown_handles_[unknown_handle_idx_], &handles_[handle_idx_],
envelope_copy.num_handles * sizeof(fidl_handle_t));
handle_idx_ = end_incoming_handle;
unknown_handle_idx_ = total_unknown_handles;
}
return Status::kSuccess;
}
void OnError(const char* error) { SetError(error); }
zx_status_t status() const { return status_; }
bool DidConsumeAllBytes() const { return next_out_of_line_ == num_bytes_; }
bool DidConsumeAllHandles() const { return handle_idx_ == num_handles_; }
uint32_t unknown_handle_idx() const { return unknown_handle_idx_; }
const fidl_handle_t* unknown_handles() const { return unknown_handles_; }
private:
void SetError(const char* error) {
if (status_ != ZX_OK) {
return;
}
status_ = ZX_ERR_INVALID_ARGS;
if (!out_error_msg_) {
return;
}
*out_error_msg_ = error;
}
template <typename MaskType>
Status ValidatePadding(const MaskType* padding_ptr, MaskType mask) {
if ((*padding_ptr & mask) != 0) {
SetError("non-zero padding bytes detected");
return Status::kConstraintViolationError;
}
return Status::kSuccess;
}
// Message state passed in to the constructor.
const fidl::internal::CodingConfig& encoding_configuration_;
Byte* const bytes_;
const uint32_t num_bytes_;
const fidl_handle_t* handles_;
const void* handle_metadata_;
const uint32_t num_handles_;
uint32_t next_out_of_line_;
const char** const out_error_msg_;
// HLCPP first uses FidlDecoder to do an in-place decode, then extracts data
// out into domain objects. Since HLCPP stores unknown handles
// (and LLCPP does not), this field allows HLCPP to use the decoder while
// keeping unknown handles in flexible resource unions intact.
bool hlcpp_mode_;
// Decoder state
zx_status_t status_ = ZX_OK;
uint32_t handle_idx_ = 0;
uint32_t unknown_handle_idx_ = 0;
fidl_handle_t unknown_handles_[ZX_CHANNEL_MAX_MSG_HANDLES];
};
} // namespace
namespace {
template <FidlWireFormatVersion WireFormatVersion>
zx_status_t fidl_decode_impl(const fidl::internal::CodingConfig& encoding_configuration,
const fidl_type_t* type, void* bytes, uint32_t num_bytes,
const fidl_handle_t* handles, const void* handle_metadata,
uint32_t num_handles, const char** out_error_msg, bool hlcpp_mode) {
auto drop_all_handles = [&]() { FidlHandleCloseMany(handles, num_handles); };
auto set_error = [&out_error_msg](const char* msg) {
if (out_error_msg)
*out_error_msg = msg;
};
if (unlikely(type == nullptr)) {
set_error("fidl type cannot be null");
return ZX_ERR_INVALID_ARGS;
}
if (unlikely(handles == nullptr && num_handles != 0)) {
set_error("Cannot provide non-zero handle count and null handle pointer");
return ZX_ERR_INVALID_ARGS;
}
if (unlikely(bytes == nullptr)) {
set_error("Cannot decode null bytes");
drop_all_handles();
return ZX_ERR_INVALID_ARGS;
}
if (unlikely(!FidlIsAligned(reinterpret_cast<uint8_t*>(bytes)))) {
set_error("Bytes must be aligned to FIDL_ALIGNMENT");
drop_all_handles();
return ZX_ERR_INVALID_ARGS;
}
zx_status_t status;
uint32_t primary_size;
uint32_t next_out_of_line;
if (unlikely((status = fidl::PrimaryObjectSize<WireFormatVersion>(
type, num_bytes, &primary_size, &next_out_of_line, out_error_msg)) != ZX_OK)) {
drop_all_handles();
return status;
}
uint8_t* b = reinterpret_cast<uint8_t*>(bytes);
for (uint32_t i = primary_size; i < next_out_of_line; i++) {
if (b[i] != 0) {
set_error("non-zero padding bytes detected");
drop_all_handles();
return ZX_ERR_INVALID_ARGS;
}
}
FidlDecoder<Mode::Decode, WireFormatVersion, uint8_t> decoder(
encoding_configuration, b, num_bytes, handles, handle_metadata, num_handles, next_out_of_line,
out_error_msg, hlcpp_mode);
fidl::Walk<WireFormatVersion>(decoder, type, DecodingPosition<uint8_t>{b});
if (unlikely(decoder.status() != ZX_OK)) {
drop_all_handles();
return decoder.status();
}
if (unlikely(!decoder.DidConsumeAllBytes())) {
set_error("message did not decode all provided bytes");
drop_all_handles();
return ZX_ERR_INVALID_ARGS;
}
if (unlikely(!decoder.DidConsumeAllHandles())) {
set_error("message did not decode all provided handles");
drop_all_handles();
return ZX_ERR_INVALID_ARGS;
}
(void)FidlHandleCloseMany(decoder.unknown_handles(), decoder.unknown_handle_idx());
return ZX_OK;
}
template <FidlWireFormatVersion WireFormatVersion>
zx_status_t fidl_decode_impl_handle_info(const fidl::internal::CodingConfig& encoding_configuration,
const fidl_type_t* type, void* bytes, uint32_t num_bytes,
const zx_handle_info_t* handle_infos, uint32_t num_handles,
const char** out_error_msg, bool hlcpp_mode) {
if (!handle_infos) {
return fidl_decode_impl<WireFormatVersion>(encoding_configuration, type, bytes, num_bytes,
nullptr, nullptr, num_handles, out_error_msg,
hlcpp_mode);
}
fidl_handle_t handles[ZX_CHANNEL_MAX_MSG_HANDLES];
fidl_channel_handle_metadata_t handle_metadata[ZX_CHANNEL_MAX_MSG_HANDLES];
for (uint32_t i = 0; i < num_handles; i++) {
handles[i] = handle_infos[i].handle;
handle_metadata[i] = {
.obj_type = handle_infos[i].type,
.rights = handle_infos[i].rights,
};
}
return fidl_decode_impl<WireFormatVersion>(encoding_configuration, type, bytes, num_bytes,
handles, handle_metadata, num_handles, out_error_msg,
hlcpp_mode);
}
} // namespace
zx_status_t internal__fidl_decode_etc_hlcpp__v2__may_break(const fidl_type_t* type, void* bytes,
uint32_t num_bytes,
const zx_handle_info_t* handle_infos,
uint32_t num_handle_infos,
const char** error_msg_out) {
return fidl_decode_impl_handle_info<FIDL_WIRE_FORMAT_VERSION_V2>(
default_channel_encoding_configuration, type, bytes, num_bytes, handle_infos,
num_handle_infos, error_msg_out, true);
}
zx_status_t fidl_decode_etc(const fidl_type_t* type, void* bytes, uint32_t num_bytes,
const zx_handle_info_t* handle_infos, uint32_t num_handle_infos,
const char** error_msg_out) {
return fidl_decode_impl_handle_info<FIDL_WIRE_FORMAT_VERSION_V2>(
default_channel_encoding_configuration, type, bytes, num_bytes, handle_infos,
num_handle_infos, error_msg_out, false);
}
zx_status_t fidl_decode_msg(const fidl_type_t* type, fidl_incoming_msg_t* msg,
const char** out_error_msg) {
zx_handle_info_t handle_infos[ZX_CHANNEL_MAX_MSG_HANDLES];
fidl_channel_handle_metadata_t* metadata =
reinterpret_cast<fidl_channel_handle_metadata_t*>(msg->handle_metadata);
for (uint32_t i = 0; i < msg->num_handles; i++) {
handle_infos[i] = {
.handle = msg->handles[i],
.type = metadata[i].obj_type,
.rights = metadata[i].rights,
};
msg->handles[i] = ZX_HANDLE_INVALID;
}
uint8_t* trimmed_bytes;
uint32_t trimmed_num_bytes;
zx_status_t trim_status = ::fidl::internal::fidl_exclude_header_bytes(
msg->bytes, msg->num_bytes, &trimmed_bytes, &trimmed_num_bytes, out_error_msg);
if (unlikely(trim_status != ZX_OK)) {
return ZX_ERR_INVALID_ARGS;
}
if (trimmed_num_bytes == 0) {
return ZX_OK;
}
return fidl_decode_etc(type, trimmed_bytes, trimmed_num_bytes, handle_infos, msg->num_handles,
out_error_msg);
}
template <FidlWireFormatVersion WireFormatVersion>
zx_status_t fidl_validate_impl(const fidl_type_t* type, const void* bytes, uint32_t num_bytes,
uint32_t num_handles, const char** out_error_msg) {
auto set_error = [&out_error_msg](const char* msg) {
if (out_error_msg)
*out_error_msg = msg;
};
if (unlikely(type == nullptr)) {
set_error("fidl type cannot be null");
return ZX_ERR_INVALID_ARGS;
}
if (bytes == nullptr) {
set_error("Cannot validate null bytes");
return ZX_ERR_INVALID_ARGS;
}
if (unlikely(!FidlIsAligned(reinterpret_cast<const uint8_t*>(bytes)))) {
set_error("Bytes must be aligned to FIDL_ALIGNMENT");
return ZX_ERR_INVALID_ARGS;
}
zx_status_t status;
uint32_t primary_size;
uint32_t next_out_of_line;
if (unlikely((status = fidl::PrimaryObjectSize<WireFormatVersion>(
type, num_bytes, &primary_size, &next_out_of_line, out_error_msg)) != ZX_OK)) {
return status;
}
const uint8_t* b = reinterpret_cast<const uint8_t*>(bytes);
for (uint32_t i = primary_size; i < next_out_of_line; i++) {
if (b[i] != 0) {
set_error("non-zero padding bytes detected");
return ZX_ERR_INVALID_ARGS;
}
}
FidlDecoder<Mode::Validate, WireFormatVersion, const uint8_t> validator(
default_channel_encoding_configuration, b, num_bytes, nullptr, nullptr, num_handles,
next_out_of_line, out_error_msg, false);
fidl::Walk<WireFormatVersion>(validator, type, DecodingPosition<const uint8_t>{b});
if (validator.status() == ZX_OK) {
if (!validator.DidConsumeAllBytes()) {
set_error("message did not consume all provided bytes");
return ZX_ERR_INVALID_ARGS;
}
if (!validator.DidConsumeAllHandles()) {
set_error("message did not reference all provided handles");
return ZX_ERR_INVALID_ARGS;
}
}
return validator.status();
}
zx_status_t internal__fidl_validate__v2__may_break(const fidl_type_t* type, const void* bytes,
uint32_t num_bytes, uint32_t num_handles,
const char** out_error_msg) {
return fidl_validate_impl<FIDL_WIRE_FORMAT_VERSION_V2>(type, bytes, num_bytes, num_handles,
out_error_msg);
}