blob: 16481d945e56e3124e2569700807290e35515a52 [file] [log] [blame] [edit]
// Copyright 2019 The Fuchsia Authors
//
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file or at
// https://opensource.org/licenses/MIT
#include "object/user_handles.h"
#include <ktl/algorithm.h>
#include <ktl/enforce.h>
namespace {
// Basic checks for a |handle| to be able to be sent via |channel|.
static zx_status_t common_handle_checks_locked(const Handle& handle, const Dispatcher* channel,
zx_rights_t desired_rights, zx_obj_type_t type) {
if (!handle.HasRights(ZX_RIGHT_TRANSFER))
return ZX_ERR_ACCESS_DENIED;
if (handle.dispatcher().get() == channel)
return ZX_ERR_NOT_SUPPORTED;
if (type != ZX_OBJ_TYPE_NONE && handle.dispatcher()->get_type() != type)
return ZX_ERR_WRONG_TYPE;
if (desired_rights != ZX_RIGHT_SAME_RIGHTS) {
if ((handle.rights() & desired_rights) != desired_rights) {
return ZX_ERR_INVALID_ARGS;
}
}
return ZX_OK;
}
zx::result<HandleOwner> move_handle_for_transfer(HandleOwner handle, const Dispatcher* channel,
zx_handle_t handle_val, zx_obj_type_t type,
zx_rights_t desired_rights) {
zx_status_t check_status = common_handle_checks_locked(*handle, channel, desired_rights, type);
if (check_status != ZX_OK) {
return zx::error(check_status);
}
// If the caller has requested a different set of rights, we have to mint a new handle for them.
if (desired_rights != ZX_RIGHT_SAME_RIGHTS && desired_rights != handle->rights()) {
// common_handle_checks_locked verifies that the desired rights are a subset of the handle's
// current rights.
auto descoped_rights_handle = Handle::Dup(*handle, desired_rights);
if (!descoped_rights_handle) {
return zx::error(ZX_ERR_NO_MEMORY);
}
handle = ktl::move(descoped_rights_handle);
}
return zx::ok(ktl::move(handle));
}
zx::result<HandleOwner> duplicate_handle_for_transfer(const Handle& source,
const Dispatcher* channel,
zx_handle_t handle_val, zx_obj_type_t type,
zx_rights_t desired_rights) {
zx_status_t check_status = common_handle_checks_locked(source, channel, desired_rights, type);
if (check_status != ZX_OK) {
return zx::error(check_status);
}
if (!source.HasRights(ZX_RIGHT_DUPLICATE))
return zx::error(ZX_ERR_ACCESS_DENIED);
// common_handle_checks_locked verifies that the desired rights are a subset of the handle's
// current rights.
const auto dest_rights =
(desired_rights == ZX_RIGHT_SAME_RIGHTS) ? source.rights() : desired_rights;
auto duped_handle = Handle::Dup(source, dest_rights);
if (!duped_handle) {
return zx::error(ZX_ERR_NO_MEMORY);
}
return zx::ok(ktl::move(duped_handle));
}
} // namespace
zx_status_t get_user_handles_to_consume(user_in_ptr<const zx_handle_t> user_handles, size_t offset,
size_t chunk_size, zx_handle_t* handles) {
return user_handles.copy_array_from_user(handles, chunk_size, offset);
}
zx_status_t get_user_handles_to_consume(user_inout_ptr<zx_handle_disposition_t> user_handles,
size_t offset, size_t chunk_size, zx_handle_t* handles) {
zx_handle_disposition_t local_handle_disposition[kMaxMessageHandles] = {};
chunk_size = ktl::min<size_t>(chunk_size, kMaxMessageHandles);
zx_status_t status =
user_handles.copy_array_from_user(local_handle_disposition, chunk_size, offset);
if (status != ZX_OK) {
return status;
}
for (size_t i = 0; i < chunk_size; i++) {
// !ZX_HANDLE_OP_DUPLICATE is used to capture the case where we failed
// due to a bad operational arg.
if (local_handle_disposition[i].operation != ZX_HANDLE_OP_DUPLICATE) {
handles[i] = local_handle_disposition[i].handle;
}
}
return ZX_OK;
}
// This overload is used by zx_channel_write.
zx::result<HandleOwner> get_handle_for_message_locked(ProcessDispatcher* process,
const Dispatcher* channel,
zx_handle_t handle_val) {
HandleOwner source = process->handle_table().RemoveHandleLocked(*process, handle_val);
if (!source) {
return zx::error(ZX_ERR_BAD_HANDLE);
}
return move_handle_for_transfer(ktl::move(source), channel, handle_val, ZX_OBJ_TYPE_NONE,
ZX_RIGHT_SAME_RIGHTS);
}
// This overload is used by zx_channel_write_etc.
zx::result<HandleOwner> get_handle_for_message_locked(ProcessDispatcher* process,
const Dispatcher* channel,
zx_handle_disposition_t& handle_disposition) {
const zx_handle_op_t operation = handle_disposition.operation;
const zx_rights_t desired_rights = handle_disposition.rights;
const zx_obj_type_t type = handle_disposition.type;
const zx_handle_t handle_val = handle_disposition.handle;
zx::result<HandleOwner> operation_result =
[&]() TA_REQ(process->handle_table().get_lock()) -> zx::result<HandleOwner> {
auto source = process->handle_table().GetHandleLocked(*process, handle_val);
if (!source) {
return zx::error(ZX_ERR_BAD_HANDLE);
}
// The documentation for zx_channel_write_etc says this about the operation performed on
// handles:
/// The operation applied to *handle* is one of:
///
/// * `ZX_HANDLE_OP_MOVE` This is equivalent to first issuing [`zx_handle_replace()`] then
/// [`zx_channel_write()`]. The source handle is always closed.
///
/// * `ZX_HANDLE_OP_DUPLICATE` This is equivalent to first issuing [`zx_handle_duplicate()`]
/// then [`zx_channel_write()`]. The source handle always remains open and accessible to
/// the caller.
// So when duplicating a handle, we leave the source handle in the handle table. For all other
// operations (including invalid operations) we immediately remove the source handle from the
// handle table and then attempt to move it.
if (operation == ZX_HANDLE_OP_DUPLICATE) {
return duplicate_handle_for_transfer(*source, channel, handle_val, type, desired_rights);
}
HandleOwner source_owner = process->handle_table().RemoveHandleLocked(source);
if (operation == ZX_HANDLE_OP_MOVE) {
return move_handle_for_transfer(ktl::move(source_owner), channel, handle_val, type,
desired_rights);
}
return zx::error(ZX_ERR_INVALID_ARGS);
}();
if (operation_result.is_error()) {
handle_disposition.result = operation_result.error_value();
}
return operation_result;
}