blob: f7401d0481e0f4d48ca26bca5adf1eb8aa4cc2d9 [file] [log] [blame]
// Copyright 2017 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 <mini-process/mini-process.h>
#include <dlfcn.h>
#include <elfload/elfload.h>
#include <lib/elf-psabi/sp.h>
#include <stdint.h>
#include <zircon/process.h>
#include <zircon/processargs.h>
#include <zircon/status.h>
#include <zircon/syscalls.h>
#include "subprocess.h"
static void* get_syscall_addr(const void* syscall_fn, uintptr_t vdso_base) {
Dl_info dl_info;
if (dladdr(syscall_fn, &dl_info) == 0)
return 0;
return (void*)(vdso_base + ((uintptr_t)dl_info.dli_saddr - (uintptr_t)dl_info.dli_fbase));
}
static zx_status_t write_ctx_message(
zx_handle_t channel, uintptr_t vdso_base, zx_handle_t transferred_handle) {
minip_ctx_t ctx = {
.handle_close = get_syscall_addr(&zx_handle_close, vdso_base),
.object_wait_one = get_syscall_addr(&zx_object_wait_one, vdso_base),
.object_signal = get_syscall_addr(&zx_object_signal, vdso_base),
.event_create = get_syscall_addr(&zx_event_create, vdso_base),
.profile_create = get_syscall_addr(&zx_profile_create, vdso_base),
.channel_create = get_syscall_addr(&zx_channel_create, vdso_base),
.channel_read = get_syscall_addr(&zx_channel_read, vdso_base),
.channel_write = get_syscall_addr(&zx_channel_write, vdso_base),
.process_exit = get_syscall_addr(&zx_process_exit, vdso_base),
.object_get_info = get_syscall_addr(&zx_object_get_info, vdso_base),
.port_create = get_syscall_addr(&zx_port_create, vdso_base),
.pager_create = get_syscall_addr(&zx_pager_create, vdso_base),
.pager_create_vmo = get_syscall_addr(&zx_pager_create_vmo, vdso_base),
.vmo_contiguous_create = get_syscall_addr(&zx_vmo_create_contiguous, vdso_base),
.vmo_physical_create = get_syscall_addr(&zx_vmo_create_physical, vdso_base)
};
return zx_channel_write(channel, 0u, &ctx, sizeof(ctx), &transferred_handle, 1u);
}
// Sets up a VMO on the given VMAR which contains the mini process code and space for a stack.
zx_status_t mini_process_load_stack(zx_handle_t vmar, bool with_code,
zx_vaddr_t* stack_base, zx_vaddr_t* sp) {
// Allocate a single VMO for the child. It doubles as the stack on the top and
// as the executable code (minipr_thread_loop()) at the bottom. In theory, actual
// stack usage is minimal, like 160 bytes or less.
uint64_t stack_size = 16 * 1024u;
zx_handle_t stack_vmo = ZX_HANDLE_INVALID;
zx_status_t status = zx_vmo_create(stack_size, 0, &stack_vmo);
if (status != ZX_OK)
return status;
// Try to set the name, but ignore any errors since it's purely for
// debugging and diagnostics.
static const char vmo_name[] = "mini-process:stack";
zx_object_set_property(stack_vmo, ZX_PROP_NAME, vmo_name, sizeof(vmo_name));
zx_vm_option_t perms = ZX_VM_PERM_READ | ZX_VM_PERM_WRITE;
if (with_code) {
// We assume that the code to execute is less than kSizeLimit bytes.
const uint32_t kSizeLimit = 2000;
status = zx_vmo_write(stack_vmo, &minipr_thread_loop, 0u, kSizeLimit);
if (status != ZX_OK)
goto exit;
// TODO(mdempsky): Separate minipr_thread_loop and stack into
// separate VMOs to enforce W^X.
status = zx_vmo_replace_as_executable(stack_vmo, ZX_HANDLE_INVALID, &stack_vmo);
if (status != ZX_OK)
goto exit;
perms |= ZX_VM_PERM_EXECUTE;
}
status = zx_vmar_map(vmar, perms, 0, stack_vmo, 0, stack_size, stack_base);
if (status != ZX_OK)
goto exit;
// Compute a valid starting SP for the machine's ABI.
*sp = compute_initial_stack_pointer(*stack_base, stack_size);
exit:
// Close the VMO handle no matter what; if we failed we want to release it, and if
// we succeeded the VMAR retains a reference to it so we don't need a handle.
zx_handle_close(stack_vmo);
return status;
}
zx_status_t mini_process_load_vdso(zx_handle_t process, zx_handle_t vmar,
uintptr_t* base, uintptr_t* entry) {
// This is not thread-safe. It steals the startup handle, so it's not
// compatible with also using launchpad (which also needs to steal the
// startup handle).
static zx_handle_t vdso_vmo = ZX_HANDLE_INVALID;
if (vdso_vmo == ZX_HANDLE_INVALID) {
vdso_vmo = zx_take_startup_handle(PA_HND(PA_VMO_VDSO, 0));
if (vdso_vmo == ZX_HANDLE_INVALID) {
return ZX_ERR_INTERNAL;
}
}
elf_load_header_t header;
uintptr_t phoff;
zx_status_t status = elf_load_prepare(vdso_vmo, NULL, 0,
&header, &phoff);
if (status == ZX_OK) {
elf_phdr_t phdrs[header.e_phnum];
status = elf_load_read_phdrs(vdso_vmo, phdrs, phoff,
header.e_phnum);
if (status == ZX_OK) {
status = elf_load_map_segments(vmar, &header, phdrs, vdso_vmo,
NULL, base, entry);
}
}
return status;
}
zx_status_t start_mini_process_etc(zx_handle_t process, zx_handle_t thread,
zx_handle_t vmar,
zx_handle_t transferred_handle,
zx_handle_t* control_channel) {
zx_vaddr_t stack_base = 0;
zx_vaddr_t sp = 0;
zx_status_t status = mini_process_load_stack(vmar, true, &stack_base, &sp);
if (status != ZX_OK)
return status;
zx_handle_t chn[2] = { ZX_HANDLE_INVALID, ZX_HANDLE_INVALID };
if (!control_channel) {
// Simple mode /////////////////////////////////////////////////////////////
// Don't map the VDSO, so the only thing the mini-process can do is busy-loop.
// The handle sent to the process is just the caller's handle.
status = zx_process_start(process, thread, stack_base, sp, transferred_handle, 0);
transferred_handle = ZX_HANDLE_INVALID;
} else {
// Complex mode ////////////////////////////////////////////////////////////
// The mini-process is going to run a simple request-response over a channel
// So we need to:
// 1- map the VDSO in the child process, without launchpad.
// 2- create a channel and give one end to the child process.
// 3- send a message with the rest of the syscall function addresses.
// 4- wait for reply.
status = zx_channel_create(0u, &chn[0], &chn[1]);
if (status != ZX_OK)
goto exit;
uintptr_t vdso_base = 0;
status = mini_process_load_vdso(process, vmar, &vdso_base, NULL);
if (status != ZX_OK)
goto exit;
status = write_ctx_message(chn[0], vdso_base, transferred_handle);
transferred_handle = ZX_HANDLE_INVALID;
if (status != ZX_OK)
goto exit;
uintptr_t channel_read = (uintptr_t)get_syscall_addr(&zx_channel_read, vdso_base);
status = zx_process_start(process, thread, stack_base, sp, chn[1], channel_read);
chn[1] = ZX_HANDLE_INVALID;
if (status != ZX_OK)
goto exit;
uint32_t observed;
status = zx_object_wait_one(chn[0],
ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED, ZX_TIME_INFINITE, &observed);
if (observed & ZX_CHANNEL_PEER_CLOSED) {
// the child process died prematurely.
status = ZX_ERR_UNAVAILABLE;
goto exit;
}
if (observed & ZX_CHANNEL_READABLE) {
uint32_t ack[2];
uint32_t actual_handles;
uint32_t actual_bytes;
status = zx_channel_read(
chn[0], 0u, ack, NULL, sizeof(uint32_t) * 2, 0u, &actual_bytes, &actual_handles);
}
*control_channel = chn[0];
chn[0] = ZX_HANDLE_INVALID;
}
exit:
if (transferred_handle != ZX_HANDLE_INVALID)
zx_handle_close(transferred_handle);
if (chn[0] != ZX_HANDLE_INVALID)
zx_handle_close(chn[0]);
if (chn[1] != ZX_HANDLE_INVALID)
zx_handle_close(chn[1]);
return status;
}
zx_status_t mini_process_cmd_send(zx_handle_t cntrl_channel, uint32_t what) {
minip_cmd_t cmd = {
.what = what,
.status = ZX_OK
};
return zx_channel_write(cntrl_channel, 0, &cmd, sizeof(cmd), NULL, 0);
}
zx_status_t mini_process_cmd_read_reply(zx_handle_t cntrl_channel,
zx_handle_t* handle) {
zx_status_t status = zx_object_wait_one(
cntrl_channel, ZX_CHANNEL_READABLE | ZX_CHANNEL_PEER_CLOSED,
ZX_TIME_INFINITE, NULL);
if (status != ZX_OK)
return status;
minip_cmd_t reply;
uint32_t handle_count = handle ? 1 : 0;
uint32_t actual_bytes = 0;
uint32_t actual_handles = 0;
status = zx_channel_read(cntrl_channel, 0, &reply, handle, sizeof(reply),
handle_count, &actual_bytes, &actual_handles);
if (status != ZX_OK)
return status;
return reply.status;
}
zx_status_t mini_process_cmd(zx_handle_t cntrl_channel, uint32_t what, zx_handle_t* handle) {
zx_status_t status = mini_process_cmd_send(cntrl_channel, what);
if (status != ZX_OK)
return status;
return mini_process_cmd_read_reply(cntrl_channel, handle);
}
zx_status_t start_mini_process(zx_handle_t job, zx_handle_t transferred_handle,
zx_handle_t* process, zx_handle_t* thread) {
*process = ZX_HANDLE_INVALID;
zx_handle_t vmar = ZX_HANDLE_INVALID;
zx_handle_t channel = ZX_HANDLE_INVALID;
zx_status_t status = zx_process_create(job, "minipr", 6u, 0u, process, &vmar);
if (status != ZX_OK)
goto exit;
*thread = ZX_HANDLE_INVALID;
status = zx_thread_create(*process, "minith", 6u, 0, thread);
if (status != ZX_OK)
goto exit;
status = start_mini_process_etc(*process, *thread, vmar, transferred_handle, &channel);
transferred_handle = ZX_HANDLE_INVALID; // The transferred_handle gets consumed.
exit:
if (status != ZX_OK) {
if (transferred_handle != ZX_HANDLE_INVALID)
zx_handle_close(transferred_handle);
if (*process != ZX_HANDLE_INVALID)
zx_handle_close(*process);
if (*thread != ZX_HANDLE_INVALID)
zx_handle_close(*thread);
}
if (channel != ZX_HANDLE_INVALID)
zx_handle_close(channel);
return status;
}
zx_status_t start_mini_process_thread(zx_handle_t thread, zx_handle_t vmar) {
zx_vaddr_t stack_base = 0;
zx_vaddr_t sp = 0;
zx_status_t status = mini_process_load_stack(vmar, true, &stack_base, &sp);
if (status != ZX_OK)
return status;
return zx_thread_start(thread, stack_base, sp, 0, 0);
}