1226 lines
32 KiB
C
1226 lines
32 KiB
C
#include <horizon/sys/loader.h>
|
|
|
|
#include <sys/launch.h>
|
|
#include <magenta/bootstrap.h>
|
|
#include <magenta/misc.h>
|
|
#include <magenta/handle.h>
|
|
#include <magenta/vmo.h>
|
|
#include <magenta/vmar.h>
|
|
#include <magenta/task.h>
|
|
#include <magenta/tunnel.h>
|
|
#include <magenta/object.h>
|
|
#include <magenta/ldsvc.h>
|
|
#include <magenta/errors.h>
|
|
#include <magenta/signals.h>
|
|
#include <mio/fs.h>
|
|
#include <mio/fd.h>
|
|
#include <mio/namespace.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <unistd.h>
|
|
#include <mio/fs.h>
|
|
#include <mio/mio.h>
|
|
#include <xpc.h>
|
|
#include <fcntl.h>
|
|
#include <errno.h>
|
|
#include "__elf.h"
|
|
|
|
#define LDV1_VERSION 0
|
|
#define LDV2_VERSION 1
|
|
|
|
/* 1 MiB default stack */
|
|
#define DEFAULT_STACK_SZ 256 * 0x1000
|
|
|
|
#define NR_DEFAULT_HANDLES 6
|
|
#define NR_STDIO_HANDLES 3
|
|
#define MAX_ARG_HANDLES 32
|
|
|
|
#define ARG_HANDLE(h, t) (mx_bootstrap_handle_t){ .handle = h, .info = t }
|
|
|
|
#define RET_ERR(code) \
|
|
__set_errno(code); \
|
|
return -1
|
|
|
|
static char g_launch_error[1024] = {};
|
|
|
|
struct launch_ctx {
|
|
/* list of handles that were opened by launch() that should be closed if an error occurs */
|
|
mx_handle_t *auto_handles;
|
|
size_t auto_handle_count;
|
|
|
|
const char **names;
|
|
size_t name_count;
|
|
|
|
/* bootstrap handle array */
|
|
mx_bootstrap_handle_t *b_handles;
|
|
size_t b_handle_count;
|
|
|
|
mx_handle_t exec_vmo, stack_vmo;
|
|
mx_vaddr_t stack_ptr;
|
|
|
|
int ldsvc_version;
|
|
union {
|
|
mx_handle_t handle;
|
|
xpc_socket sock;
|
|
} ldsvc;
|
|
|
|
mx_handle_t bootstrap_local, bootstrap_remote;
|
|
|
|
/* ELF-loader specific items */
|
|
struct elf_image exec, vdso;
|
|
mx_handle_t interp_vmo;
|
|
char *interp_path;
|
|
mx_handle_t new_task, new_vmar;
|
|
mx_vaddr_t entry_point;
|
|
|
|
struct namespace_entry *inherited_ns;
|
|
size_t inherited_ns_len;
|
|
|
|
int *fd;
|
|
|
|
/* this is used to send STDIO handles to the interpreter.
|
|
* the same handles are sent in both messages. */
|
|
mx_handle_t stdio[LAUNCH_MAX_FDS];
|
|
};
|
|
|
|
static void set_error_msg(const char *s, ...)
|
|
{
|
|
va_list arg;
|
|
va_start(arg, s);
|
|
vsnprintf(g_launch_error, sizeof g_launch_error, s, arg);
|
|
va_end(arg);
|
|
}
|
|
|
|
const char *launch_error(void)
|
|
{
|
|
return g_launch_error;
|
|
}
|
|
|
|
static int collect_stdio_handles(mx_bootstrap_handle_t *out, size_t max)
|
|
{
|
|
int offset = 0;
|
|
|
|
mx_handle_t handle;
|
|
for (int i = 0; i < NR_STDIO_HANDLES; i++) {
|
|
if (offset >= max) {
|
|
break;
|
|
}
|
|
|
|
int r = mio_dup_handle(i, &handle);
|
|
if (r < 0) {
|
|
continue;
|
|
}
|
|
|
|
out[offset].handle = handle;
|
|
out[offset].info = MX_B_HND(MX_B_FD, i);
|
|
offset++;
|
|
}
|
|
|
|
return offset;
|
|
}
|
|
|
|
static const char *task_name(const char *path)
|
|
{
|
|
const char *name = path;
|
|
const char *s = path;
|
|
while (*s) {
|
|
char c = *s;
|
|
s++;
|
|
|
|
if (c == '/') {
|
|
name = s;
|
|
}
|
|
}
|
|
|
|
return *name ? name : path;
|
|
}
|
|
|
|
static int validate_argv(int argc, const char **argv)
|
|
{
|
|
if (argc && !argv) {
|
|
set_error_msg("launch_info.argv is NULL");
|
|
return EFAULT;
|
|
}
|
|
|
|
for (int i = 0; i < argc; i++) {
|
|
if (!argv[i]) {
|
|
set_error_msg("argument #%d is NULL", i);
|
|
return EFAULT;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int validate_fds(const struct launch_info *info, struct launch_ctx *ctx)
|
|
{
|
|
ctx->fd = calloc(LAUNCH_MAX_FDS, sizeof(int));
|
|
bool auto_stdio = true;
|
|
|
|
if (info->flags & LAUNCH_SET_FD) {
|
|
memcpy(ctx->fd, info->fd, sizeof info->fd);
|
|
auto_stdio = false;
|
|
} else {
|
|
for (int i = 0; i < LAUNCH_MAX_FDS; i++) {
|
|
ctx->fd[i] = i;
|
|
}
|
|
}
|
|
|
|
for (size_t i = 0; i < LAUNCH_MAX_FDS; i++) {
|
|
int fd = ctx->fd[i];
|
|
bool inherited = false;
|
|
|
|
if (fd == FD_INHERIT) {
|
|
fd = ctx->fd[i] = i;
|
|
inherited = true;
|
|
}
|
|
|
|
if (fd == FD_NONE) {
|
|
continue;
|
|
}
|
|
|
|
struct mio_object *obj = mio_fd_list_object_from_fd(mio_global_fd_list(), fd);
|
|
if (obj) {
|
|
continue;
|
|
}
|
|
|
|
if (inherited || auto_stdio) {
|
|
ctx->fd[i] = FD_NONE;
|
|
} else {
|
|
set_error_msg("file descriptor %d (launch_info.fd[%zu]) is invalid", fd, i);
|
|
return EBADF;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int validate_ns(int flags, const struct namespace_entry *ns, size_t count)
|
|
{
|
|
if (count && !ns) {
|
|
set_error_msg("launch_info.ns is NULL");
|
|
return EFAULT;
|
|
}
|
|
|
|
if (!(flags & LAUNCH_SET_NS) && count) {
|
|
/* TODO better error messages */
|
|
set_error_msg("cannot create a new namespace and inherit an existing one");
|
|
return EINVAL;
|
|
}
|
|
|
|
for (size_t i = 0; i < count; i++) {
|
|
const struct namespace_entry *ent = &ns[i];
|
|
if (!ent->path) {
|
|
set_error_msg("filepath for namespace entry #%zu is invalid", i);
|
|
return EFAULT;
|
|
}
|
|
|
|
if (ent->fd != -1 && ent->handle != MX_NULL_HANDLE) {
|
|
set_error_msg("namespace entry %s has both a file descriptor and a handle", ent->path);
|
|
return EINVAL;
|
|
}
|
|
|
|
if (ent->fd) {
|
|
struct mio_object *obj = mio_fd_list_object_from_fd(mio_global_fd_list(), ent->fd);
|
|
if (!obj) {
|
|
set_error_msg("file descriptor %d for namespace entry %s is invalid", ent->fd, ent->path);
|
|
return EBADF;
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int collect_names(const struct launch_info *info, struct launch_ctx *ctx)
|
|
{
|
|
size_t ns_count = info->ns_count;
|
|
const struct namespace_entry *ns = info->ns;
|
|
|
|
if (!(info->flags & LAUNCH_SET_NS)) {
|
|
ns_count = ctx->inherited_ns_len;
|
|
ns = ctx->inherited_ns;
|
|
}
|
|
|
|
size_t name_count = ns_count;
|
|
if (info->cwd.path) {
|
|
name_count++;
|
|
}
|
|
|
|
const char **names = calloc(name_count, sizeof *names);
|
|
|
|
for (size_t i = 0; i < ns_count; i++) {
|
|
const struct namespace_entry *ent = &ns[i];
|
|
names[i] = ent->path;
|
|
}
|
|
|
|
if (info->cwd.path) {
|
|
names[name_count - 1] = info->cwd.path;
|
|
}
|
|
|
|
ctx->names = names;
|
|
ctx->name_count = name_count;
|
|
return 0;
|
|
}
|
|
|
|
static int count_handles(const struct launch_info *info, struct launch_ctx *ctx)
|
|
{
|
|
size_t all_handles = 0, auto_handles = 0;
|
|
for (size_t i = 0; i < LAUNCH_MAX_FDS; i++) {
|
|
if (ctx->fd[i] != FD_NONE) {
|
|
all_handles++;
|
|
auto_handles++;
|
|
}
|
|
}
|
|
|
|
size_t ns_len = info->ns_count;
|
|
const struct namespace_entry *ns = info->ns;
|
|
bool auto_ns = false;
|
|
|
|
if (!(info->flags & LAUNCH_SET_NS)) {
|
|
ns_len = ctx->inherited_ns_len;
|
|
ns = ctx->inherited_ns;
|
|
auto_ns = true;
|
|
}
|
|
|
|
for (size_t i = 0; i < ns_len; i++) {
|
|
const struct namespace_entry *ent = &ns[i];
|
|
|
|
all_handles++;
|
|
|
|
if (ent->handle == MX_NULL_HANDLE || auto_ns) {
|
|
auto_handles++;
|
|
}
|
|
}
|
|
|
|
for (size_t i = 0; i < info->handle_count; i++) {
|
|
int type = MX_B_HND_TYPE(info->handles[i].info);
|
|
|
|
switch (type) {
|
|
/* the user can only send handles of the following types.
|
|
* all other handle types are used internally by launch() */
|
|
case MX_B_TUNNEL_LDSVC:
|
|
case MX_B_TUNNEL_EXPORT:
|
|
case MX_B_RESOURCE_ROOT:
|
|
case MX_B_RESOURCE_IOPORT:
|
|
case MX_B_RESOURCE_IRQ:
|
|
case MX_B_USER0:
|
|
case MX_B_USER1:
|
|
case MX_B_USER2:
|
|
case MX_B_USER3:
|
|
case MX_B_USER4:
|
|
break;
|
|
default:
|
|
set_error_msg("handles of type %x cannot be sent to new process", type);
|
|
return EINVAL;
|
|
}
|
|
}
|
|
|
|
/* these handles are sent by launch() in all cases
|
|
* MX_B_TASK_SELF
|
|
* MX_B_VMO_VDSO
|
|
* MX_B_VMO_EXEC
|
|
* MX_B_VMAR_ROOT
|
|
* MX_B_VMAR_EXEC
|
|
* MX_B_TUNNEL_BTSTP
|
|
*/
|
|
all_handles += NR_DEFAULT_HANDLES;
|
|
|
|
all_handles += info->handle_count;
|
|
|
|
ctx->auto_handle_count = auto_handles;
|
|
ctx->b_handle_count = all_handles;
|
|
|
|
ctx->auto_handles = calloc(ctx->auto_handle_count, sizeof *ctx->auto_handles);
|
|
ctx->b_handles = calloc(ctx->b_handle_count, sizeof *ctx->b_handles);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int collect_handles(const struct launch_info *info, struct launch_ctx *ctx)
|
|
{
|
|
memcpy(ctx->b_handles, info->handles, info->handle_count * sizeof *info->handles);
|
|
size_t b_idx = info->handle_count;
|
|
size_t auto_idx = 0;
|
|
|
|
for (int i = 0; i < LAUNCH_MAX_FDS; i++) {
|
|
int fd = ctx->fd[i];
|
|
if (fd == FD_NONE) {
|
|
continue;
|
|
}
|
|
|
|
mx_handle_t fd_handle = MX_NULL_HANDLE;
|
|
|
|
if (fd == FD_INHERIT) {
|
|
mio_dup_handle(i, &fd_handle);
|
|
} else {
|
|
mio_dup_handle(fd, &fd_handle);
|
|
}
|
|
|
|
if (fd_handle == MX_NULL_HANDLE) {
|
|
if (fd == FD_INHERIT) {
|
|
continue;
|
|
}
|
|
|
|
set_error_msg("cannot duplicate file descriptor %d",
|
|
fd == FD_INHERIT ? i : fd);
|
|
return EBADF;
|
|
}
|
|
|
|
ctx->b_handles[b_idx].info = MX_B_HND(MX_B_FD, i);
|
|
ctx->b_handles[b_idx].handle = fd_handle;
|
|
b_idx++;
|
|
|
|
ctx->auto_handles[auto_idx++] = fd_handle;
|
|
ctx->stdio[i] = fd_handle;
|
|
}
|
|
|
|
if (!(info->flags & LAUNCH_SET_NS)) {
|
|
for (size_t i = 0; i < ctx->inherited_ns_len; i++) {
|
|
const struct namespace_entry *ent = &ctx->inherited_ns[i];
|
|
|
|
ctx->b_handles[b_idx].info = MX_B_HND(MX_B_NS_DIR, i);
|
|
ctx->b_handles[b_idx].handle = ent->handle;
|
|
b_idx++;
|
|
|
|
ctx->auto_handles[auto_idx++] = ent->handle;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
for (size_t i = 0; i < info->ns_count; i++) {
|
|
const struct namespace_entry *ent = &info->ns[i];
|
|
|
|
mx_handle_t ns_handle = MX_NULL_HANDLE;
|
|
bool is_auto = false;
|
|
|
|
if (ent->handle) {
|
|
ns_handle = ent->handle;
|
|
} else if (ent->fd != -1) {
|
|
mio_dup_handle(ent->fd, &ns_handle);
|
|
is_auto = true;
|
|
} else {
|
|
const char *src = ent->src;
|
|
if (!src) {
|
|
src = ent->path;
|
|
}
|
|
|
|
/* TODO allow caller to specify permissions */
|
|
int ns_fd = open(src, O_RDWR);
|
|
if (ns_fd == -1) {
|
|
int err = errno;
|
|
set_error_msg("cannot open namespace entry '%s': %s", src, err);
|
|
return err;
|
|
}
|
|
|
|
mio_release_handle(ns_fd, &ns_handle);
|
|
is_auto = true;
|
|
}
|
|
|
|
if (ns_handle == MX_NULL_HANDLE) {
|
|
set_error_msg("file descriptor or handle for namespace entry '%s' is invalid", ent->path);
|
|
return EBADF;
|
|
}
|
|
|
|
ctx->b_handles[b_idx].info = MX_B_HND(MX_B_NS_DIR, i);
|
|
ctx->b_handles[b_idx].handle = ns_handle;
|
|
b_idx++;
|
|
|
|
if (is_auto) {
|
|
ctx->auto_handles[auto_idx++] = ns_handle;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void fail_cleanup(struct launch_ctx *ctx)
|
|
{
|
|
if (ctx->exec_vmo) {
|
|
mx_handle_close(ctx->exec_vmo);
|
|
}
|
|
|
|
if (ctx->auto_handles) {
|
|
for (size_t i = 0; i < ctx->auto_handle_count; i++) {
|
|
if (ctx->auto_handles[i]) {
|
|
mx_handle_close(ctx->auto_handles[i]);
|
|
}
|
|
}
|
|
|
|
free(ctx->auto_handles);
|
|
}
|
|
|
|
if (ctx->inherited_ns) {
|
|
for (size_t i = 0; i < ctx->inherited_ns_len; i++) {
|
|
struct namespace_entry *ent = &ctx->inherited_ns[i];
|
|
free((char *)ent->path);
|
|
mx_handle_close(ent->handle);
|
|
}
|
|
|
|
free(ctx->inherited_ns);
|
|
ctx->inherited_ns = NULL;
|
|
ctx->inherited_ns_len = 0;
|
|
}
|
|
|
|
if (ctx->names) {
|
|
free(ctx->names);
|
|
ctx->names = NULL;
|
|
ctx->name_count = 0;
|
|
}
|
|
|
|
if (ctx->b_handles) {
|
|
free(ctx->b_handles);
|
|
}
|
|
|
|
__elf_image_cleanup(&ctx->exec);
|
|
__elf_image_cleanup(&ctx->vdso);
|
|
|
|
if (ctx->interp_vmo) {
|
|
mx_handle_close(ctx->interp_vmo);
|
|
ctx->interp_vmo = MX_NULL_HANDLE;
|
|
}
|
|
|
|
if (ctx->interp_path) {
|
|
free(ctx->interp_path);
|
|
ctx->interp_path = NULL;
|
|
}
|
|
|
|
if (ctx->new_vmar) {
|
|
mx_handle_close(ctx->new_vmar);
|
|
ctx->new_vmar = MX_NULL_HANDLE;
|
|
}
|
|
|
|
if (ctx->stack_vmo) {
|
|
mx_handle_close(ctx->stack_vmo);
|
|
ctx->stack_vmo = MX_NULL_HANDLE;
|
|
}
|
|
|
|
if (ctx->new_task) {
|
|
mx_handle_close(ctx->new_task);
|
|
ctx->new_task = MX_NULL_HANDLE;
|
|
}
|
|
|
|
if (ctx->bootstrap_local) {
|
|
mx_handle_close(ctx->bootstrap_local);
|
|
ctx->bootstrap_local = MX_NULL_HANDLE;
|
|
}
|
|
|
|
if (ctx->bootstrap_remote) {
|
|
mx_handle_close(ctx->bootstrap_remote);
|
|
ctx->bootstrap_remote = MX_NULL_HANDLE;
|
|
}
|
|
|
|
if (ctx->fd) {
|
|
free(ctx->fd);
|
|
ctx->fd = NULL;
|
|
}
|
|
|
|
if (ctx->ldsvc_version == LDV1_VERSION && ctx->ldsvc.handle) {
|
|
mx_handle_close(ctx->ldsvc.handle);
|
|
ctx->ldsvc.handle = MX_NULL_HANDLE;
|
|
} else if (ctx->ldsvc_version == LDV2_VERSION && ctx->ldsvc.sock) {
|
|
xpc_socket_destroy(ctx->ldsvc.sock);
|
|
ctx->ldsvc.sock = NULL;
|
|
}
|
|
}
|
|
|
|
static void success_cleanup(struct launch_ctx *ctx)
|
|
{
|
|
if (ctx->exec_vmo) {
|
|
mx_handle_close(ctx->exec_vmo);
|
|
}
|
|
|
|
if (ctx->auto_handles) {
|
|
for (size_t i = 0; i < ctx->auto_handle_count; i++) {
|
|
if (ctx->auto_handles[i]) {
|
|
mx_handle_close(ctx->auto_handles[i]);
|
|
}
|
|
}
|
|
|
|
free(ctx->auto_handles);
|
|
}
|
|
|
|
if (ctx->inherited_ns) {
|
|
for (size_t i = 0; i < ctx->inherited_ns_len; i++) {
|
|
struct namespace_entry *ent = &ctx->inherited_ns[i];
|
|
free((char *)ent->path);
|
|
mx_handle_close(ent->handle);
|
|
}
|
|
|
|
free(ctx->inherited_ns);
|
|
ctx->inherited_ns = NULL;
|
|
ctx->inherited_ns_len = 0;
|
|
}
|
|
|
|
if (ctx->names) {
|
|
free(ctx->names);
|
|
ctx->names = NULL;
|
|
ctx->name_count = 0;
|
|
}
|
|
|
|
if (ctx->b_handles) {
|
|
free(ctx->b_handles);
|
|
}
|
|
|
|
__elf_image_cleanup(&ctx->exec);
|
|
__elf_image_cleanup(&ctx->vdso);
|
|
|
|
if (ctx->interp_vmo) {
|
|
mx_handle_close(ctx->interp_vmo);
|
|
ctx->interp_vmo = MX_NULL_HANDLE;
|
|
}
|
|
|
|
if (ctx->interp_path) {
|
|
free(ctx->interp_path);
|
|
ctx->interp_path = NULL;
|
|
}
|
|
|
|
if (ctx->stack_vmo) {
|
|
mx_handle_close(ctx->stack_vmo);
|
|
ctx->stack_vmo = MX_NULL_HANDLE;
|
|
}
|
|
|
|
if (ctx->new_vmar) {
|
|
mx_handle_close(ctx->new_vmar);
|
|
ctx->new_vmar = MX_NULL_HANDLE;
|
|
}
|
|
|
|
if (ctx->bootstrap_local) {
|
|
mx_handle_close(ctx->bootstrap_local);
|
|
ctx->bootstrap_local = MX_NULL_HANDLE;
|
|
}
|
|
|
|
if (ctx->fd) {
|
|
free(ctx->fd);
|
|
ctx->fd = NULL;
|
|
}
|
|
|
|
if (ctx->ldsvc_version == LDV1_VERSION && ctx->ldsvc.handle) {
|
|
mx_handle_close(ctx->ldsvc.handle);
|
|
ctx->ldsvc.handle = MX_NULL_HANDLE;
|
|
} else if (ctx->ldsvc_version == LDV2_VERSION && ctx->ldsvc.sock) {
|
|
xpc_socket_destroy(ctx->ldsvc.sock);
|
|
ctx->ldsvc.sock = NULL;
|
|
}
|
|
}
|
|
|
|
static int inherit_namespace(const struct launch_info *info, struct launch_ctx *ctx)
|
|
{
|
|
if (info->flags & LAUNCH_SET_NS) {
|
|
return 0;
|
|
}
|
|
|
|
ctx->inherited_ns = mio_namespace_export(mio_global_namespace(), &ctx->inherited_ns_len);
|
|
return ctx->inherited_ns ? 0 : -1;
|
|
}
|
|
|
|
static mx_handle_t request_v1(mx_handle_t ldsvc, const char *name)
|
|
{
|
|
size_t name_len = strlen(name);
|
|
size_t msg_len = sizeof(mx_ldsvc_msg_t) + name_len;
|
|
unsigned char msg_buf[512];
|
|
|
|
if (name_len > sizeof msg_buf - sizeof (mx_ldsvc_msg_t)) {
|
|
name_len = sizeof msg_buf - sizeof (mx_ldsvc_msg_t);
|
|
}
|
|
|
|
mx_ldsvc_msg_t *msg = (mx_ldsvc_msg_t *)msg_buf;
|
|
char *name_dest = (char *)msg + sizeof(mx_ldsvc_msg_t);
|
|
|
|
msg->name_off = sizeof(mx_ldsvc_msg_t);
|
|
msg->name_len = name_len;
|
|
msg->op = MX_LDSVC_OP_LOAD_OBJECT;
|
|
memcpy(name_dest, name, name_len);
|
|
|
|
mx_tunnel_write(ldsvc, msg_buf, msg_len, NULL, 0);
|
|
|
|
mx_handle_t handle;
|
|
|
|
mx_tunnel_read(ldsvc,
|
|
msg_buf, sizeof msg_buf,
|
|
&handle, 1,
|
|
&msg_len, NULL);
|
|
|
|
if (msg->code != MX_LDSVC_CODE_OK) {
|
|
return MX_NULL_HANDLE;
|
|
}
|
|
|
|
return handle;
|
|
}
|
|
|
|
static mx_handle_t request_v2(xpc_socket ldsvc, const char *name)
|
|
{
|
|
int32_t result;
|
|
mx_handle_t vmo;
|
|
xpc_error err = horizon_sys_LoaderService_GetImageVMO(ldsvc, name, &result, &vmo);
|
|
if (err != XPC_OK) {
|
|
return MX_NULL_HANDLE;
|
|
}
|
|
|
|
if (result != 0) {
|
|
return MX_NULL_HANDLE;
|
|
}
|
|
|
|
return vmo;
|
|
}
|
|
|
|
static mx_handle_t request_image(struct launch_ctx *ctx, const char *name)
|
|
{
|
|
switch (ctx->ldsvc_version) {
|
|
case 0:
|
|
return request_v1(ctx->ldsvc.handle, name);
|
|
case 1:
|
|
return request_v2(ctx->ldsvc.sock, name);
|
|
default:
|
|
return MX_NULL_HANDLE;
|
|
}
|
|
}
|
|
|
|
static const char *get_name_from_path(const struct launch_info *info)
|
|
{
|
|
const char *p = info->path;
|
|
const char *e = NULL;
|
|
|
|
for (int i = 0; p[i]; i++) {
|
|
if (p[i] == '/') {
|
|
e = p + i + 1;
|
|
}
|
|
}
|
|
|
|
if (!e || *e == '\0') {
|
|
return p;
|
|
}
|
|
|
|
return e;
|
|
}
|
|
|
|
static int load_exec(const struct launch_info *info, struct launch_ctx *ctx)
|
|
{
|
|
mx_handle_t local_vmar = mx_bootstrap_handle_get(MX_B_VMAR_ROOT);
|
|
mx_handle_t self_task = mx_bootstrap_handle_get(MX_B_TASK_SELF);
|
|
mx_handle_t vdso_vmo = mx_bootstrap_handle_get(MX_B_VMO_VDSO);
|
|
|
|
if (!local_vmar || !self_task || !vdso_vmo) {
|
|
return EPERM;
|
|
}
|
|
|
|
char interp_path[1024];
|
|
interp_path[0] = '\0';
|
|
|
|
__elf_get_interp_path(ctx->exec_vmo, interp_path, sizeof interp_path);
|
|
|
|
mx_handle_t real_exec = ctx->exec_vmo;
|
|
|
|
if (*interp_path != '\0') {
|
|
real_exec = request_image(ctx, interp_path);
|
|
|
|
if (real_exec == MX_NULL_HANDLE) {
|
|
set_error_msg("cannot open program interpreter '%s'", interp_path);
|
|
return ENOENT;
|
|
}
|
|
|
|
ctx->interp_vmo = real_exec;
|
|
ctx->interp_path = strdup(interp_path);
|
|
}
|
|
|
|
const char *new_task_name = get_name_from_path(info);
|
|
mx_status_t status = mx_task_create(self_task,
|
|
new_task_name, strlen(new_task_name),
|
|
0, &ctx->new_task, &ctx->new_vmar);
|
|
|
|
if (status != MX_OK) {
|
|
return mio_errno_from_mx_status(status);
|
|
}
|
|
|
|
__elf_image_init(local_vmar, ctx->new_vmar, &ctx->exec);
|
|
if (__elf_image_load_image(&ctx->exec, real_exec)) {
|
|
set_error_msg("cannot load %s image", ctx->interp_vmo ? "interpreter" : "executable");
|
|
return ENOEXEC;
|
|
}
|
|
|
|
__elf_image_init(local_vmar, ctx->new_vmar, &ctx->vdso);
|
|
if (__elf_image_load_image(&ctx->vdso, vdso_vmo)) {
|
|
set_error_msg("cannot load vDSO image");
|
|
return ENOEXEC;
|
|
}
|
|
|
|
if (__elf_image_link(&ctx->exec, &ctx->vdso)) {
|
|
set_error_msg("cannot link %s image", ctx->interp_vmo ? "interpreter" : "executable");
|
|
return ENOEXEC;
|
|
}
|
|
|
|
ctx->entry_point = __elf_image_entry_point(&ctx->exec);
|
|
return 0;
|
|
}
|
|
|
|
static int allocate_stack(struct launch_ctx *ctx)
|
|
{
|
|
mx_status_t status = mx_vmo_create(DEFAULT_STACK_SZ, 0, &ctx->stack_vmo);
|
|
if (status != MX_OK) {
|
|
set_error_msg("cannot allocate stack for new process: %s", mx_status_to_string(status));
|
|
return mio_errno_from_mx_status(status);
|
|
}
|
|
|
|
mx_vaddr_t stack_buf;
|
|
status = mx_vmar_map(ctx->new_vmar, MX_VM_PERM_READ | MX_VM_PERM_WRITE,
|
|
0, ctx->stack_vmo, 0, DEFAULT_STACK_SZ, &stack_buf);
|
|
|
|
if (status != MX_OK) {
|
|
set_error_msg("cannot map stack for new process: %s", mx_status_to_string(status));
|
|
return mio_errno_from_mx_status(status);
|
|
}
|
|
|
|
ctx->stack_ptr = stack_buf + DEFAULT_STACK_SZ;
|
|
return 0;
|
|
}
|
|
|
|
static int get_ldv1_handle(mx_handle_t handle, struct launch_ctx *ctx)
|
|
{
|
|
mx_handle_t t0, t1;
|
|
mx_tunnel_create(&t0, &t1);
|
|
|
|
mx_ldsvc_msg_t msg;
|
|
msg.op = MX_LDSVC_OP_ADD_PEER;
|
|
|
|
mx_handle_t original = mx_bootstrap_handle_get(MX_B_TUNNEL_LDSVC);
|
|
mx_status_t status = mx_tunnel_write(original, &msg, sizeof msg, &t0, 1);
|
|
if (status != MX_OK) {
|
|
set_error_msg("cannot contact LDv1 loader service: %s", mx_status_to_string(status));
|
|
return mio_errno_from_mx_status(status);
|
|
}
|
|
|
|
status = mx_tunnel_read(original, &msg, sizeof msg, NULL, 0, NULL, NULL);
|
|
|
|
if (status != MX_OK) {
|
|
set_error_msg("cannot contact LDv1 loader service: %s", mx_status_to_string(status));
|
|
mx_handle_close(t1);
|
|
return mio_errno_from_mx_status(status);
|
|
}
|
|
|
|
if (msg.code != MX_LDSVC_CODE_OK) {
|
|
set_error_msg("cannot connect to LDv1 loader service: code %d", msg.code);
|
|
mx_handle_close(t1);
|
|
return ENOLINK;
|
|
}
|
|
|
|
ctx->ldsvc_version = LDV1_VERSION;
|
|
ctx->ldsvc.handle = t1;
|
|
return 0;
|
|
}
|
|
|
|
static int get_ldv2_handle(mx_handle_t handle, struct launch_ctx *ctx)
|
|
{
|
|
xpc_socket sock = xpc_socket_create(XPC_SOCKET_REQUESTER);
|
|
xpc_socket_connect_tunnel(sock, handle);
|
|
|
|
mx_handle_t t0, t1;
|
|
mx_tunnel_create(&t0, &t1);
|
|
|
|
int32_t result;
|
|
xpc_error err = horizon_sys_LoaderService_AddPeer(sock, t1, &result);
|
|
mx_handle_close(t1);
|
|
|
|
if (err != XPC_OK) {
|
|
set_error_msg("cannot contact LDv2 loader service: %s", xpc_error_to_string(err));
|
|
mx_handle_close(t0);
|
|
xpc_socket_destroy(sock);
|
|
return ENOLINK;
|
|
}
|
|
|
|
if (result != 0) {
|
|
set_error_msg("cannot connect to LDv2 loader service: code %d", result);
|
|
mx_handle_close(t0);
|
|
xpc_socket_destroy(sock);
|
|
return ENOLINK;
|
|
}
|
|
|
|
xpc_socket_release_tunnel(sock, NULL);
|
|
xpc_socket_connect_tunnel(sock, t0);
|
|
|
|
ctx->ldsvc_version = LDV2_VERSION;
|
|
ctx->ldsvc.sock = sock;
|
|
return 0;
|
|
}
|
|
|
|
static int open_ldv2_handle(struct launch_ctx *ctx)
|
|
{
|
|
int ldsvc_fd = open("/svc/horizon.sys.LoaderService/0", O_RDWR);
|
|
if (ldsvc_fd == -1) {
|
|
set_error_msg("cannot connect to loader service: %s", strerror(errno));
|
|
return errno;
|
|
}
|
|
|
|
mx_handle_t handle;
|
|
mio_release_handle(ldsvc_fd, &handle);
|
|
|
|
xpc_socket sock = xpc_socket_create(XPC_SOCKET_REQUESTER);
|
|
xpc_socket_connect_tunnel(sock, handle);
|
|
|
|
ctx->ldsvc_version = LDV2_VERSION;
|
|
ctx->ldsvc.sock = sock;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int get_ldsvc_handle(struct launch_ctx *ctx)
|
|
{
|
|
size_t nr_handles;
|
|
const mx_bootstrap_handle_t *handles = mx_bootstrap_handle_get_all(&nr_handles);
|
|
|
|
mx_handle_t handle = MX_NULL_HANDLE;
|
|
int version;
|
|
|
|
for (size_t i = 0; i < nr_handles; i++) {
|
|
int type = MX_B_HND_TYPE(handles[i].info);
|
|
int ver = MX_B_HND_ARG(handles[i].info);
|
|
|
|
if (type != MX_B_TUNNEL_LDSVC) {
|
|
continue;
|
|
}
|
|
|
|
handle = handles[i].handle;
|
|
version = ver;
|
|
break;
|
|
}
|
|
|
|
int err = 0;
|
|
if (handle == MX_NULL_HANDLE) {
|
|
err = open_ldv2_handle(ctx);
|
|
} else if (version == LDV1_VERSION) {
|
|
err = get_ldv1_handle(handle, ctx);
|
|
} else if (version == LDV2_VERSION) {
|
|
err = get_ldv2_handle(handle, ctx);
|
|
} else {
|
|
set_error_msg("no connection to loader service");
|
|
return ENOLINK;
|
|
}
|
|
|
|
return err;
|
|
}
|
|
|
|
static mx_bootstrap_msg_t *build_bootstrap_message(
|
|
int argc, const char **argv,
|
|
int envc, const char **envp,
|
|
int n_names, const char **names,
|
|
size_t n_handles, mx_bootstrap_handle_t *handles,
|
|
size_t *out_msg_len, mx_handle_t **out_handles)
|
|
{
|
|
size_t arg_len = 0;
|
|
for (int i = 0; i < argc; i++) {
|
|
arg_len += strlen(argv[i]) + 1;
|
|
}
|
|
|
|
size_t env_len = 0;
|
|
for (int i = 0; i < envc; i++) {
|
|
env_len += strlen(envp[i]) + 1;
|
|
}
|
|
|
|
size_t names_len = 0;
|
|
for (int i = 0; i < n_names; i++) {
|
|
names_len += strlen(names[i]) + 1;
|
|
}
|
|
|
|
size_t msg_len =
|
|
sizeof(mx_bootstrap_msg_t)
|
|
+ (n_handles * sizeof *handles)
|
|
+ arg_len
|
|
+ env_len
|
|
+ names_len;
|
|
|
|
mx_bootstrap_msg_t *msg = malloc(msg_len);
|
|
|
|
msg->args_num = argc;
|
|
msg->environ_num = envc;
|
|
msg->names_num = n_names;
|
|
|
|
msg->args_off = sizeof *msg;
|
|
msg->environ_off = msg->args_off + arg_len;
|
|
msg->names_off = msg->environ_off + env_len;
|
|
msg->handle_info_off = msg->names_off + names_len;
|
|
|
|
char *arg_ptr = (char *)msg + msg->args_off;
|
|
for (int i = 0; i < argc; i++) {
|
|
const char *arg = argv[i];
|
|
for (int ii = 0; arg[ii]; ii++) {
|
|
*arg_ptr++ = arg[ii];
|
|
}
|
|
|
|
*arg_ptr++ = '\0';
|
|
}
|
|
|
|
char *env_ptr = (char *)msg + msg->environ_off;
|
|
for (int i = 0; i < envc; i++) {
|
|
const char *env = envp[i];
|
|
for (int ii = 0; env[ii]; ii++) {
|
|
*env_ptr++ = env[ii];
|
|
}
|
|
|
|
*env_ptr++ = '\0';
|
|
}
|
|
|
|
char *name_ptr = (char *)msg + msg->names_off;
|
|
for (int i = 0; i < n_names; i++) {
|
|
const char *name = names[i];
|
|
for (int ii = 0; name[ii]; ii++) {
|
|
*name_ptr++ = name[ii];
|
|
}
|
|
|
|
*name_ptr++ = '\0';
|
|
}
|
|
|
|
mx_handle_t *handle_buf = calloc(n_handles, sizeof *handle_buf);
|
|
uint32_t *uptr = (uint32_t *)((char *)msg + msg->handle_info_off);
|
|
for (size_t i = 0; i < n_handles; i++) {
|
|
*uptr++ = handles[i].info;
|
|
handle_buf[i] = handles[i].handle;
|
|
}
|
|
|
|
*out_msg_len = msg_len;
|
|
*out_handles = handle_buf;
|
|
return msg;
|
|
}
|
|
|
|
static mx_handle_t *raw_handles(mx_bootstrap_handle_t *handles, size_t count)
|
|
{
|
|
mx_handle_t *out = calloc(count, sizeof *out);
|
|
for (size_t i = 0; i < count; i++) {
|
|
out[i] = handles[i].handle;
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
static int send_interp_bootstrap_message(const struct launch_info *args, struct launch_ctx *ctx)
|
|
{
|
|
mx_handle_t vdso_vmo = mx_bootstrap_handle_get(MX_B_VMO_VDSO);
|
|
mx_handle_t ldsvc = MX_NULL_HANDLE;
|
|
|
|
if (ctx->ldsvc_version == 0) {
|
|
ldsvc = ctx->ldsvc.handle;
|
|
ctx->ldsvc.handle = MX_NULL_HANDLE;
|
|
} else {
|
|
xpc_socket_release_tunnel(ctx->ldsvc.sock, &ldsvc);
|
|
xpc_socket_destroy(ctx->ldsvc.sock);
|
|
ctx->ldsvc.sock = NULL;
|
|
}
|
|
|
|
/* required handles for interpreter:
|
|
* ldsvc
|
|
* exec vmo
|
|
* vdso vmo
|
|
* self task
|
|
* self vmar
|
|
* exec vmar
|
|
*/
|
|
mx_bootstrap_handle_t handles[] = {
|
|
ARG_HANDLE(ctx->new_task, MX_B_HND(MX_B_TASK_SELF, 0)),
|
|
ARG_HANDLE(ctx->exec_vmo, MX_B_HND(MX_B_VMO_EXEC, 0)),
|
|
ARG_HANDLE(ctx->new_vmar, MX_B_HND(MX_B_VMAR_ROOT, 0)),
|
|
ARG_HANDLE(ctx->exec.remote_exec_vmar, MX_B_HND(MX_B_VMAR_EXEC, 0)),
|
|
ARG_HANDLE(vdso_vmo, MX_B_HND(MX_B_VMO_VDSO, 0)),
|
|
ARG_HANDLE(ldsvc, MX_B_HND(MX_B_TUNNEL_LDSVC, ctx->ldsvc_version)),
|
|
ARG_HANDLE(ctx->stdio[0], MX_B_HND(MX_B_FD, 0)),
|
|
ARG_HANDLE(ctx->stdio[1], MX_B_HND(MX_B_FD, 1)),
|
|
ARG_HANDLE(ctx->stdio[2], MX_B_HND(MX_B_FD, 2)),
|
|
};
|
|
|
|
static size_t handle_count = sizeof handles / sizeof handles[0];
|
|
|
|
const char *argv[] = {
|
|
ctx->interp_path,
|
|
args->path,
|
|
};
|
|
const int argc = sizeof argv / sizeof *argv;
|
|
|
|
mx_handle_t *raw_handles;
|
|
|
|
size_t msg_len = 0;
|
|
mx_bootstrap_msg_t *msg = build_bootstrap_message(
|
|
argc, argv,
|
|
0, NULL,
|
|
0, NULL,
|
|
handle_count, handles,
|
|
&msg_len, &raw_handles);
|
|
|
|
mx_tunnel_write_etc(ctx->bootstrap_local,
|
|
MX_TUNNEL_DUPLICATE_HANDLES,
|
|
msg, msg_len,
|
|
raw_handles, handle_count);
|
|
|
|
free(raw_handles);
|
|
free(msg);
|
|
return 0;
|
|
}
|
|
|
|
static int send_exec_bootstrap_message(const struct launch_info *args, struct launch_ctx *ctx)
|
|
{
|
|
mx_bootstrap_handle_t *default_handles =
|
|
ctx->b_handles
|
|
+ ctx->b_handle_count
|
|
- NR_DEFAULT_HANDLES;
|
|
|
|
mx_handle_t vdso_vmo = mx_bootstrap_handle_get(MX_B_VMO_VDSO);
|
|
|
|
/* these handles are sent by launch() in all cases
|
|
* MX_B_TASK_SELF
|
|
* MX_B_VMO_VDSO
|
|
* MX_B_VMO_EXEC
|
|
* MX_B_VMAR_ROOT
|
|
* MX_B_VMAR_EXEC
|
|
* MX_B_TUNNEL_BTSTP
|
|
*/
|
|
int tmp = 0;
|
|
default_handles[tmp++] = ARG_HANDLE(ctx->new_task, MX_B_HND(MX_B_TASK_SELF, 0));
|
|
default_handles[tmp++] = ARG_HANDLE(vdso_vmo, MX_B_HND(MX_B_VMO_VDSO, 0));
|
|
default_handles[tmp++] = ARG_HANDLE(ctx->exec_vmo, MX_B_HND(MX_B_VMO_EXEC, 0));
|
|
default_handles[tmp++] = ARG_HANDLE(ctx->new_vmar, MX_B_HND(MX_B_VMAR_ROOT, 0));
|
|
default_handles[tmp++] = ARG_HANDLE(ctx->exec.remote_exec_vmar, MX_B_HND(MX_B_VMAR_EXEC, 0));
|
|
default_handles[tmp++] = ARG_HANDLE(ctx->bootstrap_remote, MX_B_HND(MX_B_TUNNEL_BTSTP, 0));
|
|
|
|
const char *default_argv[] = { args->path };
|
|
int default_argc = sizeof default_argv / sizeof *default_argv;
|
|
|
|
const char **argv = args->argv;
|
|
int argc = args->argc;
|
|
|
|
if (!argv) {
|
|
argv = default_argv;
|
|
argc = default_argc;
|
|
}
|
|
|
|
mx_handle_t *raw_handles;
|
|
|
|
size_t msg_len;
|
|
mx_bootstrap_msg_t *msg = build_bootstrap_message(
|
|
argc, argv,
|
|
0, NULL,
|
|
ctx->name_count, ctx->names,
|
|
ctx->b_handle_count, ctx->b_handles,
|
|
&msg_len, &raw_handles);
|
|
|
|
mx_tunnel_write_etc(ctx->bootstrap_local,
|
|
MX_TUNNEL_DUPLICATE_HANDLES,
|
|
msg, msg_len,
|
|
raw_handles, ctx->b_handle_count);
|
|
|
|
free(raw_handles);
|
|
free(msg);
|
|
return 0;
|
|
}
|
|
|
|
static int send_bootstrap_messages(const struct launch_info *args, struct launch_ctx *ctx)
|
|
{
|
|
if (ctx->interp_vmo) {
|
|
send_interp_bootstrap_message(args, ctx);
|
|
}
|
|
|
|
send_exec_bootstrap_message(args, ctx);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int launch(const struct launch_info *args, mx_handle_t *out_task)
|
|
{
|
|
struct launch_ctx ctx = {};
|
|
|
|
int err = get_ldsvc_handle(&ctx);
|
|
if (err != 0) {
|
|
fail_cleanup(&ctx);
|
|
RET_ERR(err);
|
|
}
|
|
|
|
if (!args->path) {
|
|
fail_cleanup(&ctx);
|
|
RET_ERR(EFAULT);
|
|
}
|
|
|
|
err = validate_argv(args->argc, args->argv);
|
|
if (err != 0) {
|
|
fail_cleanup(&ctx);
|
|
RET_ERR(err);
|
|
}
|
|
|
|
err = validate_fds(args, &ctx);
|
|
if (err != 0) {
|
|
fail_cleanup(&ctx);
|
|
RET_ERR(err);
|
|
}
|
|
|
|
err = validate_ns(args->flags, args->ns, args->ns_count);
|
|
if (err != 0) {
|
|
fail_cleanup(&ctx);
|
|
RET_ERR(err);
|
|
}
|
|
|
|
err = inherit_namespace(args, &ctx);
|
|
if (err != 0) {
|
|
fail_cleanup(&ctx);
|
|
RET_ERR(err);
|
|
}
|
|
|
|
err = count_handles(args, &ctx);
|
|
if (err != 0) {
|
|
fail_cleanup(&ctx);
|
|
RET_ERR(err);
|
|
}
|
|
|
|
err = collect_handles(args, &ctx);
|
|
if (err != 0) {
|
|
fail_cleanup(&ctx);
|
|
RET_ERR(err);
|
|
}
|
|
|
|
err = collect_names(args, &ctx);
|
|
if (err != 0) {
|
|
fail_cleanup(&ctx);
|
|
RET_ERR(err);
|
|
}
|
|
|
|
int exec_fd = open(args->path, O_RDONLY);
|
|
if (exec_fd == -1) {
|
|
int error = errno;
|
|
fail_cleanup(&ctx);
|
|
set_error_msg("cannot open file '%s': %s", args->path, strerror(error));
|
|
RET_ERR(error);
|
|
}
|
|
|
|
err = mio_map_file(exec_fd, &ctx.exec_vmo);
|
|
close(exec_fd);
|
|
|
|
if (err != 0) {
|
|
fail_cleanup(&ctx);
|
|
set_error_msg("cannot map file '%s': %s", args->path, strerror(-err));
|
|
RET_ERR(-err);
|
|
}
|
|
|
|
err = load_exec(args, &ctx);
|
|
if (err != 0) {
|
|
fail_cleanup(&ctx);
|
|
RET_ERR(err);
|
|
}
|
|
|
|
err = allocate_stack(&ctx);
|
|
if (err != 0) {
|
|
fail_cleanup(&ctx);
|
|
RET_ERR(err);
|
|
}
|
|
|
|
mx_tunnel_create(&ctx.bootstrap_local, &ctx.bootstrap_remote);
|
|
|
|
err = send_bootstrap_messages(args, &ctx);
|
|
if (err != 0) {
|
|
fail_cleanup(&ctx);
|
|
RET_ERR(err);
|
|
}
|
|
|
|
success_cleanup(&ctx);
|
|
set_error_msg("success");
|
|
mx_task_start(ctx.new_task, ctx.entry_point, ctx.stack_ptr, ctx.bootstrap_remote, 0);
|
|
mx_handle_close(ctx.bootstrap_remote);
|
|
|
|
*out_task = ctx.new_task;
|
|
return 0;
|
|
}
|