blob: 63403c913ff1684896c2d767c85874a92abadd8f [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 <platform.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <trace.h>
#include <arch/arch_ops.h>
#include <lib/ktrace.h>
#include <lib/user_copy/user_ptr.h>
#include <object/handle_owner.h>
#include <object/handles.h>
#include <object/job_dispatcher.h>
#include <object/process_dispatcher.h>
#include <object/resource_dispatcher.h>
#include <object/thread_dispatcher.h>
#include <object/vm_address_region_dispatcher.h>
#include <zircon/syscalls/debug.h>
#include <zircon/syscalls/policy.h>
#include <fbl/auto_lock.h>
#include <fbl/inline_array.h>
#include <fbl/ref_ptr.h>
#include <fbl/string_piece.h>
#include "syscalls_priv.h"
#define LOCAL_TRACE 0
#define THREAD_SET_PRIORITY_EXPERIMENT 1
#if THREAD_SET_PRIORITY_EXPERIMENT
#include <kernel/cmdline.h>
#include <kernel/thread.h>
#include <lk/init.h>
#endif
// For reading general purpose integer registers, we can allocate in
// an inline array and save the malloc. Assume 64 registers as a
// conservative estimate for an architecture with 32 general purpose
// integer registers.
constexpr uint32_t kInlineThreadStateSize = sizeof(void*) * 64;
constexpr uint32_t kMaxThreadStateSize = ZX_MAX_THREAD_STATE_SIZE;
constexpr size_t kMaxDebugReadBlock = 64 * 1024u * 1024u;
constexpr size_t kMaxDebugWriteBlock = 64 * 1024u * 1024u;
// Assume the typical set-policy call has 8 items or less.
constexpr size_t kPolicyBasicInlineCount = 8;
#if THREAD_SET_PRIORITY_EXPERIMENT
// See ZX-940
static bool thread_set_priority_allowed = false;
static void thread_set_priority_experiment_init_hook(uint) {
thread_set_priority_allowed = cmdline_get_bool("thread.set.priority.allowed", false);
printf("thread set priority experiment is : %s\n",
thread_set_priority_allowed ? "ENABLED" : "DISABLED");
}
LK_INIT_HOOK(thread_set_priority_experiment,
thread_set_priority_experiment_init_hook,
LK_INIT_LEVEL_THREADING - 1);
#endif
// TODO(ZX-1025): copy_user_string may truncate the incoming string,
// and may copy extra data past the NUL.
// TODO(dbort): If anyone else needs this, move it into user_ptr.
static zx_status_t copy_user_string(const user_in_ptr<const char>& src,
size_t src_len,
char* buf, size_t buf_len,
fbl::StringPiece* sp) {
if (!src || src_len > buf_len) {
return ZX_ERR_INVALID_ARGS;
}
zx_status_t result = src.copy_array_from_user(buf, src_len);
if (result != ZX_OK) {
return ZX_ERR_INVALID_ARGS;
}
// ensure zero termination
size_t str_len = (src_len == buf_len ? src_len - 1 : src_len);
buf[str_len] = 0;
*sp = fbl::StringPiece(buf);
return ZX_OK;
}
// Convenience function to go from process handle to process.
static zx_status_t get_process(ProcessDispatcher* up,
zx_handle_t proc_handle,
fbl::RefPtr<ProcessDispatcher>* proc) {
return up->GetDispatcherWithRights(proc_handle, ZX_RIGHT_WRITE, proc);
}
zx_status_t sys_thread_create(zx_handle_t process_handle,
user_in_ptr<const char> _name, uint32_t name_len,
uint32_t options, user_out_ptr<zx_handle_t> _out) {
LTRACEF("process handle %x, options %#x\n", process_handle, options);
// currently, the only valid option value is 0
if (options != 0)
return ZX_ERR_INVALID_ARGS;
// copy out the name
char buf[ZX_MAX_NAME_LEN];
fbl::StringPiece sp;
// Silently truncate the given name.
if (name_len > sizeof(buf))
name_len = sizeof(buf);
zx_status_t result = copy_user_string(_name, name_len,
buf, sizeof(buf), &sp);
if (result != ZX_OK)
return result;
LTRACEF("name %s\n", buf);
// convert process handle to process dispatcher
auto up = ProcessDispatcher::GetCurrent();
fbl::RefPtr<ProcessDispatcher> process;
result = get_process(up, process_handle, &process);
if (result != ZX_OK)
return result;
uint32_t pid = (uint32_t)process->get_koid();
// create the thread dispatcher
fbl::RefPtr<Dispatcher> thread_dispatcher;
zx_rights_t thread_rights;
result = ThreadDispatcher::Create(fbl::move(process), options, sp,
&thread_dispatcher, &thread_rights);
if (result != ZX_OK)
return result;
uint32_t tid = (uint32_t)thread_dispatcher->get_koid();
ktrace(TAG_THREAD_CREATE, tid, pid, 0, 0);
ktrace_name(TAG_THREAD_NAME, tid, pid, buf);
HandleOwner handle(MakeHandle(fbl::move(thread_dispatcher), thread_rights));
if (!handle)
return ZX_ERR_NO_MEMORY;
if (_out.copy_to_user(up->MapHandleToValue(handle)) != ZX_OK)
return ZX_ERR_INVALID_ARGS;
up->AddHandle(fbl::move(handle));
return ZX_OK;
}
zx_status_t sys_thread_start(zx_handle_t thread_handle, uintptr_t entry,
uintptr_t stack, uintptr_t arg1, uintptr_t arg2) {
LTRACEF("handle %x, entry %#" PRIxPTR ", sp %#" PRIxPTR
", arg1 %#" PRIxPTR ", arg2 %#" PRIxPTR "\n",
thread_handle, entry, stack, arg1, arg2);
auto up = ProcessDispatcher::GetCurrent();
fbl::RefPtr<ThreadDispatcher> thread;
zx_status_t status = up->GetDispatcherWithRights(thread_handle, ZX_RIGHT_WRITE,
&thread);
if (status != ZX_OK)
return status;
ktrace(TAG_THREAD_START, (uint32_t)thread->get_koid(), 0, 0, 0);
return thread->Start(entry, stack, arg1, arg2, /* initial_thread= */ false);
}
void sys_thread_exit() {
LTRACE_ENTRY;
ThreadDispatcher::GetCurrent()->Exit();
}
zx_status_t sys_thread_read_state(zx_handle_t handle, uint32_t state_kind,
user_out_ptr<void> _buffer,
uint32_t buffer_len, user_out_ptr<uint32_t> _actual) {
LTRACEF("handle %x, state_kind %u\n", handle, state_kind);
auto up = ProcessDispatcher::GetCurrent();
// TODO(ZX-968): debug rights
fbl::RefPtr<ThreadDispatcher> thread;
zx_status_t status = up->GetDispatcherWithRights(handle, ZX_RIGHT_READ, &thread);
if (status != ZX_OK)
return status;
// avoid malloc'ing insane amounts
if (buffer_len > kMaxThreadStateSize)
return ZX_ERR_INVALID_ARGS;
fbl::AllocChecker ac;
fbl::InlineArray<uint8_t, kInlineThreadStateSize> bytes(&ac, buffer_len);
if (!ac.check())
return ZX_ERR_NO_MEMORY;
status = thread->ReadState(state_kind, bytes.get(), &buffer_len);
// Always set the actual size so the caller can provide larger buffers.
// The value is only usable if the status is ZX_OK or ZX_ERR_BUFFER_TOO_SMALL.
if (status == ZX_OK || status == ZX_ERR_BUFFER_TOO_SMALL) {
if (_actual.copy_to_user(buffer_len) != ZX_OK)
return ZX_ERR_INVALID_ARGS;
}
if (status != ZX_OK)
return status;
if (_buffer.copy_array_to_user(bytes.get(), buffer_len) != ZX_OK)
return ZX_ERR_INVALID_ARGS;
return ZX_OK;
}
zx_status_t sys_thread_write_state(zx_handle_t handle, uint32_t state_kind,
user_in_ptr<const void> _buffer, uint32_t buffer_len) {
LTRACEF("handle %x, state_kind %u\n", handle, state_kind);
auto up = ProcessDispatcher::GetCurrent();
// TODO(ZX-968): debug rights
fbl::RefPtr<ThreadDispatcher> thread;
zx_status_t status = up->GetDispatcherWithRights(handle, ZX_RIGHT_WRITE, &thread);
if (status != ZX_OK)
return status;
// avoid malloc'ing insane amounts
if (buffer_len > kMaxThreadStateSize)
return ZX_ERR_INVALID_ARGS;
fbl::AllocChecker ac;
fbl::InlineArray<uint8_t, kInlineThreadStateSize> bytes(&ac, buffer_len);
if (!ac.check())
return ZX_ERR_NO_MEMORY;
status = _buffer.copy_array_from_user(bytes.get(), buffer_len);
if (status != ZX_OK)
return ZX_ERR_INVALID_ARGS;
status = thread->WriteState(state_kind, bytes.get(), buffer_len);
return status;
}
// See ZX-940
zx_status_t sys_thread_set_priority(int32_t prio) {
#if THREAD_SET_PRIORITY_EXPERIMENT
// If the experimental zx_thread_set_priority has not been enabled using the
// kernel command line option, simply deny this request.
if (!thread_set_priority_allowed)
return ZX_ERR_NOT_SUPPORTED;
if ((prio < LOWEST_PRIORITY) || (prio > HIGHEST_PRIORITY))
return ZX_ERR_INVALID_ARGS;
thread_set_priority(prio);
return ZX_OK;
#else
return ZX_ERR_NOT_SUPPORTED;
#endif
}
zx_status_t sys_task_suspend(zx_handle_t task_handle) {
LTRACE_ENTRY;
auto up = ProcessDispatcher::GetCurrent();
// TODO(teisenbe): Add support for tasks other than threads
fbl::RefPtr<ThreadDispatcher> thread;
zx_status_t status = up->GetDispatcherWithRights(task_handle, ZX_RIGHT_WRITE,
&thread);
if (status != ZX_OK)
return status;
return thread->Suspend();
}
zx_status_t sys_process_create(zx_handle_t job_handle,
user_in_ptr<const char> _name, uint32_t name_len,
uint32_t options, user_out_ptr<zx_handle_t> _proc_handle,
user_out_ptr<zx_handle_t> _vmar_handle) {
LTRACEF("job handle %x, options %#x\n", job_handle, options);
// currently, the only valid option value is 0
if (options != 0)
return ZX_ERR_INVALID_ARGS;
// copy out the name
char buf[ZX_MAX_NAME_LEN];
fbl::StringPiece sp;
// Silently truncate the given name.
if (name_len > sizeof(buf))
name_len = sizeof(buf);
zx_status_t result = copy_user_string(_name, name_len,
buf, sizeof(buf), &sp);
if (result != ZX_OK)
return result;
LTRACEF("name %s\n", buf);
// convert job handle to job dispatcher
auto up = ProcessDispatcher::GetCurrent();
fbl::RefPtr<JobDispatcher> job;
// TODO(ZX-968): define process creation job rights.
auto status = up->GetDispatcherWithRights(job_handle, ZX_RIGHT_WRITE, &job);
if (status != ZX_OK)
return status;
// create a new process dispatcher
fbl::RefPtr<Dispatcher> proc_dispatcher;
fbl::RefPtr<VmAddressRegionDispatcher> vmar_dispatcher;
zx_rights_t proc_rights, vmar_rights;
zx_status_t res = ProcessDispatcher::Create(fbl::move(job), sp, options,
&proc_dispatcher, &proc_rights,
&vmar_dispatcher, &vmar_rights);
if (res != ZX_OK)
return res;
uint32_t koid = (uint32_t)proc_dispatcher->get_koid();
ktrace(TAG_PROC_CREATE, koid, 0, 0, 0);
ktrace_name(TAG_PROC_NAME, koid, 0, buf);
// Give arch-specific tracing a chance to record process creation.
arch_trace_process_create(koid, vmar_dispatcher->vmar()->aspace()->arch_aspace().arch_table_phys());
// Create a handle and attach the dispatcher to it
HandleOwner proc_h(MakeHandle(fbl::move(proc_dispatcher), proc_rights));
if (!proc_h)
return ZX_ERR_NO_MEMORY;
// Create a handle and attach the dispatcher to it
HandleOwner vmar_h(MakeHandle(fbl::move(vmar_dispatcher), vmar_rights));
if (!vmar_h)
return ZX_ERR_NO_MEMORY;
if (_proc_handle.copy_to_user(up->MapHandleToValue(proc_h)) != ZX_OK)
return ZX_ERR_INVALID_ARGS;
if (_vmar_handle.copy_to_user(up->MapHandleToValue(vmar_h)) != ZX_OK)
return ZX_ERR_INVALID_ARGS;
up->AddHandle(fbl::move(vmar_h));
up->AddHandle(fbl::move(proc_h));
return ZX_OK;
}
// Note: This is used to start the main thread (as opposed to using
// sys_thread_start for that) for a few reasons:
// - less easily exploitable
// We want to make sure we can't generically transfer handles to a process.
// This has the nice property of restricting the evil (transferring handle
// to new process) to exactly one spot, and can be called exactly once per
// process, since it also pushes it into a new state.
// - maintains the state machine invariant that 'started' processes have one
// thread running
zx_status_t sys_process_start(zx_handle_t process_handle, zx_handle_t thread_handle,
uintptr_t pc, uintptr_t sp,
zx_handle_t arg_handle_value, uintptr_t arg2) {
LTRACEF("phandle %x, thandle %x, pc %#" PRIxPTR ", sp %#" PRIxPTR
", arg_handle %x, arg2 %#" PRIxPTR "\n",
process_handle, thread_handle, pc, sp, arg_handle_value, arg2);
auto up = ProcessDispatcher::GetCurrent();
// get process dispatcher
fbl::RefPtr<ProcessDispatcher> process;
zx_status_t status = get_process(up, process_handle, &process);
if (status != ZX_OK)
return status;
// get thread_dispatcher
fbl::RefPtr<ThreadDispatcher> thread;
status = up->GetDispatcherWithRights(thread_handle, ZX_RIGHT_WRITE, &thread);
if (status != ZX_OK)
return status;
// test that the thread belongs to the starting process
if (thread->process() != process.get())
return ZX_ERR_ACCESS_DENIED;
HandleOwner arg_handle;
{
fbl::AutoLock lock(up->handle_table_lock());
auto handle = up->GetHandleLocked(arg_handle_value);
if (!handle)
return ZX_ERR_BAD_HANDLE;
if (!handle->HasRights(ZX_RIGHT_TRANSFER))
return ZX_ERR_ACCESS_DENIED;
arg_handle = up->RemoveHandleLocked(arg_handle_value);
}
auto arg_nhv = process->MapHandleToValue(arg_handle);
process->AddHandle(fbl::move(arg_handle));
status = thread->Start(pc, sp, arg_nhv, arg2, /* initial_thread */ true);
if (status != ZX_OK) {
// Put back the |arg_handle| into the calling process.
auto handle = process->RemoveHandle(arg_nhv);
up->AddHandle(fbl::move(handle));
return status;
}
ktrace(TAG_PROC_START, (uint32_t)thread->get_koid(),
(uint32_t)process->get_koid(), 0, 0);
return ZX_OK;
}
void sys_process_exit(int retcode) {
LTRACEF("retcode %d\n", retcode);
ProcessDispatcher::GetCurrent()->Exit(retcode);
}
zx_status_t sys_process_read_memory(zx_handle_t proc, uintptr_t vaddr,
user_out_ptr<void> _buffer,
size_t len, user_out_ptr<size_t> _actual) {
LTRACEF("vaddr 0x%" PRIxPTR ", size %zu\n", vaddr, len);
if (!_buffer)
return ZX_ERR_INVALID_ARGS;
if (len == 0 || len > kMaxDebugReadBlock)
return ZX_ERR_INVALID_ARGS;
auto up = ProcessDispatcher::GetCurrent();
fbl::RefPtr<ProcessDispatcher> process;
zx_status_t status = up->GetDispatcherWithRights(proc, ZX_RIGHT_READ | ZX_RIGHT_WRITE,
&process);
if (status != ZX_OK)
return status;
auto aspace = process->aspace();
if (!aspace)
return ZX_ERR_BAD_STATE;
auto region = aspace->FindRegion(vaddr);
if (!region)
return ZX_ERR_NO_MEMORY;
auto vm_mapping = region->as_vm_mapping();
if (!vm_mapping)
return ZX_ERR_NO_MEMORY;
auto vmo = vm_mapping->vmo();
if (!vmo)
return ZX_ERR_NO_MEMORY;
uint64_t offset = vaddr - vm_mapping->base() + vm_mapping->object_offset();
size_t read = 0;
// Force map the range, even if it crosses multiple mappings.
// TODO(ZX-730): This is a workaround for this bug. If we start decommitting
// things, the bug will come back. We should fix this more properly.
{
uint8_t byte = 0;
auto int_data = _buffer.reinterpret<uint8_t>();
for (size_t i = 0; i < len; i += PAGE_SIZE) {
status = int_data.copy_array_to_user(&byte, 1, i);
if (status != ZX_OK) {
return status;
}
}
if (len > 0) {
status = int_data.copy_array_to_user(&byte, 1, len - 1);
if (status != ZX_OK) {
return status;
}
}
}
zx_status_t st = vmo->ReadUser(_buffer, offset, len, &read);
if (st == ZX_OK) {
if (_actual.copy_to_user(static_cast<size_t>(read)) != ZX_OK)
return ZX_ERR_INVALID_ARGS;
}
return st;
}
zx_status_t sys_process_write_memory(zx_handle_t proc, uintptr_t vaddr,
user_in_ptr<const void> _buffer,
size_t len, user_out_ptr<size_t> _actual) {
LTRACEF("vaddr 0x%" PRIxPTR ", size %zu\n", vaddr, len);
if (!_buffer)
return ZX_ERR_INVALID_ARGS;
if (len == 0 || len > kMaxDebugWriteBlock)
return ZX_ERR_INVALID_ARGS;
auto up = ProcessDispatcher::GetCurrent();
fbl::RefPtr<ProcessDispatcher> process;
zx_status_t status = up->GetDispatcherWithRights(proc, ZX_RIGHT_WRITE, &process);
if (status != ZX_OK)
return status;
auto aspace = process->aspace();
if (!aspace)
return ZX_ERR_BAD_STATE;
auto region = aspace->FindRegion(vaddr);
if (!region)
return ZX_ERR_NO_MEMORY;
auto vm_mapping = region->as_vm_mapping();
if (!vm_mapping)
return ZX_ERR_NO_MEMORY;
auto vmo = vm_mapping->vmo();
if (!vmo)
return ZX_ERR_NO_MEMORY;
// Force map the range, even if it crosses multiple mappings.
// TODO(ZX-730): This is a workaround for this bug. If we start decommitting
// things, the bug will come back. We should fix this more properly.
{
uint8_t byte = 0;
auto int_data = _buffer.reinterpret<const uint8_t>();
for (size_t i = 0; i < len; i += PAGE_SIZE) {
status = int_data.copy_array_from_user(&byte, 1, i);
if (status != ZX_OK) {
return status;
}
}
if (len > 0) {
status = int_data.copy_array_from_user(&byte, 1, len - 1);
if (status != ZX_OK) {
return status;
}
}
}
uint64_t offset = vaddr - vm_mapping->base() + vm_mapping->object_offset();
size_t written = 0;
zx_status_t st = vmo->WriteUser(_buffer, offset, len, &written);
if (st == ZX_OK) {
if (_actual.copy_to_user(static_cast<size_t>(written)) != ZX_OK)
return ZX_ERR_INVALID_ARGS;
}
return st;
}
// helper routine for sys_task_kill
template <typename T>
static zx_status_t kill_task(fbl::RefPtr<Dispatcher> dispatcher) {
auto task = DownCastDispatcher<T>(&dispatcher);
if (!task)
return ZX_ERR_WRONG_TYPE;
task->Kill();
return ZX_OK;
}
zx_status_t sys_task_kill(zx_handle_t task_handle) {
LTRACEF("handle %x\n", task_handle);
auto up = ProcessDispatcher::GetCurrent();
fbl::RefPtr<Dispatcher> dispatcher;
auto status = up->GetDispatcherWithRights(task_handle, ZX_RIGHT_DESTROY, &dispatcher);
if (status != ZX_OK)
return status;
// see if it's a process or thread and dispatch accordingly
switch (dispatcher->get_type()) {
case ZX_OBJ_TYPE_PROCESS:
return kill_task<ProcessDispatcher>(fbl::move(dispatcher));
case ZX_OBJ_TYPE_THREAD:
return kill_task<ThreadDispatcher>(fbl::move(dispatcher));
case ZX_OBJ_TYPE_JOB:
return kill_task<JobDispatcher>(fbl::move(dispatcher));
default:
return ZX_ERR_WRONG_TYPE;
}
}
zx_status_t sys_job_create(zx_handle_t parent_job, uint32_t options, user_out_ptr<zx_handle_t> _out) {
LTRACEF("parent: %x\n", parent_job);
if (options != 0u)
return ZX_ERR_INVALID_ARGS;
auto up = ProcessDispatcher::GetCurrent();
fbl::RefPtr<JobDispatcher> parent;
zx_status_t status = up->GetDispatcherWithRights(parent_job, ZX_RIGHT_WRITE, &parent);
if (status != ZX_OK)
return status;
fbl::RefPtr<Dispatcher> job;
zx_rights_t rights;
status = JobDispatcher::Create(options, fbl::move(parent), &job, &rights);
if (status != ZX_OK)
return status;
HandleOwner job_handle(MakeHandle(fbl::move(job), rights));
if (_out.copy_to_user(up->MapHandleToValue(job_handle)) != ZX_OK)
return ZX_ERR_INVALID_ARGS;
up->AddHandle(fbl::move(job_handle));
return ZX_OK;
}
zx_status_t sys_job_set_policy(zx_handle_t job_handle, uint32_t options,
uint32_t topic, user_in_ptr<const void> _policy,
uint32_t count) {
if ((options != ZX_JOB_POL_RELATIVE) && (options != ZX_JOB_POL_ABSOLUTE))
return ZX_ERR_INVALID_ARGS;
if (!_policy || (count == 0u))
return ZX_ERR_INVALID_ARGS;
if (topic != ZX_JOB_POL_BASIC)
return ZX_ERR_INVALID_ARGS;
fbl::AllocChecker ac;
fbl::InlineArray<
zx_policy_basic, kPolicyBasicInlineCount> policy(&ac, count);
if (!ac.check())
return ZX_ERR_NO_MEMORY;
auto status = _policy.copy_array_from_user(policy.get(), sizeof(zx_policy_basic) * count);
if (status != ZX_OK)
return ZX_ERR_INVALID_ARGS;
auto up = ProcessDispatcher::GetCurrent();
fbl::RefPtr<JobDispatcher> job;
status = up->GetDispatcherWithRights(job_handle, ZX_RIGHT_SET_POLICY, &job);
if (status != ZX_OK)
return status;
return job->SetPolicy(options, policy.get(), policy.size());
}
zx_status_t sys_job_set_relative_importance(
zx_handle_t resource_handle,
zx_handle_t job_handle, zx_handle_t less_important_job_handle) {
ProcessDispatcher* up = ProcessDispatcher::GetCurrent();
// If the caller has a valid handle to the root resource, let them perform
// this operation no matter the rights on the job handles.
{
fbl::RefPtr<ResourceDispatcher> resource;
zx_status_t status = up->GetDispatcherWithRights(
resource_handle, ZX_RIGHT_NONE, &resource);
if (status != ZX_OK)
return status;
// TODO(ZX-971): Check that this is actually the appropriate resource
}
// Get the job to modify.
fbl::RefPtr<JobDispatcher> job;
zx_status_t status = up->GetDispatcherWithRights(
job_handle, ZX_RIGHT_NONE, &job);
if (status != ZX_OK)
return status;
// Get its less-important neighbor, or null.
fbl::RefPtr<JobDispatcher> li_job;
if (less_important_job_handle != ZX_HANDLE_INVALID) {
status = up->GetDispatcherWithRights(
less_important_job_handle, ZX_RIGHT_NONE, &li_job);
if (status != ZX_OK)
return status;
}
return job->MakeMoreImportantThan(fbl::move(li_job));
}