| // Copyright 2019 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/hermetic-compute/hermetic-compute.h> |
| |
| #include <climits> |
| #include <elfload/elfload.h> |
| #include <lib/zx/thread.h> |
| #include <lib/zx/vmar.h> |
| #include <lib/zx/vmo.h> |
| #include <zircon/assert.h> |
| #include <zircon/status.h> |
| |
| namespace { |
| |
| constexpr uintptr_t PageTrunc(uintptr_t addr) { return addr & -PAGE_SIZE; } |
| |
| constexpr size_t PageRound(size_t size) { return (size + PAGE_SIZE - 1) & -PAGE_SIZE; } |
| |
| // Use huge guards so everything is far away from everything else. |
| constexpr size_t kGuardSize = size_t{1} << 30; // 1G |
| static_assert(kGuardSize % PAGE_SIZE == 0); |
| |
| // Make space for a module to use up to this much address space. |
| constexpr size_t kMaxModuleSize = kGuardSize; |
| |
| zx_status_t MapWithGuards(const zx::vmar& vmar, const zx::vmo& vmo, uint64_t vmo_offset, |
| size_t size, zx_vm_option_t perm, uintptr_t* out_address) { |
| // Create a VMAR to contain the mapping and the guard pages around it. |
| // Once the VMAR handle goes out of scope, these mappings cannot change |
| // (except by unmapping the whole region). |
| zx::vmar child_vmar; |
| uintptr_t base; |
| zx_status_t status = vmar.allocate(0, size + (2 * kGuardSize), |
| ((perm & ZX_VM_PERM_READ) ? ZX_VM_CAN_MAP_READ : 0) | |
| ((perm & ZX_VM_PERM_WRITE) ? ZX_VM_CAN_MAP_WRITE : 0) | |
| ((perm & ZX_VM_PERM_EXECUTE) ? ZX_VM_CAN_MAP_EXECUTE : 0) | |
| ZX_VM_CAN_MAP_SPECIFIC, |
| &child_vmar, &base); |
| if (status == ZX_OK) { |
| status = child_vmar.map(kGuardSize, vmo, vmo_offset, size, perm | ZX_VM_SPECIFIC, out_address); |
| } |
| return status; |
| } |
| |
| constexpr size_t kMaxPhdrs = 16; |
| |
| constexpr const char kThreadName[] = "hermetic-compute"; |
| |
| zx_status_t CreateThread(const zx::process& process, zx::thread* out_thread) { |
| return zx::thread::create(process, kThreadName, sizeof(kThreadName) - 1, 0, out_thread); |
| } |
| |
| } // namespace |
| |
| zx_status_t HermeticComputeProcess::LoadElf(const zx::vmo& vmo, uintptr_t* out_base, |
| uintptr_t* out_entry, size_t* out_stack_size) { |
| elf_load_header_t header; |
| uintptr_t phoff; |
| zx_status_t status = elf_load_prepare(vmo.get(), nullptr, 0, &header, &phoff); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| if (header.e_phnum > kMaxPhdrs) { |
| return ERR_ELF_BAD_FORMAT; |
| } |
| |
| elf_phdr_t phdrs[kMaxPhdrs]; |
| status = elf_load_read_phdrs(vmo.get(), phdrs, phoff, header.e_phnum); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| uint32_t max_perm = 0; |
| for (uint_fast16_t i = 0; i < header.e_phnum; ++i) { |
| switch (phdrs[i].p_type) { |
| case PT_GNU_STACK: |
| if (out_stack_size) { |
| // The module must have a PT_GNU_STACK header indicating |
| // how much stack it needs (which can be zero). |
| if (phdrs[i].p_filesz != 0 || phdrs[i].p_flags != (PF_R | PF_W)) { |
| return ERR_ELF_BAD_FORMAT; |
| } |
| *out_stack_size = phdrs[i].p_memsz; |
| out_stack_size = nullptr; |
| } |
| break; |
| case PT_LOAD: |
| // The first segment should start at zero (no prelinking here!). |
| // elfload checks other aspects of the addresses and sizes. |
| if (max_perm == 0 && PageTrunc(phdrs[i].p_vaddr) != 0) { |
| return ERR_ELF_BAD_FORMAT; |
| } |
| max_perm |= phdrs[i].p_flags; |
| break; |
| } |
| } |
| if (out_stack_size || // Never saw PT_GNU_STACK. |
| (max_perm & ~(PF_R | PF_W | PF_X))) { |
| return ERR_ELF_BAD_FORMAT; |
| } |
| |
| // Allocate a very large VMAR to put big guard regions around the module. |
| zx::vmar guard_vmar; |
| uintptr_t base; |
| status = vmar_.allocate( |
| 0, kMaxModuleSize + (2 * kGuardSize), |
| ((max_perm & PF_R) ? ZX_VM_CAN_MAP_READ : 0) | ((max_perm & PF_W) ? ZX_VM_CAN_MAP_WRITE : 0) | |
| ((max_perm & PF_X) ? ZX_VM_CAN_MAP_EXECUTE : 0) | ZX_VM_CAN_MAP_SPECIFIC, |
| &guard_vmar, &base); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| // Now allocate a large VMAR between guard regions inside which the |
| // code will go at a random location. |
| zx::vmar code_vmar; |
| status = guard_vmar.allocate(kGuardSize, kMaxModuleSize, |
| ((max_perm & PF_R) ? ZX_VM_CAN_MAP_READ : 0) | |
| ((max_perm & PF_W) ? ZX_VM_CAN_MAP_WRITE : 0) | |
| ((max_perm & PF_X) ? ZX_VM_CAN_MAP_EXECUTE : 0) | ZX_VM_SPECIFIC, |
| &code_vmar, &base); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| // It's no longer possible to put other things into the guarded region. |
| guard_vmar.reset(); |
| |
| // Now map the segments inside the code VMAR. elfload creates another |
| // right-sized VMAR to contain the segments. The location of that VMAR |
| // within |code_vmar| is random. We don't hold onto the inner VMAR |
| // handle, so the segment mappings can't be modified. |
| return elf_load_map_segments(code_vmar.get(), &header, phdrs, vmo.get(), nullptr, out_base, |
| out_entry); |
| } |
| |
| zx_status_t HermeticComputeProcess::LoadStack(size_t* size, zx::vmo* out_vmo, |
| uintptr_t* out_stack_base) { |
| *size = PageRound(*size); |
| zx_status_t status = zx::vmo::create(*size, 0, out_vmo); |
| if (status == ZX_OK) { |
| status = MapWithGuards(vmar_, *out_vmo, 0, *size, ZX_VM_PERM_READ | ZX_VM_PERM_WRITE, |
| out_stack_base); |
| } |
| return status; |
| } |
| |
| zx_status_t HermeticComputeProcess::Start(uintptr_t entry, uintptr_t sp, zx::handle arg1, |
| uintptr_t arg2) { |
| zx::thread thread; |
| zx_status_t status = CreateThread(process_, &thread); |
| if (status == ZX_OK) { |
| status = process_.start(thread, entry, sp, std::move(arg1), arg2); |
| } |
| return status; |
| } |
| |
| zx_status_t HermeticComputeProcess::Start(zx::handle handle, zx::thread* out_thread, |
| zx::suspend_token* out_token) { |
| zx_status_t status = CreateThread(process_, out_thread); |
| if (status != ZX_OK) { |
| return status; |
| } |
| |
| status = out_thread->suspend(out_token); |
| if (status == ZX_OK) { |
| // The initial register values are all zeros (except maybe the handle). |
| // They'll be changed before the thread ever runs in user mode. |
| status = process_.start(*out_thread, 0, 0, std::move(handle), 0); |
| } |
| |
| if (status == ZX_OK) { |
| // It's started and will immediately suspend itself before ever |
| // reaching user mode, but we have to wait to ensure it's actually |
| // officially suspended before we can access its user register state. |
| zx_signals_t signals; |
| status = out_thread->wait_one(ZX_THREAD_SUSPENDED | ZX_THREAD_TERMINATED, zx::time::infinite(), |
| &signals); |
| if (status == ZX_OK) { |
| if (signals & ZX_THREAD_TERMINATED) { |
| status = ZX_ERR_PEER_CLOSED; |
| } else { |
| ZX_DEBUG_ASSERT(signals & ZX_THREAD_SUSPENDED); |
| } |
| } |
| } |
| |
| if (status != ZX_OK) { |
| out_thread->kill(); |
| out_thread->reset(); |
| out_token->reset(); |
| } |
| |
| return status; |
| } |
| |
| zx_status_t HermeticComputeProcess::Wait(int64_t* result, zx::time deadline) { |
| zx_signals_t signals; |
| zx_status_t status = process_.wait_one(ZX_PROCESS_TERMINATED, deadline, &signals); |
| if (status == ZX_OK) { |
| ZX_DEBUG_ASSERT(signals == ZX_PROCESS_TERMINATED); |
| if (result) { |
| zx_info_process_t info; |
| status = process_.get_info(ZX_INFO_PROCESS, &info, sizeof(info), nullptr, nullptr); |
| if (status == ZX_OK) { |
| ZX_DEBUG_ASSERT(info.exited); |
| *result = info.return_code; |
| } |
| } |
| } |
| return status; |
| } |
| |
| zx_status_t HermeticComputeProcess::Map(const zx::vmo& vmo, uint64_t vmo_offset, size_t size, |
| bool writable, uintptr_t* ptr) { |
| return MapWithGuards(vmar(), vmo, vmo_offset, size, |
| ZX_VM_PERM_READ | (writable ? ZX_VM_PERM_WRITE : 0), ptr); |
| } |