blob: c8dfb4a50a3d2fb56ec033e5b2a808544760654a [file] [log] [blame]
// Copyright 2016 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 <launchpad/launchpad.h>
#include <launchpad/vmo.h>
#include "elf.h"
#include <assert.h>
#include <ldmsg/ldmsg.h>
#include <lib/elf-psabi/sp.h>
#include <lib/fdio/io.h>
#include <lib/zircon-internal/default_stack_size.h>
#include <stdatomic.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/param.h>
#include <threads.h>
#include <zircon/assert.h>
#include <zircon/dlfcn.h>
#include <zircon/process.h>
#include <zircon/processargs.h>
#include <zircon/syscalls.h>
enum special_handles {
HND_LDSVC_LOADER,
HND_EXEC_VMO,
HND_SEGMENTS_VMAR,
HND_SPECIAL_COUNT
};
struct launchpad {
uint32_t argc;
uint32_t envc;
uint32_t namec;
char* args;
size_t args_len;
char* env;
size_t env_len;
char* names;
size_t names_len;
size_t num_script_args;
char* script_args;
size_t script_args_len;
zx_handle_t* handles;
uint32_t* handles_info;
size_t handle_count;
size_t handle_alloc;
const char* errmsg;
zx_status_t error;
zx_vaddr_t entry;
zx_vaddr_t base;
zx_vaddr_t vdso_base;
size_t stack_size;
bool set_stack_size;
zx_handle_t special_handles[HND_SPECIAL_COUNT];
bool loader_message;
zx_handle_t reserve_vmar;
bool fresh_process;
};
// Returned when calloc() fails on create, so callers
// can still call all the various add handles functions
// which will discard the handles, etc, etc.
static launchpad_t invalid_launchpad = {
.errmsg = "create: could not allocate launchpad_t",
.error = ZX_ERR_NO_MEMORY,
};
static zx_status_t lp_error(launchpad_t* lp, zx_status_t error, const char* msg) {
if (lp->error == ZX_OK) {
lp->error = error;
lp->errmsg = msg;
}
return lp->error;
}
zx_status_t launchpad_get_status(launchpad_t* lp) {
return lp->error;
}
void launchpad_abort(launchpad_t* lp, zx_status_t error, const char* msg) {
lp_error(lp, (error < 0) ? error : ZX_ERR_INTERNAL, msg);
}
const char* launchpad_error_message(launchpad_t* lp) {
return lp->errmsg;
}
#define HND_LOADER_COUNT 3
// We always install the process handle as the first in the message.
#define lp_proc(lp) ((lp)->handles[0])
// We always install the vmar handle as the second in the message.
#define lp_vmar(lp) ((lp)->handles[1])
void launchpad_destroy(launchpad_t* lp) {
if (lp == &invalid_launchpad)
return;
zx_handle_close(lp->reserve_vmar);
zx_handle_close_many(lp->special_handles, HND_SPECIAL_COUNT);
zx_handle_close_many(lp->handles, lp->handle_count);
free(lp->handles);
free(lp->handles_info);
free(lp->script_args);
free(lp->args);
free(lp->env);
free(lp->names);
free(lp);
}
// Create a new launchpad for a given existing process handle and
// its root VMAR handle. On success, the launchpad takes ownership
// of both handles.
static zx_status_t launchpad_create_with_process(zx_handle_t proc,
zx_handle_t vmar,
launchpad_t** result) {
launchpad_t* lp = calloc(1, sizeof(*lp));
if (lp == NULL) {
lp = &invalid_launchpad;
} else {
lp->errmsg = "no error";
}
if (launchpad_add_handle(lp, proc, PA_PROC_SELF) == ZX_OK) {
// If the process has an existing vDSO mapping, record it for
// use by launchpad_start_extra.
zx_status_t status = zx_object_get_property(
proc, ZX_PROP_PROCESS_VDSO_BASE_ADDRESS,
&lp->vdso_base, sizeof(lp->vdso_base));
if (status != ZX_OK)
lp_error(lp, status,
"create: cannot get ZX_PROP_PROCESS_VDSO_BASE_ADDRESS");
}
launchpad_add_handle(lp, vmar, PA_VMAR_ROOT);
*result = lp;
return lp->error;
}
// Create a new process and a launchpad that will set it up.
zx_status_t launchpad_create_with_jobs(zx_handle_t creation_job, zx_handle_t transferred_job,
const char* name, launchpad_t** result) {
uint32_t name_len = strlen(name);
zx_handle_t proc = ZX_HANDLE_INVALID;
zx_handle_t vmar = ZX_HANDLE_INVALID;
zx_status_t status = zx_process_create(creation_job, name, name_len, 0, &proc, &vmar);
launchpad_t* lp;
if (launchpad_create_with_process(proc, vmar, &lp) == ZX_OK)
lp->fresh_process = true;
if (status < 0)
lp_error(lp, status, "create: zx_process_create() failed");
if (transferred_job != ZX_HANDLE_INVALID) {
launchpad_add_handle(lp, transferred_job, PA_JOB_DEFAULT);
}
*result = lp;
return lp->error;
}
zx_status_t launchpad_create(zx_handle_t job,
const char* name, launchpad_t** result) {
if (job == ZX_HANDLE_INVALID)
job = zx_job_default();
zx_handle_t xjob = ZX_HANDLE_INVALID;
zx_handle_duplicate(job, ZX_RIGHT_SAME_RIGHTS, &xjob);
return launchpad_create_with_jobs(job, xjob, name, result);
}
zx_handle_t launchpad_get_process_handle(launchpad_t* lp) {
return lp_proc(lp);
}
zx_handle_t launchpad_get_root_vmar_handle(launchpad_t* lp) {
return lp_vmar(lp);
}
static zx_status_t build_stringtable(launchpad_t* lp,
int count, const char* const* item,
size_t* total_out, char** out) {
if (lp->error)
return lp->error;
if (count < 0)
return lp_error(lp, ZX_ERR_INVALID_ARGS, "negative string array count");
size_t total = 0;
for (int i = 0; i < count; ++i)
total += strlen(item[i]) + 1;
char* buffer = NULL;
if (total > 0) {
buffer = malloc(total);
if (buffer == NULL)
return lp_error(lp, ZX_ERR_NO_MEMORY, "out of memory for string array");
char* p = buffer;
for (int i = 0; i < count; ++i)
p = stpcpy(p, item[i]) + 1;
if ((size_t) (p - buffer) != total) {
// The strings changed in parallel. Not kosher!
free(buffer);
return lp_error(lp, ZX_ERR_INVALID_ARGS, "string array modified during use");
}
}
*total_out = total;
*out = buffer;
return ZX_OK;
}
zx_status_t launchpad_set_args(launchpad_t* lp,
int argc, const char* const* argv) {
size_t total;
char* buffer;
zx_status_t r = build_stringtable(lp, argc, argv, &total, &buffer);
if (r < 0)
return r;
free(lp->args);
lp->argc = argc;
lp->args = buffer;
lp->args_len = total;
return ZX_OK;
}
zx_status_t launchpad_set_nametable(launchpad_t* lp,
size_t count, const char* const* names) {
size_t total;
char* buffer;
zx_status_t r = build_stringtable(lp, count, names, &total, &buffer);
if (r < 0)
return r;
free(lp->names);
lp->namec = count;
lp->names = buffer;
lp->names_len = total;
return ZX_OK;
}
zx_status_t launchpad_set_environ(launchpad_t* lp, const char* const* envp) {
uint32_t count = 0;
if (envp != NULL) {
for (const char* const* ep = envp; *ep != NULL; ++ep) {
++count;
}
}
size_t total;
char* buffer;
zx_status_t r = build_stringtable(lp, count, envp, &total, &buffer);
if (r < 0)
return r;
free(lp->env);
lp->envc = count;
lp->env = buffer;
lp->env_len = total;
return ZX_OK;
}
static zx_status_t more_handles(launchpad_t* lp, size_t n) {
if (lp->error)
return lp->error;
if (ZX_CHANNEL_MAX_MSG_HANDLES - lp->handle_count < n)
return lp_error(lp, ZX_ERR_NO_MEMORY, "too many handles for handle table");
if (lp->handle_alloc - lp->handle_count < n) {
size_t alloc = lp->handle_alloc == 0 ? 8 : lp->handle_alloc * 2;
while (alloc - lp->handle_count < n)
alloc <<= 1;
zx_handle_t* handles = realloc(lp->handles,
alloc * sizeof(handles[0]));
if (handles == NULL)
return lp_error(lp, ZX_ERR_NO_MEMORY, "out of memory for handle table");
lp->handles = handles;
uint32_t* info = realloc(lp->handles_info, alloc * sizeof(info[0]));
if (info == NULL)
return lp_error(lp, ZX_ERR_NO_MEMORY, "out of memory for handle table");
lp->handles_info = info;
lp->handle_alloc = alloc;
}
return ZX_OK;
}
zx_status_t launchpad_add_handle(launchpad_t* lp, zx_handle_t h, uint32_t id) {
if (h == ZX_HANDLE_INVALID)
return lp_error(lp, ZX_ERR_BAD_HANDLE, "added invalid handle");
zx_status_t status = more_handles(lp, 1);
if (status == ZX_OK) {
lp->handles[lp->handle_count] = h;
lp->handles_info[lp->handle_count] = id;
++lp->handle_count;
} else {
zx_handle_close(h);
}
return status;
}
zx_status_t launchpad_add_handles(launchpad_t* lp, size_t n,
const zx_handle_t h[],
const uint32_t id[]) {
zx_status_t status = more_handles(lp, n);
if (status == ZX_OK) {
memcpy(&lp->handles[lp->handle_count], h, n * sizeof(h[0]));
memcpy(&lp->handles_info[lp->handle_count], id, n * sizeof(id[0]));
lp->handle_count += n;
for (size_t i = 0; i < n; i++) {
if (h[i] == ZX_HANDLE_INVALID) {
return lp_error(lp, ZX_ERR_BAD_HANDLE, "added invalid handle");
}
}
} else {
for (size_t i = 0; i < n; i++) {
zx_handle_close(h[i]);
}
}
return status;
}
static void check_elf_stack_size(launchpad_t* lp, elf_load_info_t* elf) {
size_t elf_stack_size = elf_load_get_stack_size(elf);
if (elf_stack_size > 0)
launchpad_set_stack_size(lp, elf_stack_size);
}
zx_status_t launchpad_elf_load_basic(launchpad_t* lp, zx_handle_t vmo) {
if (vmo == ZX_HANDLE_INVALID)
return lp_error(lp, ZX_ERR_INVALID_ARGS, "elf_load: invalid vmo");
if (lp->error)
goto done;
elf_load_info_t* elf;
zx_status_t status;
if ((status = elf_load_start(vmo, NULL, 0, &elf)))
lp_error(lp, status, "elf_load: elf_load_start() failed");
zx_handle_t segments_vmar;
if ((status = elf_load_finish(lp_vmar(lp), elf, vmo,
&segments_vmar, &lp->base, &lp->entry)))
lp_error(lp, status, "elf_load: elf_load_finish() failed");
check_elf_stack_size(lp, elf);
elf_load_destroy(elf);
if (status == ZX_OK) {
lp->loader_message = false;
launchpad_add_handle(lp, segments_vmar,
PA_HND(PA_VMAR_LOADED, 0));
}
done:
zx_handle_close(vmo);
return lp->error;
}
zx_status_t launchpad_elf_load_extra(launchpad_t* lp, zx_handle_t vmo,
zx_vaddr_t* base, zx_vaddr_t* entry) {
if (lp->error)
return lp->error;
if (vmo == ZX_HANDLE_INVALID)
return lp_error(lp, ZX_ERR_INVALID_ARGS, "elf_load_extra: invalid vmo");
elf_load_info_t* elf;
zx_status_t status;
if ((status = elf_load_start(vmo, NULL, 0, &elf)))
lp_error(lp, status, "elf_load_extra: elf_load_start() failed");
if ((status = elf_load_finish(lp_vmar(lp), elf, vmo, NULL, base, entry)))
lp_error(lp, status, "elf_load_extra: elf_load_finish() failed");
elf_load_destroy(elf);
return lp->error;
}
#define LOADER_SVC_MSG_MAX 1024
static zx_status_t loader_svc_rpc(zx_handle_t loader_svc, uint32_t ordinal,
const void* data, size_t len, zx_handle_t* out) {
static _Atomic zx_txid_t next_txid;
ldmsg_req_t req;
memset(&req.header, 0, sizeof(req.header));
req.header.ordinal = ordinal;
size_t req_len;
zx_status_t status = ldmsg_req_encode(&req, &req_len, data, len);
if (status != ZX_OK)
return status;
req.header.txid = atomic_fetch_add(&next_txid, 1);
ldmsg_rsp_t rsp;
memset(&rsp, 0, sizeof(rsp));
zx_handle_t handle = ZX_HANDLE_INVALID;
const zx_channel_call_args_t call = {
.wr_bytes = &req,
.wr_num_bytes = req_len,
.rd_bytes = &rsp,
.rd_num_bytes = sizeof(rsp),
.rd_handles = &handle,
.rd_num_handles = 1,
};
uint32_t reply_size;
uint32_t handle_count;
status = zx_channel_call(loader_svc, 0, ZX_TIME_INFINITE, &call, &reply_size, &handle_count);
if (status != ZX_OK)
return status;
// Check for protocol violations.
if (reply_size != ldmsg_rsp_get_size(&rsp)) {
protocol_violation:
zx_handle_close(handle);
return ZX_ERR_BAD_STATE;
}
if (rsp.header.ordinal != ordinal)
goto protocol_violation;
if (rsp.rv != ZX_OK) {
if (handle != ZX_HANDLE_INVALID)
goto protocol_violation;
if (rsp.rv > 0)
goto protocol_violation;
*out = ZX_HANDLE_INVALID;
} else {
*out = handle_count ? handle : ZX_HANDLE_INVALID;
}
return rsp.rv;
}
static zx_status_t setup_loader_svc(launchpad_t* lp) {
if (lp->special_handles[HND_LDSVC_LOADER] != ZX_HANDLE_INVALID)
return ZX_OK;
zx_handle_t loader_svc;
zx_status_t status = dl_clone_loader_service(&loader_svc);
if (status < 0)
return status;
lp->special_handles[HND_LDSVC_LOADER] = loader_svc;
return ZX_OK;
}
// Reserve roughly the low half of the address space, so the new
// process can use sanitizers that need to allocate shadow memory there.
// The reservation VMAR is kept around just long enough to make sure all
// the initial allocations (mapping in the initial ELF objects, and
// allocating the initial stack) stay out of this area, and then destroyed.
// The process's own allocations can then use the full address space; if
// it's using a sanitizer, it will set up its shadow memory first thing.
static zx_status_t reserve_low_address_space(launchpad_t* lp) {
if (lp->reserve_vmar != ZX_HANDLE_INVALID)
return ZX_OK;
zx_info_vmar_t info;
zx_status_t status = zx_object_get_info(lp_vmar(lp), ZX_INFO_VMAR,
&info, sizeof(info), NULL, NULL);
if (status != ZX_OK) {
return lp_error(lp, status,
"zx_object_get_info failed on child root VMAR handle");
}
uintptr_t addr;
size_t reserve_size =
(((info.base + info.len) / 2) + PAGE_SIZE - 1) & -PAGE_SIZE;
status = zx_vmar_allocate(lp_vmar(lp), ZX_VM_SPECIFIC, 0,
reserve_size - info.base,
&lp->reserve_vmar, &addr);
if (status != ZX_OK) {
return lp_error(
lp, status,
"zx_vmar_allocate failed for low address space reservation");
}
if (addr != info.base) {
return lp_error(lp, ZX_ERR_BAD_STATE,
"zx_vmar_allocate gave wrong address?!?");
}
return ZX_OK;
}
// Consumes 'vmo' on success, not on failure.
static zx_status_t handle_interp(launchpad_t* lp, zx_handle_t vmo,
const char* interp, size_t interp_len) {
zx_status_t status = setup_loader_svc(lp);
if (status != ZX_OK)
return status;
zx_handle_t interp_vmo;
status = loader_svc_rpc(
lp->special_handles[HND_LDSVC_LOADER], LDMSG_OP_LOAD_OBJECT,
interp, interp_len, &interp_vmo);
if (status != ZX_OK)
return status;
if (lp->fresh_process) {
// A fresh process using PT_INTERP might be loading a libc.so that
// supports sanitizers, so in that case (the most common case)
// keep the mappings launchpad makes out of the low address region.
status = reserve_low_address_space(lp);
if (status != ZX_OK)
return status;
}
elf_load_info_t* elf;
zx_handle_t segments_vmar;
status = elf_load_start(interp_vmo, NULL, 0, &elf);
if (status == ZX_OK) {
status = elf_load_finish(lp_vmar(lp), elf, interp_vmo,
&segments_vmar, &lp->base, &lp->entry);
elf_load_destroy(elf);
}
zx_handle_close(interp_vmo);
if (status == ZX_OK) {
if (lp->special_handles[HND_EXEC_VMO] != ZX_HANDLE_INVALID)
zx_handle_close(lp->special_handles[HND_EXEC_VMO]);
lp->special_handles[HND_EXEC_VMO] = vmo;
if (lp->special_handles[HND_SEGMENTS_VMAR] != ZX_HANDLE_INVALID)
zx_handle_close(lp->special_handles[HND_SEGMENTS_VMAR]);
lp->special_handles[HND_SEGMENTS_VMAR] = segments_vmar;
lp->loader_message = true;
}
return status;
}
static zx_status_t launchpad_elf_load_body(launchpad_t* lp, const char* hdr_buf,
size_t buf_sz, zx_handle_t vmo) {
elf_load_info_t* elf;
zx_status_t status;
if (lp->error)
goto done;
if ((status = elf_load_start(vmo, hdr_buf, buf_sz, &elf)) != ZX_OK) {
lp_error(lp, status, "elf_load: elf_load_start() failed");
} else {
char* interp;
size_t interp_len;
status = elf_load_get_interp(elf, vmo, &interp, &interp_len);
if (status != ZX_OK) {
lp_error(lp, status, "elf_load: get_interp() failed");
} else {
if (interp == NULL) {
zx_handle_t segments_vmar;
status = elf_load_finish(lp_vmar(lp), elf, vmo, &segments_vmar,
&lp->base, &lp->entry);
if (status != ZX_OK) {
lp_error(lp, status, "elf_load: elf_load_finish() failed");
} else {
// With no PT_INTERP, we obey PT_GNU_STACK.p_memsz for
// the stack size setting. With PT_INTERP, the dynamic
// linker is responsible for that.
check_elf_stack_size(lp, elf);
lp->loader_message = false;
launchpad_add_handle(
lp, segments_vmar,
PA_HND(PA_VMAR_LOADED, 0));
}
} else {
if ((status = handle_interp(lp, vmo, interp, interp_len))) {
lp_error(lp, status, "elf_load: handle_interp failed");
} else {
// handle_interp() takes ownership of vmo on success
vmo = ZX_HANDLE_INVALID;
}
free(interp);
}
}
elf_load_destroy(elf);
}
done:
if (vmo)
zx_handle_close(vmo);
return lp->error;
}
// Find the starting point of the interpreter and the interpreter
// arguments in a #! script header. Note that the input buffer (line)
// will be modified to add a NULL after the interpreter name.
static zx_status_t parse_interp_spec(char *line, char **interp_start,
size_t *interp_len, char **args_start)
{
*args_start = NULL;
// Skip the '#!' prefix
char* next_char = line + 2;
// Skip whitespace
next_char += strspn(next_char, " \t");
// No interpreter specified
if (*next_char == '\0')
return ZX_ERR_NOT_FOUND;
*interp_start = next_char;
// Skip the interpreter name
next_char += strcspn(next_char, " \t");
*interp_len = next_char - *interp_start;
if (*next_char == '\0')
return ZX_OK;
*next_char++ = '\0';
// Look for the args
next_char += strspn(next_char, " \t");
if (*next_char == '\0')
return ZX_OK;
*args_start = next_char;
return ZX_OK;
}
zx_status_t launchpad_file_load(launchpad_t* lp, zx_handle_t vmo) {
if (vmo == ZX_HANDLE_INVALID)
return lp_error(lp, ZX_ERR_INVALID_ARGS, "file_load: invalid vmo");
if (lp->script_args != NULL) {
free(lp->script_args);
lp->script_args = NULL;
}
lp->script_args_len = 0;
lp->num_script_args = 0;
size_t script_nest_level = 0;
char first_line[LP_MAX_INTERP_LINE_LEN + 1];
size_t to_read = sizeof(first_line);
size_t vmo_size;
zx_status_t status = zx_vmo_get_size(vmo, &vmo_size);
if (status != ZX_OK) {
return lp_error(lp, status, "file_load: zx_vmo_get_size() failed");
}
if (to_read > vmo_size) {
to_read = vmo_size;
}
while (1) {
// Read enough to get the interpreter specification of a script
status = zx_vmo_read(vmo, first_line, 0, to_read);
// This is not a script -- load as an ELF file
if ((status == ZX_OK)
&& (to_read < 2 || first_line[0] != '#' || first_line[1] != '!'))
break;
zx_handle_close(vmo);
if (status != ZX_OK)
return lp_error(lp, status, "file_load: zx_vmo_read() failed");
script_nest_level++;
// No point trying to read an interpreter we're not going to consider
if (script_nest_level > LP_MAX_SCRIPT_NEST_LEVEL)
return lp_error(lp, ZX_ERR_NOT_SUPPORTED,
"file_load: too many levels of script indirection");
// Normalize the line so that it is NULL-terminated
char* newline_pos = memchr(first_line, '\n', to_read);
if (newline_pos)
*newline_pos = '\0';
else if (to_read == sizeof(first_line))
return lp_error(lp, ZX_ERR_OUT_OF_RANGE,
"file_load: first line of script too long");
else
first_line[to_read] = '\0';
char* interp_start;
size_t interp_len;
char* args_start;
status = parse_interp_spec(first_line, &interp_start, &interp_len,
&args_start);
if (status != ZX_OK)
return lp_error(lp, status,
"file_load: failed to parse interpreter spec");
size_t args_len = (args_start == NULL) ? 0 : newline_pos - args_start;
// Add interpreter and args to start of lp->script_args
size_t new_args_len = interp_len + 1;
if (args_start != NULL)
new_args_len += args_len + 1;
char *new_buf = malloc(new_args_len + lp->script_args_len);
if (new_buf == NULL)
return lp_error(lp, ZX_ERR_NO_MEMORY, "file_load: out of memory");
memcpy(new_buf, interp_start, interp_len + 1);
lp->num_script_args++;
if (args_start != NULL) {
memcpy(&new_buf[interp_len + 1], args_start, args_len + 1);
lp->num_script_args++;
}
if (lp->script_args != NULL) {
memcpy(&new_buf[new_args_len], lp->script_args,
lp->script_args_len);
free(lp->script_args);
}
lp->script_args = new_buf;
lp->script_args_len += new_args_len;
// Load the interpreter into memory
status = setup_loader_svc(lp);
if (status != ZX_OK)
return lp_error(lp, status, "file_load: setup_loader_svc() failed");
status = loader_svc_rpc(lp->special_handles[HND_LDSVC_LOADER],
LDMSG_OP_LOAD_SCRIPT_INTERPRETER,
interp_start, interp_len, &vmo);
if (status != ZX_OK)
return lp_error(lp, status, "file_load: loader_svc_rpc() failed");
}
// Finally, load the interpreter itself
status = launchpad_elf_load_body(lp, first_line, to_read, vmo);
if (status != ZX_OK)
lp_error(lp, status, "file_load: failed to load ELF file");
return status;
}
zx_status_t launchpad_elf_load(launchpad_t* lp, zx_handle_t vmo) {
if (vmo == ZX_HANDLE_INVALID)
return lp_error(lp, ZX_ERR_INVALID_ARGS, "elf_load: invalid vmo");
return launchpad_elf_load_body(lp, NULL, 0, vmo);
}
static zx_handle_t vdso_vmo = ZX_HANDLE_INVALID;
static mtx_t vdso_mutex = MTX_INIT;
static void vdso_lock(void) __TA_ACQUIRE(&vdso_mutex) {
mtx_lock(&vdso_mutex);
}
static void vdso_unlock(void) __TA_RELEASE(&vdso_mutex) {
mtx_unlock(&vdso_mutex);
}
static zx_handle_t vdso_get_vmo(void) {
if (vdso_vmo == ZX_HANDLE_INVALID)
vdso_vmo = zx_take_startup_handle(PA_HND(PA_VMO_VDSO, 0));
return vdso_vmo;
}
zx_status_t launchpad_get_vdso_vmo(zx_handle_t* out) {
vdso_lock();
zx_status_t status = zx_handle_duplicate(vdso_get_vmo(),
ZX_RIGHT_SAME_RIGHTS, out);
vdso_unlock();
return status;
}
zx_handle_t launchpad_set_vdso_vmo(zx_handle_t new_vdso_vmo) {
vdso_lock();
zx_handle_t old = vdso_vmo;
vdso_vmo = new_vdso_vmo;
vdso_unlock();
return old;
}
zx_status_t launchpad_add_vdso_vmo(launchpad_t* lp) {
if (lp->error)
return lp->error;
zx_handle_t vdso;
zx_status_t status;
if ((status = launchpad_get_vdso_vmo(&vdso)) != ZX_OK)
return lp_error(lp, status, "add_vdso_vmo: get_vdso_vmo failed");
// Takes ownership of 'vdso'.
return launchpad_add_handle(lp, vdso, PA_HND(PA_VMO_VDSO, 0));
}
zx_status_t launchpad_load_vdso(launchpad_t* lp, zx_handle_t vmo) {
if (vmo != ZX_HANDLE_INVALID)
return launchpad_elf_load_extra(lp, vmo, &lp->vdso_base, NULL);
vdso_lock();
vmo = vdso_get_vmo();
zx_status_t status = launchpad_elf_load_extra(lp, vmo,
&lp->vdso_base, NULL);
vdso_unlock();
return status;
}
zx_status_t launchpad_get_entry_address(launchpad_t* lp, zx_vaddr_t* entry) {
if (lp->entry == 0)
return ZX_ERR_BAD_STATE;
*entry = lp->entry;
return ZX_OK;
}
zx_status_t launchpad_get_base_address(launchpad_t* lp, zx_vaddr_t* base) {
if (lp->base == 0)
return ZX_ERR_BAD_STATE;
*base = lp->base;
return ZX_OK;
}
bool launchpad_send_loader_message(launchpad_t* lp, bool do_send) {
bool result = lp->loader_message;
if (!lp->error)
lp->loader_message = do_send;
return result;
}
zx_handle_t launchpad_use_loader_service(launchpad_t* lp, zx_handle_t svc) {
zx_handle_t result = lp->special_handles[HND_LDSVC_LOADER];
lp->special_handles[HND_LDSVC_LOADER] = svc;
return result;
}
// Construct a load message. Fill in the header, args, and environment
// fields, and leave space for the handles, which should be filled in
// by the caller.
// TODO(mcgrathr): One day we'll have a gather variant of message_write
// and then we can send this without copying into a temporary buffer.
static zx_status_t build_message(launchpad_t* lp, size_t num_handles,
void** msg_buf, size_t* buf_size,
bool with_names) {
size_t msg_size = sizeof(zx_proc_args_t);
static_assert(sizeof(zx_proc_args_t) % sizeof(uint32_t) == 0,
"handles misaligned in load message");
msg_size += sizeof(uint32_t) * num_handles;
msg_size += lp->script_args_len;
msg_size += lp->args_len;
msg_size += lp->env_len;
msg_size += lp->names_len;
void* msg = malloc(msg_size);
if (msg == NULL)
return ZX_ERR_NO_MEMORY;
zx_proc_args_t* header = msg;
memset(header, 0, sizeof(*header));
header->protocol = ZX_PROCARGS_PROTOCOL;
header->version = ZX_PROCARGS_VERSION;
header->handle_info_off = sizeof(*header);
// Include the argument strings so the dynamic linker can use argv[0]
// in messages it prints.
header->args_off = header->handle_info_off +
sizeof(uint32_t) * num_handles;
header->args_num = lp->num_script_args + lp->argc;
if (header->args_num > 0) {
uint8_t* script_args_start = (uint8_t*)msg + header->args_off;
memcpy(script_args_start, lp->script_args, lp->script_args_len);
uint8_t* args_start = script_args_start + lp->script_args_len;
memcpy(args_start, lp->args, lp->args_len);
}
size_t total_args_len = lp->script_args_len + lp->args_len;
// Include the environment strings so the dynamic linker can
// see options like LD_DEBUG or whatnot.
if (lp->envc > 0) {
header->environ_off = header->args_off + total_args_len;
header->environ_num = lp->envc;
uint8_t* env_start = (uint8_t*)msg + header->environ_off;
memcpy(env_start, lp->env, lp->env_len);
}
if (with_names && (lp->namec > 0)) {
header->names_off = header->args_off + total_args_len + lp->env_len;
header->names_num = lp->namec;
uint8_t* names_start = (uint8_t*)msg + header->names_off;
memcpy(names_start, lp->names, lp->names_len);
}
*msg_buf = msg;
*buf_size = msg_size;
return ZX_OK;
}
static zx_status_t send_loader_message(launchpad_t* lp,
zx_handle_t first_thread,
zx_handle_t tochannel) {
void* msg;
size_t msg_size;
size_t num_handles = HND_SPECIAL_COUNT + HND_LOADER_COUNT;
zx_status_t status = build_message(lp, num_handles, &msg, &msg_size, false);
if (status != ZX_OK)
return status;
zx_proc_args_t* header = msg;
uint32_t* msg_handle_info;
msg_handle_info = (uint32_t*) ((uint8_t*)msg + header->handle_info_off);
// This loop should be completely unrolled. But using a switch here
// gives us compiler warnings if we forget to handle any of the special
// types listed in the enum.
zx_handle_t handles[HND_SPECIAL_COUNT + HND_LOADER_COUNT];
size_t nhandles = 0;
for (enum special_handles i = 0; i <= HND_SPECIAL_COUNT; ++i) {
uint32_t id = 0; // -Wall
switch (i) {
case HND_SPECIAL_COUNT:;
// Duplicate the handles for the loader so we can send them in the
// loader message and still have them later.
zx_handle_t proc;
status = zx_handle_duplicate(lp_proc(lp), ZX_RIGHT_SAME_RIGHTS, &proc);
if (status != ZX_OK) {
free(msg);
return status;
}
zx_handle_t vmar;
status = zx_handle_duplicate(lp_vmar(lp), ZX_RIGHT_SAME_RIGHTS, &vmar);
if (status != ZX_OK) {
zx_handle_close(proc);
free(msg);
return status;
}
zx_handle_t thread;
status = zx_handle_duplicate(first_thread, ZX_RIGHT_SAME_RIGHTS, &thread);
if (status != ZX_OK) {
zx_handle_close(proc);
zx_handle_close(vmar);
free(msg);
return status;
}
handles[nhandles] = proc;
msg_handle_info[nhandles] = PA_PROC_SELF;
handles[nhandles + 1] = vmar;
msg_handle_info[nhandles + 1] = PA_VMAR_ROOT;
handles[nhandles + 2] = thread;
msg_handle_info[nhandles + 2] = PA_THREAD_SELF;
nhandles += HND_LOADER_COUNT;
continue;
case HND_LDSVC_LOADER:
id = PA_LDSVC_LOADER;
break;
case HND_EXEC_VMO:
id = PA_VMO_EXECUTABLE;
break;
case HND_SEGMENTS_VMAR:
id = PA_VMAR_LOADED;
break;
}
if (lp->special_handles[i] != ZX_HANDLE_INVALID) {
handles[nhandles] = lp->special_handles[i];
msg_handle_info[nhandles] = id;
++nhandles;
}
}
status = zx_channel_write(tochannel, 0, msg, msg_size, handles, nhandles);
if (status == ZX_OK)
lp->loader_message = false;
// message_write consumed all those handles.
for (enum special_handles i = 0; i < HND_SPECIAL_COUNT; ++i)
lp->special_handles[i] = ZX_HANDLE_INVALID;
free(msg);
return status;
}
size_t launchpad_set_stack_size(launchpad_t* lp, size_t new_size) {
size_t old_size = lp->stack_size;
if (new_size >= (SIZE_MAX & -PAGE_SIZE)) {
// Ridiculously large size won't actually work at allocation time,
// but at least page rounding won't wrap it around to zero.
new_size = SIZE_MAX & -PAGE_SIZE;
} else if (new_size > 0) {
// Round up to page size.
new_size = (new_size + PAGE_SIZE - 1) & -PAGE_SIZE;
}
if (lp->error == ZX_OK) {
lp->stack_size = new_size;
lp->set_stack_size = true;
}
return old_size;
}
static zx_status_t prepare_start(launchpad_t* lp, launchpad_start_data_t* result) {
if (lp->entry == 0)
return lp_error(lp, ZX_ERR_BAD_STATE, "prepare start bad state");
zx_status_t status = ZX_OK;
zx_handle_t to_child = ZX_HANDLE_INVALID;
zx_handle_t bootstrap = ZX_HANDLE_INVALID;
zx_handle_t process = ZX_HANDLE_INVALID;
zx_handle_t root_vmar = ZX_HANDLE_INVALID;
zx_handle_t thread = ZX_HANDLE_INVALID;
void *msg = NULL;
status = zx_channel_create(0, &to_child, &bootstrap);
if (status != ZX_OK)
return lp_error(lp, status, "start: cannot create channel");
const char* thread_name = "initial-thread";
status = zx_thread_create(lp_proc(lp), thread_name, strlen(thread_name), 0, &thread);
if (status != ZX_OK) {
lp_error(lp, status, "cannot create initial thread");
goto cleanup;
}
// Pass the thread handle down to the child. The handle we pass
// will be consumed by message_write. So we need a duplicate to
// pass to zx_process_start later.
zx_handle_t thread_copy;
status = zx_handle_duplicate(thread, ZX_RIGHT_SAME_RIGHTS, &thread_copy);
if (status != ZX_OK) {
lp_error(lp, status, "cannot duplicate thread handle");
goto cleanup;
}
status = launchpad_add_handle(lp, thread_copy, PA_THREAD_SELF);
if (status != ZX_OK) {
lp_error(lp, status, "cannot add thread self handle");
goto cleanup;
}
bool sent_loader_message = lp->loader_message;
if (lp->loader_message) {
status = send_loader_message(lp, thread, to_child);
if (status != ZX_OK) {
lp_error(lp, status, "failed to send loader message");
goto cleanup;
}
}
bool allocate_stack = !lp->set_stack_size || lp->stack_size > 0;
size_t size;
if (build_message(lp, lp->handle_count + (allocate_stack ? 1 : 0),
&msg, &size, true) != ZX_OK) {
lp_error(lp, ZX_ERR_NO_MEMORY,
"out of memory assembling procargs message");
goto cleanup;
}
zx_proc_args_t* header = msg;
uint32_t* next_handle = mempcpy((uint8_t*)msg + header->handle_info_off,
lp->handles_info,
lp->handle_count * sizeof(lp->handles_info[0]));
if (allocate_stack)
*next_handle = PA_VMO_STACK;
// Figure out how big an initial thread to allocate.
char stack_vmo_name[ZX_MAX_NAME_LEN];
size_t stack_size;
if (sent_loader_message && !lp->set_stack_size) {
// The initial stack will be used just for startup work and to
// contain the bootstrap message. Make it only as big as needed:
// the message itself and its array of handles, plus some slop.
stack_size = size + (lp->handle_count * sizeof(zx_handle_t));
// This constant is defined by the C library in <limits.h>. It's
// tuned to be enough to cover the dynamic linker and C library
// startup code's stack usage (up until the point it switches to
// its own stack in __libc_start_main), but leave a little space so
// for small bootstrap message sizes the stack needs only one page.
stack_size += PTHREAD_STACK_MIN;
stack_size = (stack_size + PAGE_SIZE - 1) & -PAGE_SIZE;
snprintf(stack_vmo_name, sizeof(stack_vmo_name),
"stack: msg of %#zx", size);
} else {
// Use the requested or default size.
stack_size =
lp->set_stack_size ? lp->stack_size : ZIRCON_DEFAULT_STACK_SIZE;
snprintf(stack_vmo_name, sizeof(stack_vmo_name), "stack: %s %#zx",
lp->set_stack_size ? "explicit" : "default", stack_size);
// Assume the process will read the bootstrap message onto its
// initial thread's stack. If it would need more than half its
// stack just to read the message, consider that an unreasonably
// large size for the message (presumably arguments and
// environment strings that are unreasonably large).
if (stack_size > 0 && size > stack_size / 2) {
lp_error(lp, ZX_ERR_BUFFER_TOO_SMALL,
"procargs message is too large");
goto cleanup;
}
}
zx_vaddr_t sp = 0;
if (stack_size > 0) {
// Allocate the initial thread's stack.
zx_handle_t stack_vmo;
zx_status_t status = zx_vmo_create(stack_size, 0, &stack_vmo);
if (status != ZX_OK) {
lp_error(lp, status, "cannot create stack vmo");
goto cleanup;
}
zx_object_set_property(stack_vmo, ZX_PROP_NAME,
stack_vmo_name, strlen(stack_vmo_name));
zx_vaddr_t stack_base;
status = zx_vmar_map(lp_vmar(lp),
ZX_VM_PERM_READ | ZX_VM_PERM_WRITE,
0, stack_vmo, 0, stack_size, &stack_base);
if (status == ZX_OK) {
ZX_DEBUG_ASSERT(stack_size % PAGE_SIZE == 0);
sp = compute_initial_stack_pointer(stack_base, stack_size);
// Pass the stack VMO to the process. Our protocol with the
// new process is that we warrant that this is the VMO from
// which the initial stack is mapped and that we've exactly
// mapped the entire thing, so vm_object_get_size on this in
// concert with the initial SP value tells it the exact bounds
// of its stack.
//
// Note this expands the handle list after we've already
// built the bootstrap message. We shoved an extra info
// slot with PA_VMO_STACK into the message, so now this new
// final handle will correspond to that slot.
status = launchpad_add_handle(lp, stack_vmo, PA_VMO_STACK);
}
if (status != ZX_OK) {
zx_handle_close(stack_vmo);
lp_error(lp, status, "cannot map stack vmo");
goto cleanup;
}
}
if (lp->reserve_vmar != ZX_HANDLE_INVALID) {
// We're done doing mappings, so clear out the reservation VMAR.
status = zx_vmar_destroy(lp->reserve_vmar);
if (status != ZX_OK) {
lp_error(lp, status, "\
zx_vmar_destroy failed on low address space reservation VMAR");
goto cleanup;
}
status = zx_handle_close(lp->reserve_vmar);
if (status != ZX_OK) {
lp_error(lp, status, "\
zx_handle_close failed on low address space reservation VMAR");
goto cleanup;
}
lp->reserve_vmar = ZX_HANDLE_INVALID;
}
// The process handle in lp->handles[0] will be consumed by message_write.
// So we'll need a duplicate to do process operations later.
status = zx_handle_duplicate(lp_proc(lp), ZX_RIGHT_SAME_RIGHTS, &process);
if (status != ZX_OK) {
lp_error(lp, status, "cannot duplicate process handle");
goto cleanup;
}
// The root_vmar handle in lp->handles[0] will be consumed by message_write.
// So we'll need a duplicate to do process operations later.
status = zx_handle_duplicate(lp_vmar(lp), ZX_RIGHT_SAME_RIGHTS, &root_vmar);
if (status != ZX_OK) {
lp_error(lp, status, "cannot duplicate root vmar handle");
goto cleanup;
}
status = zx_channel_write(to_child, 0, msg, size, lp->handles, lp->handle_count);
// message_write consumed all the handles.
for (size_t i = 0; i < lp->handle_count; ++i)
lp->handles[i] = ZX_HANDLE_INVALID;
lp->handle_count = 0;
if (status != ZX_OK) {
lp_error(lp, status, "failed to write procargs message");
goto cleanup;
}
zx_handle_close(to_child);
free(msg);
result->process = process;
result->root_vmar = root_vmar;
result->thread = thread;
result->entry = lp->entry;
result->stack = sp;
result->bootstrap = bootstrap;
result->vdso_base = lp->vdso_base;
result->base = lp->base;
return ZX_OK;
cleanup:
if (to_child != ZX_HANDLE_INVALID)
zx_handle_close(to_child);
if (bootstrap != ZX_HANDLE_INVALID)
zx_handle_close(bootstrap);
if (process != ZX_HANDLE_INVALID)
zx_handle_close(process);
if (root_vmar != ZX_HANDLE_INVALID)
zx_handle_close(root_vmar);
if (thread != ZX_HANDLE_INVALID)
zx_handle_close(thread);
free(msg);
return lp->error;
}
// Start the process running. If the send_loader_message flag is
// set and this succeeds in sending the initial bootstrap message,
// it clears the loader-service handle. If this succeeds in sending
// the main bootstrap message, it clears the list of handles to
// transfer (after they've been transferred) as well as the process
// handle.
//
// Returns the process handle via |process_out| on success, giving
// ownership to the caller. On failure, the return value doesn't
// distinguish failure to send the first or second message from
// failure to start the process, so on failure the loader-service
// handle might or might not have been cleared and the handles to
// transfer might or might not have been cleared.
static zx_status_t launchpad_start(launchpad_t* lp, zx_handle_t* process_out) {
if (lp->error)
return lp->error;
launchpad_start_data_t data;
zx_status_t status = prepare_start(lp, &data);
if (status != ZX_OK)
return status;
status = zx_process_start(data.process, data.thread, data.entry, data.stack,
data.bootstrap, data.vdso_base);
zx_handle_close(data.thread);
zx_handle_close(data.root_vmar);
if (status != ZX_OK) {
zx_handle_close(data.process);
return lp_error(lp, status, "zx_process_start() failed");
}
*process_out = data.process;
return ZX_OK;
}
zx_status_t launchpad_go(launchpad_t* lp, zx_handle_t* proc, const char** errmsg) {
zx_handle_t h = ZX_HANDLE_INVALID;
zx_status_t status = launchpad_start(lp, &h);
if (errmsg)
*errmsg = lp->errmsg;
if (status == ZX_OK) {
if (proc) {
*proc = h;
} else {
zx_handle_close(h);
}
}
launchpad_destroy(lp);
return status;
}
zx_status_t launchpad_ready_set(launchpad_t* lp,
launchpad_start_data_t* data,
const char** errmsg) {
zx_status_t status = prepare_start(lp, data);
if (errmsg)
*errmsg = lp->errmsg;
launchpad_destroy(lp);
return status;
}
static zx_status_t launchpad_file_load_with_vdso(launchpad_t* lp, zx_handle_t vmo) {
launchpad_file_load(lp, vmo);
launchpad_load_vdso(lp, ZX_HANDLE_INVALID);
return launchpad_add_vdso_vmo(lp);
}
zx_status_t launchpad_load_from_file(launchpad_t* lp, const char* path) {
zx_handle_t vmo;
zx_status_t status = launchpad_vmo_from_file(path, &vmo);
if (status == ZX_OK) {
return launchpad_file_load_with_vdso(lp, vmo);
} else {
return lp_error(lp, status, "launchpad_vmo_from_file failure");
}
}
zx_status_t launchpad_load_from_vmo(launchpad_t* lp, zx_handle_t vmo) {
return launchpad_file_load_with_vdso(lp, vmo);
}