blob: 537c0aecb380740a67814fb293816d7dbd52456c [file] [log] [blame]
// Copyright 2016 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 <err.h>
#include <inttypes.h>
#include <trace.h>
#include <lib/ktrace.h>
#include <zircon/syscalls/policy.h>
#include <object/channel_dispatcher.h>
#include <object/handle_owner.h>
#include <object/handles.h>
#include <object/message_packet.h>
#include <object/process_dispatcher.h>
#include <fbl/algorithm.h>
#include <fbl/auto_lock.h>
#include <fbl/ref_ptr.h>
#include "syscalls_priv.h"
using fbl::AutoLock;
#define LOCAL_TRACE 0
zx_status_t sys_channel_create(
uint32_t options, user_ptr<zx_handle_t> out0, user_ptr<zx_handle_t> out1) {
LTRACEF("out_handles %p,%p\n", out0.get(), out1.get());
if (options != 0u)
return ZX_ERR_INVALID_ARGS;
auto up = ProcessDispatcher::GetCurrent();
zx_status_t res = up->QueryPolicy(ZX_POL_NEW_CHANNEL);
if (res != ZX_OK)
return res;
fbl::RefPtr<Dispatcher> mpd0, mpd1;
zx_rights_t rights;
zx_status_t result = ChannelDispatcher::Create(&mpd0, &mpd1, &rights);
if (result != ZX_OK)
return result;
uint64_t id0 = mpd0->get_koid();
uint64_t id1 = mpd1->get_koid();
HandleOwner h0(MakeHandle(fbl::move(mpd0), rights));
if (!h0)
return ZX_ERR_NO_MEMORY;
HandleOwner h1(MakeHandle(fbl::move(mpd1), rights));
if (!h1)
return ZX_ERR_NO_MEMORY;
if (out0.copy_to_user(up->MapHandleToValue(h0)) != ZX_OK)
return ZX_ERR_INVALID_ARGS;
if (out1.copy_to_user(up->MapHandleToValue(h1)) != ZX_OK)
return ZX_ERR_INVALID_ARGS;
up->AddHandle(fbl::move(h0));
up->AddHandle(fbl::move(h1));
ktrace(TAG_CHANNEL_CREATE, (uint32_t)id0, (uint32_t)id1, options, 0);
return ZX_OK;
}
static void msg_get_handles(ProcessDispatcher* up, MessagePacket* msg,
user_ptr<zx_handle_t> handles, uint32_t num_handles) {
Handle* const* handle_list = msg->handles();
msg->set_owns_handles(false);
zx_handle_t hvs[kMaxMessageHandles];
for (size_t i = 0; i < num_handles; ++i) {
hvs[i] = up->MapHandleToValue(handle_list[i]);
}
handles.copy_array_to_user(hvs, num_handles);
for (size_t i = 0; i < num_handles; ++i) {
if (handle_list[i]->dispatcher()->get_state_tracker())
handle_list[i]->dispatcher()->get_state_tracker()->Cancel(handle_list[i]);
HandleOwner handle(handle_list[i]);
// TODO(MG-969): This takes a lock per call. Consider doing these in a batch.
up->AddHandle(fbl::move(handle));
}
}
zx_status_t sys_channel_read(zx_handle_t handle_value, uint32_t options,
user_ptr<void> bytes, user_ptr<zx_handle_t> handles,
uint32_t num_bytes, uint32_t num_handles,
user_ptr<uint32_t> actual_bytes, user_ptr<uint32_t> actual_handles) {
LTRACEF("handle %x bytes %p num_bytes %p handles %p num_handles %p",
handle_value, bytes.get(), actual_bytes.get(), handles.get(), actual_handles.get());
auto up = ProcessDispatcher::GetCurrent();
fbl::RefPtr<ChannelDispatcher> channel;
zx_status_t result = up->GetDispatcherWithRights(handle_value, ZX_RIGHT_READ, &channel);
if (result != ZX_OK)
return result;
// Currently MAY_DISCARD is the only allowable option.
if (options & ~ZX_CHANNEL_READ_MAY_DISCARD)
return ZX_ERR_NOT_SUPPORTED;
fbl::unique_ptr<MessagePacket> msg;
result = channel->Read(&num_bytes, &num_handles, &msg,
options & ZX_CHANNEL_READ_MAY_DISCARD);
if (result != ZX_OK && result != ZX_ERR_BUFFER_TOO_SMALL)
return result;
// On ZX_ERR_BUFFER_TOO_SMALL, Read() gives us the size of the next message (which remains
// unconsumed, unless |options| has ZX_CHANNEL_READ_MAY_DISCARD set).
if (actual_bytes) {
if (actual_bytes.copy_to_user(num_bytes) != ZX_OK)
return ZX_ERR_INVALID_ARGS;
}
if (actual_handles) {
if (actual_handles.copy_to_user(num_handles) != ZX_OK)
return ZX_ERR_INVALID_ARGS;
}
if (result == ZX_ERR_BUFFER_TOO_SMALL)
return result;
if (num_bytes > 0u) {
if (msg->CopyDataTo(bytes) != ZX_OK)
return ZX_ERR_INVALID_ARGS;
}
// The documented public API states that that writing to the handles buffer
// must happen after writing to the data buffer.
if (num_handles > 0u) {
msg_get_handles(up, msg.get(), handles, num_handles);
}
ktrace(TAG_CHANNEL_READ, (uint32_t)channel->get_koid(), num_bytes, num_handles, 0);
return result;
}
static zx_status_t channel_read_out(ProcessDispatcher* up,
fbl::unique_ptr<MessagePacket> reply,
zx_channel_call_args_t* args,
user_ptr<uint32_t> actual_bytes,
user_ptr<uint32_t> actual_handles) {
uint32_t num_bytes = reply->data_size();
uint32_t num_handles = reply->num_handles();
if ((args->rd_num_bytes < num_bytes) || (args->rd_num_handles < num_handles)) {
return ZX_ERR_BUFFER_TOO_SMALL;
}
if (actual_bytes.copy_to_user(num_bytes) != ZX_OK) {
return ZX_ERR_INVALID_ARGS;
}
if (actual_handles.copy_to_user(num_handles) != ZX_OK) {
return ZX_ERR_INVALID_ARGS;
}
if (num_bytes > 0u) {
if (reply->CopyDataTo(make_user_ptr(args->rd_bytes)) != ZX_OK) {
return ZX_ERR_INVALID_ARGS;
}
}
if (num_handles > 0u) {
msg_get_handles(up, reply.get(), make_user_ptr(args->rd_handles), num_handles);
}
return ZX_OK;
}
// Handles generating the final results for call successes and read-half failures.
static zx_status_t channel_call_epilogue(ProcessDispatcher* up,
fbl::unique_ptr<MessagePacket> reply,
zx_channel_call_args_t* args,
zx_status_t call_status,
user_ptr<uint32_t> actual_bytes,
user_ptr<uint32_t> actual_handles,
user_ptr<zx_status_t> read_status) {
// Timeout is always returned directly.
if (call_status == ZX_ERR_TIMED_OUT) {
return call_status;
}
if (call_status == ZX_OK) {
call_status = channel_read_out(up, fbl::move(reply), args, actual_bytes, actual_handles);
}
if (call_status != ZX_OK) {
if (read_status) {
read_status.copy_to_user(call_status);
}
return ZX_ERR_CALL_FAILED;
}
return ZX_OK;
}
static zx_status_t msg_put_handles(ProcessDispatcher* up, MessagePacket* msg, zx_handle_t* handles,
user_ptr<const zx_handle_t> user_handles, uint32_t num_user_handles,
Dispatcher* channel) {
if (user_handles.copy_array_from_user(handles, num_user_handles) != ZX_OK)
return ZX_ERR_INVALID_ARGS;
{
// Loop twice, first we collect and validate handles, the second pass
// we remove them from this process.
AutoLock lock(up->handle_table_lock());
for (size_t ix = 0; ix != num_user_handles; ++ix) {
auto handle = up->GetHandleLocked(handles[ix]);
if (!handle)
return ZX_ERR_BAD_HANDLE;
if (handle->dispatcher().get() == channel) {
// You may not write a channel endpoint handle
// into that channel endpoint
return ZX_ERR_NOT_SUPPORTED;
}
if (!handle->HasRights(ZX_RIGHT_TRANSFER))
return ZX_ERR_ACCESS_DENIED;
msg->mutable_handles()[ix] = handle;
}
for (size_t ix = 0; ix != num_user_handles; ++ix) {
auto handle = up->RemoveHandleLocked(handles[ix]).release();
// Passing duplicate handles is not allowed.
// If we've already seen this handle flag an error.
if (!handle) {
// Put back the handles we've already removed.
for (size_t idx = 0; idx < ix; ++idx) {
up->UndoRemoveHandleLocked(handles[idx]);
}
// TODO(MG-968): more specific error?
return ZX_ERR_INVALID_ARGS;
}
}
}
// On success, the MessagePacket owns the handles.
msg->set_owns_handles(true);
return ZX_OK;
}
zx_status_t sys_channel_write(zx_handle_t handle_value, uint32_t options,
user_ptr<const void> user_bytes, uint32_t num_bytes,
user_ptr<const zx_handle_t> user_handles, uint32_t num_handles) {
LTRACEF("handle %x bytes %p num_bytes %u handles %p num_handles %u options 0x%x\n",
handle_value, user_bytes.get(), num_bytes, user_handles.get(), num_handles, options);
if (options)
return ZX_ERR_INVALID_ARGS;
auto up = ProcessDispatcher::GetCurrent();
fbl::RefPtr<ChannelDispatcher> channel;
zx_status_t result = up->GetDispatcherWithRights(handle_value, ZX_RIGHT_WRITE, &channel);
if (result != ZX_OK)
return result;
fbl::unique_ptr<MessagePacket> msg;
result = MessagePacket::Create(user_bytes, num_bytes, num_handles, &msg);
if (result != ZX_OK)
return result;
zx_handle_t handles[kMaxMessageHandles];
if (num_handles > 0u) {
result = msg_put_handles(up, msg.get(), handles, user_handles, num_handles,
static_cast<Dispatcher*>(channel.get()));
if (result)
return result;
}
result = channel->Write(fbl::move(msg));
if (result != ZX_OK) {
// Write failed, put back the handles into this process.
AutoLock lock(up->handle_table_lock());
for (size_t ix = 0; ix != num_handles; ++ix) {
up->UndoRemoveHandleLocked(handles[ix]);
}
return result;
}
ktrace(TAG_CHANNEL_WRITE, (uint32_t)channel->get_koid(), num_bytes, num_handles, 0);
return ZX_OK;
}
zx_status_t sys_channel_call_noretry(zx_handle_t handle_value, uint32_t options,
zx_time_t deadline,
user_ptr<const zx_channel_call_args_t> user_args,
user_ptr<uint32_t> actual_bytes,
user_ptr<uint32_t> actual_handles,
user_ptr<zx_status_t> read_status) {
zx_channel_call_args_t args;
if (user_args.copy_from_user(&args) != ZX_OK)
return ZX_ERR_INVALID_ARGS;
if (options)
return ZX_ERR_INVALID_ARGS;
uint32_t num_bytes = args.wr_num_bytes;
uint32_t num_handles = args.wr_num_handles;
auto up = ProcessDispatcher::GetCurrent();
fbl::RefPtr<ChannelDispatcher> channel;
zx_status_t result =
up->GetDispatcherWithRights(handle_value, ZX_RIGHT_WRITE | ZX_RIGHT_READ, &channel);
if (result != ZX_OK)
return result;
// Prepare a MessagePacket for writing
fbl::unique_ptr<MessagePacket> msg;
result = MessagePacket::Create(make_user_ptr<const void>(args.wr_bytes),
num_bytes, num_handles, &msg);
if (result != ZX_OK)
return result;
zx_handle_t handles[kMaxMessageHandles];
if (num_handles > 0u) {
result = msg_put_handles(up, msg.get(), handles,
make_user_ptr<const zx_handle_t>(args.wr_handles), num_handles,
static_cast<Dispatcher*>(channel.get()));
if (result)
return result;
}
// TODO(MG-970): ktrace channel calls; maybe two traces, maybe with txid.
// Write message and wait for reply, deadline, or cancelation
bool return_handles = false;
fbl::unique_ptr<MessagePacket> reply;
if ((result = channel->Call(fbl::move(msg), deadline, &return_handles, &reply)) != ZX_OK) {
if (return_handles) {
// Write phase failed:
// 1. Put back the handles into this process.
AutoLock lock(up->handle_table_lock());
for (size_t ix = 0; ix != num_handles; ++ix) {
up->UndoRemoveHandleLocked(handles[ix]);
}
// 2. Return error directly. Note that the write phase cannot fail
// with ZX_ERR_INTERNAL_INTR_RETRY.
DEBUG_ASSERT(result != ZX_ERR_INTERNAL_INTR_RETRY);
return result;
}
}
return channel_call_epilogue(up, fbl::move(reply), &args, result,
actual_bytes, actual_handles, read_status);
}
zx_status_t sys_channel_call_finish(zx_time_t deadline,
user_ptr<const zx_channel_call_args_t> user_args,
user_ptr<uint32_t> actual_bytes,
user_ptr<uint32_t> actual_handles,
user_ptr<zx_status_t> read_status) {
zx_channel_call_args_t args;
if (user_args.copy_from_user(&args) != ZX_OK)
return ZX_ERR_INVALID_ARGS;
auto up = ProcessDispatcher::GetCurrent();
auto waiter = ThreadDispatcher::GetCurrent()->GetMessageWaiter();
fbl::RefPtr<ChannelDispatcher> channel = waiter->get_channel();
if (!channel)
return ZX_ERR_BAD_STATE;
fbl::unique_ptr<MessagePacket> reply;
zx_status_t result = channel->ResumeInterruptedCall(
waiter, deadline, &reply);
return channel_call_epilogue(up, fbl::move(reply), &args, result,
actual_bytes, actual_handles, read_status);
}