Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce ELF binfmt loader #79

Open
wants to merge 2 commits into
base: staging
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Makefile.uk
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ APPELFLOADER_CFLAGS-$(CONFIG_APPELFLOADER_DEBUG) += -DUK_DEBUG
APPELFLOADER_SRCS-y += $(APPELFLOADER_BASE)/main.c
APPELFLOADER_SRCS-y += $(APPELFLOADER_BASE)/elf_load.c
APPELFLOADER_SRCS-y += $(APPELFLOADER_BASE)/elf_ctx.c
APPELFLOADER_SRCS-y += $(APPELFLOADER_BASE)/elf_binfmt.c

APPELFLOADER_SRCS-$(CONFIG_APPELFLOADER_BRK) += $(APPELFLOADER_BASE)/syscalls/brk.c
UK_PROVIDED_SYSCALLS-$(CONFIG_APPELFLOADER_BRK) += brk-1
Expand Down
103 changes: 103 additions & 0 deletions elf_binfmt.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
/* SPDX-License-Identifier: BSD-3-Clause */
/* Copyright (c) 2024, Unikraft GmbH and The Unikraft Authors.
* Licensed under the BSD-3-Clause License (the "License").
* You may not use this file except in compliance with the License.
*/

#include <uk/assert.h>
#include <uk/arch/ctx.h>
#include <uk/binfmt.h>
#include <uk/essentials.h>
#include <uk/errptr.h>
#include <uk/init.h>

#if CONFIG_LIBUKRANDOM
#include <uk/random.h>
#endif /* CONFIG_LIBUKRANDOM */

#include "elf_prog.h"

static int uk_binfmt_load_elf(struct uk_binfmt_loader_args *args)
{
struct elf_prog *prog;
__u64 rand[2];
int rc;

UK_ASSERT(args);
UK_ASSERT(args->alloc);

/* TODO Make elf_load_vfs() modular so that we can check the file
* type before we do the actual load. That will also allow us to
* check the parameters before we load the file, as atm we're forced
* to do an elf_unload() on bad parameters.
*/
prog = elf_load_vfs(args->alloc, args->pathname, args->progname);
if (unlikely(PTRISERR(prog))) {
rc = PTR2ERR(prog);
if (rc == -ENOEXEC) {
uk_pr_warn("%s not handled by ELF binfmt loader\n",
args->pathname);
return UK_BINFMT_NOT_HANDLED;
}
uk_pr_err("Could not load ELF (%d)\n", rc);
return rc;
}

/* Save to private data in case we are requested to unload */
args->user = (void *)prog;

uk_pr_debug("%s: ELF loaded to 0x%lx-0x%lx (%lx B)\n", args->progname,
(__u64)prog->vabase, (__u64)prog->vabase + prog->valen,
prog->valen);
uk_pr_debug("%s: Entry at %p\n", args->progname, (void *)prog->entry);

#if CONFIG_LIBUKRANDOM
uk_random_fill_buffer(rand, sizeof(rand));
#else /* !CONFIG_LIBUKRANDOM */
/* Without random numbers, use a hardcoded seed */
uk_pr_warn("%s: Using hard-coded random seed\n", args->progname);
rand[0] = 0xB0B0;
rand[1] = 0xF00D;
#endif /* !CONFIG_LIBUKRANDOM */

rc = elf_arg_env_count(&args->argc, args->argv,
&args->envc, args->envp,
args->stack_size);
if (unlikely(rc < 0)) {
mogasergiu marked this conversation as resolved.
Show resolved Hide resolved
uk_pr_err("Could not load ELF (%d)\n", rc);
elf_unload(prog);
mogasergiu marked this conversation as resolved.
Show resolved Hide resolved
return rc;
}

elf_ctx_init(&args->ctx, prog, args->progname,
args->argc, args->argv,
args->envc, args->envp, rand);

return UK_BINFMT_HANDLED;
}

static int uk_binfmt_unload_elf(struct uk_binfmt_loader_args *args)
{
UK_ASSERT(args);
UK_ASSERT(args->user);

elf_unload((struct elf_prog *)args->user);

return UK_BINFMT_HANDLED;
}

static struct uk_binfmt_loader elf_loader = {
.name = "ELF loader",
.type = UK_BINFMT_LOADER_TYPE_EXEC,
.ops = {
.load = uk_binfmt_load_elf,
.unload = uk_binfmt_unload_elf,
},
};

static int uk_binfmt_elf_loader_init(struct uk_init_ctx *init_ctx __unused)
{
return uk_binfmt_register(&elf_loader);
}

uk_late_initcall(uk_binfmt_elf_loader_init, 0);
59 changes: 47 additions & 12 deletions elf_ctx.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
* header of this file.
*/

#include <limits.h>
#include <string.h>
#include <uk/plat/bootstrap.h>
#include <uk/assert.h>
Expand Down Expand Up @@ -113,29 +114,61 @@ static void infoblk_push(struct ukarch_ctx *ctx, void *buf, __sz len)
((char *)ctx->sp)[len] = '\0';
}

