blob: 53c49e81fb04b8cf4f2f1e99b70557b2e84964b7 [file] [log] [blame]
// 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);
}