#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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; }