static int envp_count(char *environ[])
int elf_arg_env_count(int *argc, const char **argv,
int *envc, const char **envp,
__sz stack_sz)
{
int envc = 0;
char **env;
const char **a;
__sz len;

if (!environ)
*argc = *envc = len = 0;
if (!argv && !envp)
return 0;

/* count the number of environment variables */
for (env = environ; *env; ++env)
++envc;
/* count argv */
if (argv)
for (a = argv; *a; ++a) {
++*argc;
len += strlen(*a);
}

/* count envp */
if (envp)
for (a = envp; *a; ++a) {
++*envc;
len += strlen(*a);
}

/* POSIX defines the minimum total size of argv + envp to ARG_MAX,
* and leaves the inclusion of NULL terminators, pointers and / or
* alignment bytes as implementation defined. The policy we implement
* is:
*
* - Permit the total length to exceed ARG_MAX as long as it's not
* greater than 1/4 of the stack size. This is essentially the same
* policy as linux (see execve(2)), with the difference that we
* don't apply the MAX_ARG_STRLEN floor of 32 pages.
*
* - Exclude auxv, NULL, alignment bytes from the checked size, as
* these are of fixed size and occupy an insignificant portion of
* the stack.
*/
UK_ASSERT(IS_ALIGNED(stack_sz, 4));
if (unlikely(len > ARG_MAX && len > stack_sz / 4))
return -E2BIG;

return envc;
return len;
}

void elf_ctx_init(struct ukarch_ctx *ctx, struct elf_prog *prog,
const char *argv0, int argc, char *argv[], char *environ[],
const char *argv0, int argc, const char *argv[],
int envc, const char *environ[],
uint64_t rand[2])
{
int i, elfvec_len, envc = envp_count(environ);
int args_count = argc + (argv0 ? 1 : 0);
char *infoblk_argvp[args_count];
char *infoblk_envp[envc];
int i, elfvec_len;

UK_ASSERT(prog);
UK_ASSERT(argv0 || ((argc >= 1) && argv));
Expand Down Expand Up @@ -220,13 +253,15 @@ void elf_ctx_init(struct ukarch_ctx *ctx, struct elf_prog *prog,

if (envc)
for (i = envc - 1; i >= 0; i--) {
infoblk_push(ctx, environ[i], strlen(environ[i]));
infoblk_push(ctx, (void *)environ[i],
mogasergiu marked this conversation as resolved.
Show resolved Hide resolved
strlen(environ[i]));
infoblk_envp[i] = (char *)ctx->sp;
}

if (argc)
for (i = argc; i >= 1; i--) {
infoblk_push(ctx, argv[i - 1], strlen(argv[i - 1]));
infoblk_push(ctx, (void *)argv[i - 1],
strlen(argv[i - 1]));
infoblk_argvp[i] = (char *)ctx->sp;
}

Expand Down
23 changes: 22 additions & 1 deletion elf_prog.h
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,28 @@ void elf_unload(struct elf_prog *elf_prog);
* release/modify while `ctx` and `prog` are in use.
*/
void elf_ctx_init(struct ukarch_ctx *ctx, struct elf_prog *prog,
const char *argv0, int argc, char *argv[], char *environ[],
const char *argv0, int argc, const char *argv[],
int envc, const char *environ[],
uint64_t rand[2]);

/**
* Count the number of args and env vars.
*
* Besides counting, also validates the total number of ars and env vars against
* limits. The policy is:
*
* The total length of args and env vars can exceed the minimum defined by POSIX
* (ARG_MAX) as long as it's not larger than 1/4 of the stack size.
*
* @param argc[out] Args count. Updated by the function.
* @param argv Args vector
* @param envc[out] Environmental variables count. Updated by the function.
* @param envp Environmental variables vector
* @param stack_sz Stack size of the process.
* @return Length on success, -E2BIG if arguments exceed limit.
*/
int elf_arg_env_count(int *argc, const char **argv,
int *envc, const char **envp,
__sz stack_sz);

#endif /* ELF_PROG_H */
14 changes: 11 additions & 3 deletions main.c
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@
#include "elf_prog.h"

#if CONFIG_LIBPOSIX_ENVIRON
extern char **environ;
extern const char **environ;
#else /* !CONFIG_LIBPOSIX_ENVIRON */
#define environ NULL
#endif /* !CONFIG_LIBPOSIX_ENVIRON */
Expand Down Expand Up @@ -180,7 +180,7 @@ static __constructor void _libelf_init(void) {
UK_CRASH("Failed to initialize libelf: Version error");
}

int main(int argc, char *argv[])
int main(int argc, const char *argv[])
{
#if CONFIG_APPELFLOADER_INITRDEXEC
struct ukplat_memregion_desc *img;
Expand All @@ -203,6 +203,7 @@ int main(int argc, char *argv[])
#if CONFIG_APPELFLOADER_VFSEXEC_ENVPWD
char *env_pwd;
#endif /* CONFIG_APPELFLOADER_VFSEXEC_ENVPWD */
int envc;

UK_ASSERT(s);

Expand Down Expand Up @@ -357,8 +358,15 @@ int main(int argc, char *argv[])
#endif /* !CONFIG_LIBUKRANDOM */

uk_pr_debug("%s: Prepare application thread...\n", progname);
ret = elf_arg_env_count(&argc, argv, &envc, environ,
PAGES2BYTES(CONFIG_APPELFLOADER_STACK_NBPAGES));
if (unlikely(ret < 0)) {
uk_pr_err("Args + env size exceeds limit, increase stack size\n");
goto out_free_thread;
}

elf_ctx_init(&app_thread->ctx, prog, progname,
argc, argv, environ, rand);
argc, argv, envc, environ, rand);
app_thread->flags |= UK_THREADF_RUNNABLE;
#if CONFIG_LIBPOSIX_PROCESS
uk_posix_process_create(uk_alloc_get_default(),
Expand Down