| // Copyright 2018 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/envelope_frames.h> |
| #include <lib/fidl/internal.h> |
| #include <lib/fidl/visitor.h> |
| #include <lib/fidl/walker.h> |
| #include <stdalign.h> |
| #include <string.h> |
| #include <zircon/assert.h> |
| #include <zircon/compiler.h> |
| |
| #include <cstdint> |
| #include <cstdlib> |
| #include <limits> |
| |
| namespace { |
| |
| struct Position; |
| |
| struct StartingPoint { |
| // The starting object of linearization |
| void* const source; |
| // The starting address of a contiguous destination buffer |
| uint8_t* const destination; |
| Position ToPosition() const; |
| }; |
| |
| struct Position { |
| // |object| points to one of the objects from the source pile |
| void* object; |
| // |offset| is an offset into the destination buffer |
| uint32_t offset; |
| Position operator+(uint32_t size) const { |
| return Position{.object = reinterpret_cast<void*>(reinterpret_cast<uint8_t*>(object) + size), |
| .offset = offset + size}; |
| } |
| Position& operator+=(uint32_t size) { |
| *this = *this + size; |
| return *this; |
| } |
| // By default, return the pointer in the destination buffer |
| template <typename T> |
| constexpr T* Get(StartingPoint start) const { |
| return reinterpret_cast<T*>(start.destination + offset); |
| } |
| // Additional method to get a pointer to one of the source objects |
| template <typename T> |
| constexpr T* GetFromSource() const { |
| return reinterpret_cast<T*>(object); |
| } |
| }; |
| |
| Position StartingPoint::ToPosition() const { return Position{.object = source, .offset = 0}; } |
| |
| using EnvelopeState = ::fidl::EnvelopeFrames::EnvelopeState; |
| |
| class FidlLinearizer final |
| : public fidl::Visitor<fidl::MutatingVisitorTrait, StartingPoint, Position> { |
| public: |
| FidlLinearizer(void* bytes, uint32_t num_bytes, uint32_t next_out_of_line, |
| const char** out_error_msg) |
| : bytes_(static_cast<uint8_t*>(bytes)), |
| num_bytes_(num_bytes), |
| next_out_of_line_(next_out_of_line), |
| out_error_msg_(out_error_msg) {} |
| |
| using StartingPoint = StartingPoint; |
| |
| using Position = Position; |
| |
| // Does not make sense to keep going after any error, since the resulting buffer |
| // would not be usable anyways. |
| static constexpr bool kContinueAfterConstraintViolation = false; |
| |
| // When we encounter a non-nullable vector/string with zero count, do not check the |
| // data pointer. It is cumbersome for the caller to provide a meaningful value other than NULL |
| // in the case of an empty vector/string. |
| static constexpr bool kAllowNonNullableCollectionsToBeAbsent = true; |
| |
| Status VisitPointer(Position ptr_position, PointeeType pointee_type, |
| ObjectPointerPointer object_ptr_ptr, uint32_t inline_size, |
| Position* out_position) { |
| // For pointers in types other than vectors and strings, the LSB is reserved to mark ownership |
| // and may be set to 1 if the object is heap allocated. However, the original pointer has this |
| // bit cleared. For vectors and strings, any value is accepted. |
| auto object_ptr = |
| pointee_type == PointeeType::kVectorOrString |
| ? *object_ptr_ptr |
| : reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(*object_ptr_ptr) & |
| ~fidl::internal::kNonArrayTrackingPtrOwnershipMask); |
| |
| uint32_t new_offset; |
| if (!FidlAddOutOfLine(next_out_of_line_, inline_size, &new_offset)) { |
| SetError("out-of-line offset overflow trying to linearize"); |
| return Status::kMemoryError; |
| } |
| |
| if (new_offset > num_bytes_) { |
| SetError("object is too big to linearize into provided buffer", ZX_ERR_BUFFER_TOO_SMALL); |
| return Status::kConstraintViolationError; |
| } |
| |
| // Copy the pointee to the desired location in secondary storage |
| memcpy(&bytes_[next_out_of_line_], object_ptr, inline_size); |
| |
| // Instruct the walker to traverse the pointee afterwards. |
| *out_position = Position{.object = object_ptr, .offset = next_out_of_line_}; |
| |
| // Update the pointer within message buffer to point to the copy |
| *object_ptr_ptr = &bytes_[next_out_of_line_]; |
| next_out_of_line_ = new_offset; |
| return Status::kSuccess; |
| } |
| |
| Status VisitHandle(Position handle_position, HandlePointer handle_ptr, zx_rights_t handle_rights, |
| zx_obj_type_t handle_subtype) { |
| // Remember the address of the handle in the original objects, |
| // such that after the entire tree is cloned into the contiguous buffer, |
| // we can clear out the handles in the original tree in one fell swoop. |
| if (handle_idx_ == ZX_CHANNEL_MAX_MSG_HANDLES) { |
| SetError("too many handles when linearizing"); |
| return Status::kConstraintViolationError; |
| } |
| original_handles_[handle_idx_] = handle_position.GetFromSource<zx_handle_t>(); |
| handle_idx_ += 1; |
| return Status::kSuccess; |
| } |
| |
| Status VisitVectorOrStringCount(CountPointer ptr) { |
| // Clear the MSB that is used for storing ownership information for vectors and strings. |
| // While this operationg could be considered part of encoding, it is LLCPP specific so it |
| // is done during linearization. |
| *ptr &= ~fidl::internal::kVectorOwnershipMask; |
| return Status::kSuccess; |
| } |
| |
| Status VisitInternalPadding(Position padding_position, uint32_t padding_length) { |
| return Status::kSuccess; |
| } |
| |
| Status EnterEnvelope(Position envelope_position, EnvelopePointer envelope, |
| const fidl_type_t* payload_type) { |
| if (envelope->data != nullptr && payload_type == nullptr) { |
| SetError("Cannot linearize envelope without a coding table"); |
| 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 (!envelope_frames_.Push(EnvelopeState(next_out_of_line_, handle_idx_))) { |
| SetError("Overly deep nested envelopes"); |
| return Status::kConstraintViolationError; |
| } |
| return Status::kSuccess; |
| } |
| |
| Status LeaveEnvelope(Position envelope_position, EnvelopePointer envelope) { |
| // Now that the envelope has been consumed, go back and update the envelope header with |
| // the correct num_bytes and num_handles values |
| auto& starting_state = envelope_frames_.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; |
| envelope->num_bytes = num_bytes; |
| envelope->num_handles = num_handles; |
| return Status::kSuccess; |
| } |
| |
| void OnError(const char* error) { SetError(error); } |
| |
| template <typename Callback> |
| void ForEachHandle(Callback cb) { |
| for (uint32_t i = 0; i < handle_idx_; i++) { |
| cb(original_handles_[i]); |
| } |
| } |
| |
| zx_status_t status() const { return status_; } |
| |
| uint32_t next_out_of_line() const { return next_out_of_line_; } |
| |
| private: |
| void SetError(const char* error, zx_status_t code = ZX_ERR_INVALID_ARGS) { |
| if (status_ == ZX_OK) { |
| status_ = code; |
| if (out_error_msg_ != nullptr) { |
| *out_error_msg_ = error; |
| } |
| } |
| } |
| |
| // Message state passed into the constructor. |
| uint8_t* const bytes_; |
| const uint32_t num_bytes_; |
| uint32_t next_out_of_line_; |
| const char** const out_error_msg_; |
| |
| // Linearizer state |
| zx_status_t status_ = ZX_OK; |
| uint32_t handle_idx_ = 0; |
| zx_handle_t* original_handles_[ZX_CHANNEL_MAX_MSG_HANDLES]; |
| fidl::EnvelopeFrames envelope_frames_; |
| }; |
| |
| } // namespace |
| |
| zx_status_t fidl_linearize(const fidl_type_t* type, void* value, uint8_t* buffer, |
| uint32_t num_bytes, uint32_t* out_num_bytes, |
| const char** out_error_msg) { |
| auto set_error = [&out_error_msg](const char* msg) { |
| if (out_error_msg) |
| *out_error_msg = msg; |
| }; |
| if (value == nullptr) { |
| set_error("Cannot linearize with null starting object"); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| if (buffer == nullptr) { |
| set_error("Cannot linearize with null destination buffer"); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| if (!FidlIsAligned(buffer)) { |
| set_error("Destination buffer must be aligned to FIDL_ALIGNMENT"); |
| return ZX_ERR_INVALID_ARGS; |
| } |
| |
| size_t primary_size; |
| zx_status_t status; |
| if ((status = fidl::PrimaryObjectSize(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_BUFFER_TOO_SMALL; |
| } |
| uint64_t next_out_of_line = 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; |
| } |
| |
| // Copy the primary object |
| memcpy(buffer, value, primary_size); |
| |
| // Zero the padding gaps |
| memset(buffer + primary_size, 0, next_out_of_line - primary_size); |
| |
| FidlLinearizer linearizer(buffer, num_bytes, static_cast<uint32_t>(next_out_of_line), |
| out_error_msg); |
| fidl::Walk(linearizer, type, |
| StartingPoint{ |
| .source = value, |
| .destination = buffer, |
| }); |
| |
| if (linearizer.status() != ZX_OK) { |
| return linearizer.status(); |
| } |
| |
| // Clear out handles in the original objects |
| linearizer.ForEachHandle([](zx_handle_t* handle_ptr) { *handle_ptr = ZX_HANDLE_INVALID; }); |
| |
| // Return the message size, which is the starting offset of the next out-of-line object |
| if (out_num_bytes) { |
| *out_num_bytes = linearizer.next_out_of_line(); |
| } |
| |
| return ZX_OK; |
| } |