blob: 732e77924e65ed2020037e44cde835a239194d9f [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 <stdalign.h>
#include <stdint.h>
#include <stdlib.h>
#include <lib/fidl/internal.h>
#include <zircon/assert.h>
#include <zircon/compiler.h>
#ifdef __Fuchsia__
#include <zircon/syscalls.h>
#endif
#include "visitor.h"
#include "walker.h"
// TODO(kulakowski) Design zx_status_t error values.
namespace {
struct Position;
struct StartingPoint {
uint8_t* const addr;
Position ToPosition() const;
};
struct Position {
uint32_t offset;
Position operator+(uint32_t size) const {
return Position { offset + size };
}
Position& operator+=(uint32_t size) {
offset += size;
return *this;
}
template <typename T>
constexpr T* Get(StartingPoint start) const {
return reinterpret_cast<T*>(start.addr + offset);
}
};
Position StartingPoint::ToPosition() const {
return Position { 0 };
}
constexpr uintptr_t kAllocPresenceMarker = FIDL_ALLOC_PRESENT;
constexpr uintptr_t kAllocAbsenceMarker = FIDL_ALLOC_ABSENT;
class FidlDecoder final : public fidl::Visitor<
fidl::MutatingVisitorTrait, StartingPoint, Position> {
public:
FidlDecoder(void* bytes, uint32_t num_bytes, const zx_handle_t* handles, uint32_t num_handles,
uint32_t next_out_of_line, const char** out_error_msg)
: bytes_(static_cast<uint8_t*>(bytes)), num_bytes_(num_bytes),
handles_(handles), num_handles_(num_handles), next_out_of_line_(next_out_of_line),
out_error_msg_(out_error_msg) {}
using StartingPoint = StartingPoint;
using Position = Position;
static constexpr bool kContinueAfterConstraintViolation = true;
Status VisitPointer(Position ptr_position,
ObjectPointerPointer object_ptr_ptr,
uint32_t inline_size,
Position* out_position) {
if (reinterpret_cast<uintptr_t>(*object_ptr_ptr) != kAllocPresenceMarker) {
SetError("decoder encountered invalid pointer");
return Status::kConstraintViolationError;
}
// We have to manually maintain alignment here. For example, a pointer
// to a struct that is 4 bytes still needs to advance the next
// out-of-line offset by 8 to maintain the aligned-to-FIDL_ALIGNMENT
// property.
static constexpr uint32_t mask = FIDL_ALIGNMENT - 1;
uint32_t new_offset = next_out_of_line_;
if (add_overflow(new_offset, inline_size, &new_offset)
|| add_overflow(new_offset, mask, &new_offset)) {
SetError("overflow updating out-of-line offset");
return Status::kMemoryError;
}
new_offset &= ~mask;
if (new_offset > num_bytes_) {
SetError("message tried to decode more than provided number of bytes");
return Status::kMemoryError;
}
*out_position = Position { next_out_of_line_ };
*object_ptr_ptr = reinterpret_cast<void*>(&bytes_[next_out_of_line_]);
next_out_of_line_ = new_offset;
return Status::kSuccess;
}
Status VisitHandle(Position handle_position, HandlePointer handle) {
if (*handle != FIDL_HANDLE_PRESENT) {
SetError("message tried to decode a garbage handle");
return Status::kConstraintViolationError;
}
if (handle_idx_ == num_handles_) {
SetError("message decoded too many handles");
return Status::kConstraintViolationError;
}
if (handles_ == nullptr) {
SetError("decoder noticed a handle is present but the handle table is empty");
*handle = ZX_HANDLE_INVALID;
return Status::kConstraintViolationError;
}
if (handles_[handle_idx_] == ZX_HANDLE_INVALID) {
SetError("invalid handle detected in handle table");
return Status::kConstraintViolationError;
}
*handle = handles_[handle_idx_];
handle_idx_++;
return Status::kSuccess;
}
Status EnterEnvelope(Position envelope_position,
EnvelopePointer envelope,
const fidl_type_t* payload_type) {
if (envelope->presence == kAllocAbsenceMarker &&
(envelope->num_bytes != 0 || envelope->num_handles != 0)) {
SetError("Envelope has absent data pointer, yet has data and/or handles");
return Status::kConstraintViolationError;
}
if (envelope->presence != kAllocAbsenceMarker && envelope->num_bytes == 0) {
SetError("Envelope has present data pointer, but zero byte count");
return Status::kConstraintViolationError;
}
uint32_t expected_handle_count;
if (add_overflow(handle_idx_, envelope->num_handles, &expected_handle_count) ||
expected_handle_count > num_handles_) {
SetError("Envelope has more handles than expected");
return Status::kConstraintViolationError;
}
// Remember the current watermark of bytes and handles, so that after processing
// the envelope, we can validate that the claimed num_bytes/num_handles matches the reality.
if (!Push(next_out_of_line_, handle_idx_)) {
SetError("Overly deep nested envelopes");
return Status::kConstraintViolationError;
}
// If we do not have the coding table for this payload,
// treat it as unknown and close its contained handles
if (envelope->presence != kAllocAbsenceMarker && payload_type == nullptr &&
envelope->num_handles > 0) {
#ifdef __Fuchsia__
zx_handle_close_many(&handles_[handle_idx_], envelope->num_handles);
#endif
handle_idx_ += num_handles_;
}
return Status::kSuccess;
}
Status LeaveEnvelope(Position envelope_position, EnvelopePointer envelope) {
// Now that the envelope has been consumed, check the correctness of the envelope header.
auto& starting_state = Pop();
uint32_t num_bytes = next_out_of_line_ - starting_state.bytes_so_far;
uint32_t num_handles = handle_idx_ - starting_state.handles_so_far;
if (envelope->num_bytes != num_bytes) {
SetError("Envelope num_bytes was mis-sized");
return Status::kConstraintViolationError;
}
if (envelope->num_handles != num_handles) {
SetError("Envelope num_handles was mis-sized");
return Status::kConstraintViolationError;
}
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_; }
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;
}
struct EnvelopeState {
uint32_t bytes_so_far;
uint32_t handles_so_far;
};
const EnvelopeState& Pop() {
ZX_ASSERT(envelope_depth_ != 0);
envelope_depth_--;
return envelope_states_[envelope_depth_];
}
bool Push(uint32_t num_bytes, uint32_t num_handles) {
if (envelope_depth_ == FIDL_RECURSION_DEPTH) {
return false;
}
envelope_states_[envelope_depth_] = (EnvelopeState) {
.bytes_so_far = num_bytes,
.handles_so_far = num_handles,
};
envelope_depth_++;
return true;
}
// Message state passed in to the constructor.
uint8_t* const bytes_;
const uint32_t num_bytes_;
const zx_handle_t* const handles_;
const uint32_t num_handles_;
uint32_t next_out_of_line_;
const char** const out_error_msg_;
// Decoder state
zx_status_t status_ = ZX_OK;
uint32_t handle_idx_ = 0;
uint32_t envelope_depth_ = 0;
EnvelopeState envelope_states_[FIDL_RECURSION_DEPTH];
};
} // namespace
zx_status_t fidl_decode(const fidl_type_t* type, void* bytes, uint32_t num_bytes,
const zx_handle_t* handles, 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 (bytes == nullptr) {
set_error("Cannot decode null bytes");
return ZX_ERR_INVALID_ARGS;
}
if (handles == nullptr && num_handles != 0) {
set_error("Cannot provide non-zero handle count and null handle pointer");
return ZX_ERR_INVALID_ARGS;
}
if (type == nullptr) {
set_error("Cannot decode a null fidl type");
return ZX_ERR_INVALID_ARGS;
}
size_t primary_size;
zx_status_t status;
if ((status = fidl::GetPrimaryObjectSize(type, &primary_size, out_error_msg)) != ZX_OK) {
return status;
}
if (primary_size > num_bytes) {
set_error("Buffer is too small for first inline object");
return ZX_ERR_INVALID_ARGS;
}
uint64_t next_out_of_line = fidl::FidlAlign(static_cast<uint32_t>(primary_size));
if (next_out_of_line > std::numeric_limits<uint32_t>::max()) {
set_error("Out of line starting offset overflows");
return ZX_ERR_INVALID_ARGS;
}
FidlDecoder decoder(bytes, num_bytes, handles, num_handles,
static_cast<uint32_t>(next_out_of_line), out_error_msg);
fidl::Walk(decoder,
type,
StartingPoint { reinterpret_cast<uint8_t*>(bytes) });
if (decoder.status() == ZX_OK) {
if (!decoder.DidConsumeAllBytes()) {
set_error("message did not decode all provided bytes");
return ZX_ERR_INVALID_ARGS;
}
if (!decoder.DidConsumeAllHandles()) {
set_error("message did not decode all provided handles");
return ZX_ERR_INVALID_ARGS;
}
} else {
#ifdef __Fuchsia__
if (handles) {
// Return value intentionally ignored. This is best-effort cleanup.
(void)zx_handle_close_many(handles, num_handles);
}
#endif
}
return decoder.status();
}
zx_status_t fidl_decode_msg(const fidl_type_t* type, fidl_msg_t* msg,
const char** out_error_msg) {
return fidl_decode(type, msg->bytes, msg->num_bytes, msg->handles,
msg->num_handles, out_error_msg);
}