|  | // 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.h> | 
|  | #include <object/job_dispatcher.h> | 
|  | #include <object/process_dispatcher.h> | 
|  | #include <object/resource_dispatcher.h> | 
|  | #include <object/suspend_token_dispatcher.h> | 
|  | #include <object/thread_dispatcher.h> | 
|  | #include <object/vm_address_region_dispatcher.h> | 
|  |  | 
|  | #include <fbl/auto_lock.h> | 
|  | #include <fbl/inline_array.h> | 
|  | #include <fbl/ref_ptr.h> | 
|  | #include <fbl/string_piece.h> | 
|  | #include <zircon/syscalls/debug.h> | 
|  | #include <zircon/syscalls/policy.h> | 
|  |  | 
|  | #include "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 | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | 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 | 
|  | // This was initially set to false by default. See ZX-940 for the rationale | 
|  | // for being enabled by default. | 
|  | bool thread_set_priority_allowed = true; | 
|  | void thread_set_priority_experiment_init_hook(uint) { | 
|  | thread_set_priority_allowed = !cmdline_get_bool("thread.set.priority.disable", 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. | 
|  | 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. | 
|  | 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); | 
|  | } | 
|  |  | 
|  | // This represents the local storage for thread_read/write_state. It should be large enough to | 
|  | // handle all structures passed over these APIs. | 
|  | union thread_state_local_buffer_t { | 
|  | zx_thread_state_general_regs_t general_regs; // ZX_THREAD_STATE_GENERAL_REGS | 
|  | zx_thread_state_fp_regs_t fp_regs;           // ZX_THREAD_STATE_FP_REGS | 
|  | zx_thread_state_vector_regs_t vector_regs;   // ZX_THREAD_STATE_VECTOR_REGS | 
|  | uint32_t single_step;                        // ZX_THREAD_STATE_SINGLE_STEP | 
|  | uint64_t x86_register_fs;                    // ZX_THREAD_X86_REGISTER_FS; | 
|  | uint64_t x86_register_gs;                    // ZX_THREAD_X86_REGISTER_GS; | 
|  | }; | 
|  |  | 
|  | // Validates the input topic to thread_read_state and thread_write_state is a valid value, and | 
|  | // checks that the input buffer size is at least as large as necessary for the topic. On ZX_OK, the | 
|  | // actual size necessary for the buffer will be placed in the output parameter. | 
|  | zx_status_t validate_thread_state_input(uint32_t in_topic, size_t in_len, size_t* out_len) { | 
|  | switch (in_topic) { | 
|  | case ZX_THREAD_STATE_GENERAL_REGS: | 
|  | *out_len = sizeof(zx_thread_state_general_regs_t); | 
|  | break; | 
|  | case ZX_THREAD_STATE_FP_REGS: | 
|  | *out_len = sizeof(zx_thread_state_fp_regs_t); | 
|  | break; | 
|  | case ZX_THREAD_STATE_VECTOR_REGS: | 
|  | *out_len = sizeof(zx_thread_state_vector_regs_t); | 
|  | break; | 
|  | case ZX_THREAD_STATE_SINGLE_STEP: | 
|  | *out_len = sizeof(zx_thread_state_single_step_t); | 
|  | break; | 
|  | case ZX_THREAD_X86_REGISTER_FS: | 
|  | *out_len = sizeof(zx_thread_x86_register_fs_t); | 
|  | break; | 
|  | case ZX_THREAD_X86_REGISTER_GS: | 
|  | *out_len = sizeof(zx_thread_x86_register_gs_t); | 
|  | break; | 
|  | default: | 
|  | return ZX_ERR_INVALID_ARGS; | 
|  | } | 
|  |  | 
|  | if (in_len < *out_len) | 
|  | return ZX_ERR_BUFFER_TOO_SMALL; | 
|  | return ZX_OK; | 
|  | } | 
|  |  | 
|  | } // namespace | 
|  |  | 
|  | zx_status_t sys_thread_create(zx_handle_t process_handle, | 
|  | user_in_ptr<const char> _name, size_t name_len, | 
|  | uint32_t options, user_out_handle* 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); | 
|  |  | 
|  | return out->make(fbl::move(thread_dispatcher), thread_rights); | 
|  | } | 
|  |  | 
|  | zx_status_t sys_thread_start(zx_handle_t thread_handle, zx_vaddr_t entry, | 
|  | zx_vaddr_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_MANAGE_THREAD, | 
|  | &thread); | 
|  | if (status != ZX_OK) { | 
|  | // Try again, but with the WRITE right. | 
|  | // TODO(kulakowski) Remove this when all callers are using MANAGE_THREAD. | 
|  | 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, size_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_READ, &thread); | 
|  | if (status != ZX_OK) | 
|  | return status; | 
|  |  | 
|  | thread_state_local_buffer_t local_buffer; | 
|  | size_t local_buffer_len = 0; | 
|  | status = validate_thread_state_input(state_kind, buffer_len, &local_buffer_len); | 
|  | if (status != ZX_OK) | 
|  | return status; | 
|  |  | 
|  | status = thread->ReadState(static_cast<zx_thread_state_topic_t>(state_kind), &local_buffer, | 
|  | local_buffer_len); | 
|  | if (status != ZX_OK) | 
|  | return status; | 
|  |  | 
|  | if (_buffer.copy_array_to_user(&local_buffer, local_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, size_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; | 
|  |  | 
|  | thread_state_local_buffer_t local_buffer; | 
|  | size_t local_buffer_len = 0; | 
|  | status = validate_thread_state_input(state_kind, buffer_len, &local_buffer_len); | 
|  | if (status != ZX_OK) | 
|  | return status; | 
|  |  | 
|  | // Additionally check that the buffer is the exact size expected (validate only checks it's | 
|  | // larger, which is sufficient for reading). | 
|  | if (local_buffer_len != buffer_len) | 
|  | return ZX_ERR_INVALID_ARGS; | 
|  |  | 
|  | status = _buffer.copy_array_from_user(&local_buffer, local_buffer_len); | 
|  | if (status != ZX_OK) | 
|  | return ZX_ERR_INVALID_ARGS; | 
|  |  | 
|  | return thread->WriteState(static_cast<zx_thread_state_topic_t>(state_kind), &local_buffer, | 
|  | local_buffer_len); | 
|  | } | 
|  |  | 
|  | // 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(get_current_thread(), prio); | 
|  |  | 
|  | return ZX_OK; | 
|  | #else | 
|  | return ZX_ERR_NOT_SUPPORTED; | 
|  | #endif | 
|  | } | 
|  |  | 
|  | zx_status_t sys_task_suspend(zx_handle_t task_handle, user_out_handle* token) { | 
|  | LTRACE_ENTRY; | 
|  |  | 
|  | auto up = ProcessDispatcher::GetCurrent(); | 
|  |  | 
|  | // TODO(ZX-1840): 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; | 
|  |  | 
|  | fbl::RefPtr<Dispatcher> suspend_token; | 
|  | zx_rights_t rights; | 
|  | status = SuspendTokenDispatcher::Create(fbl::move(thread), &suspend_token, &rights); | 
|  | if (status == ZX_OK) | 
|  | status = token->make(fbl::move(suspend_token), rights); | 
|  | return status; | 
|  | } | 
|  |  | 
|  | zx_status_t sys_task_suspend_token(zx_handle_t task_handle, user_out_handle* token) { | 
|  | return sys_task_suspend(task_handle, token); | 
|  | } | 
|  |  | 
|  | zx_status_t sys_process_create(zx_handle_t job_handle, | 
|  | user_in_ptr<const char> _name, size_t name_len, | 
|  | uint32_t options, | 
|  | user_out_handle* proc_handle, | 
|  | user_out_handle* 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; | 
|  |  | 
|  | auto up = ProcessDispatcher::GetCurrent(); | 
|  |  | 
|  | // We check the policy against the process calling zx_process_create, which | 
|  | // is the operative policy, rather than against |job_handle|. Access to | 
|  | // |job_handle| is controlled by the rights associated with the handle. | 
|  | zx_status_t result = up->QueryPolicy(ZX_POL_NEW_PROCESS); | 
|  | if (result != ZX_OK) | 
|  | return result; | 
|  |  | 
|  | // 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); | 
|  | result = copy_user_string(_name, name_len, buf, sizeof(buf), &sp); | 
|  | if (result != ZX_OK) | 
|  | return result; | 
|  | LTRACEF("name %s\n", buf); | 
|  |  | 
|  | 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; | 
|  | result = ProcessDispatcher::Create(fbl::move(job), sp, options, | 
|  | &proc_dispatcher, &proc_rights, | 
|  | &vmar_dispatcher, &vmar_rights); | 
|  | if (result != ZX_OK) | 
|  | return result; | 
|  |  | 
|  | 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()); | 
|  |  | 
|  | result = proc_handle->make(fbl::move(proc_dispatcher), proc_rights); | 
|  | if (result == ZX_OK) | 
|  | result = vmar_handle->make(fbl::move(vmar_dispatcher), vmar_rights); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | // 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, | 
|  | zx_vaddr_t pc, zx_vaddr_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) { | 
|  | up->RemoveHandle(arg_handle_value); | 
|  | return status; | 
|  | } | 
|  |  | 
|  | // get thread_dispatcher | 
|  | fbl::RefPtr<ThreadDispatcher> thread; | 
|  | status = up->GetDispatcherWithRights(thread_handle, ZX_RIGHT_WRITE, &thread); | 
|  | if (status != ZX_OK) { | 
|  | up->RemoveHandle(arg_handle_value); | 
|  | return status; | 
|  | } | 
|  |  | 
|  | HandleOwner arg_handle = up->RemoveHandle(arg_handle_value); | 
|  |  | 
|  | // test that the thread belongs to the starting process | 
|  | if (thread->process() != process.get()) | 
|  | return ZX_ERR_ACCESS_DENIED; | 
|  | if (!arg_handle) | 
|  | return ZX_ERR_BAD_HANDLE; | 
|  | if (!arg_handle->HasRights(ZX_RIGHT_TRANSFER)) | 
|  | return ZX_ERR_ACCESS_DENIED; | 
|  |  | 
|  | auto arg_nhv = process->MapHandleToValue(arg_handle); | 
|  | process->AddHandle(fbl::move(arg_handle)); | 
|  |  | 
|  | status = thread->Start(pc, sp, static_cast<uintptr_t>(arg_nhv), | 
|  | arg2, /* initial_thread */ true); | 
|  | if (status != ZX_OK) { | 
|  | // Remove |arg_handle| from the process that failed to start. | 
|  | process->RemoveHandle(arg_nhv); | 
|  | 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(int64_t retcode) { | 
|  | LTRACEF("retcode %" PRId64 "\n", retcode); | 
|  | ProcessDispatcher::GetCurrent()->Exit(retcode); | 
|  | } | 
|  |  | 
|  | zx_status_t sys_process_read_memory(zx_handle_t proc, zx_vaddr_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; | 
|  |  | 
|  | // 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; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | uint64_t offset = vaddr - vm_mapping->base() + vm_mapping->object_offset(); | 
|  | // TODO(ZX-1631): While this limits reading to the mapped address space of | 
|  | // this VMO, it should be reading from multiple VMOs, not a single one. | 
|  | // Additionally, it is racy with the mapping going away. | 
|  | len = MIN(len, vm_mapping->size() - (vaddr - vm_mapping->base())); | 
|  | zx_status_t st = vmo->ReadUser(_buffer, offset, len); | 
|  |  | 
|  | if (st == ZX_OK) { | 
|  | zx_status_t status = _actual.copy_to_user(static_cast<size_t>(len)); | 
|  | if (status != ZX_OK) | 
|  | return status; | 
|  | } | 
|  | return st; | 
|  | } | 
|  |  | 
|  | zx_status_t sys_process_write_memory(zx_handle_t proc, zx_vaddr_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(); | 
|  | // TODO(ZX-1631): While this limits writing to the mapped address space of | 
|  | // this VMO, it should be writing to multiple VMOs, not a single one. | 
|  | // Additionally, it is racy with the mapping going away. | 
|  | len = MIN(len, vm_mapping->size() - (vaddr - vm_mapping->base())); | 
|  | zx_status_t st = vmo->WriteUser(_buffer, offset, len); | 
|  |  | 
|  | if (st == ZX_OK) { | 
|  | zx_status_t status = _actual.copy_to_user(static_cast<size_t>(len)); | 
|  | if (status != ZX_OK) | 
|  | return status; | 
|  | } | 
|  | 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_handle* 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_MANAGE_JOB, &parent); | 
|  | if (status != ZX_OK) { | 
|  | // Try again, but with the WRITE right. | 
|  | // TODO(kulakowski) Remove this when all callers are using MANAGE_JOB. | 
|  | 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) | 
|  | status = out->make(fbl::move(job), rights); | 
|  | return status; | 
|  | } | 
|  |  | 
|  | 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()); | 
|  | } |