From be460ab836792e4102b85129d89410e496e4aa56 Mon Sep 17 00:00:00 2001 From: Michalis Pappas Date: Thu, 11 Apr 2024 15:44:12 +0200 Subject: [PATCH 1/2] Add command-line parameter parsing with limit checks. 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, andlignment bytes from the checked size, as these are of fixed size and occupy an insignificant portion of the stack. Signed-off-by: Michalis Pappas --- elf_ctx.c | 59 +++++++++++++++++++++++++++++++++++++++++++----------- elf_prog.h | 23 ++++++++++++++++++++- main.c | 14 ++++++++++--- 3 files changed, 80 insertions(+), 16 deletions(-) diff --git a/elf_ctx.c b/elf_ctx.c index 1d100e2..39b9021 100644 --- a/elf_ctx.c +++ b/elf_ctx.c @@ -46,6 +46,7 @@ * header of this file. */ +#include #include #include #include @@ -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)); @@ -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], + 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; } diff --git a/elf_prog.h b/elf_prog.h index 98cc1ad..1b83153 100644 --- a/elf_prog.h +++ b/elf_prog.h @@ -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 */ diff --git a/main.c b/main.c index 0ffc12d..f525125 100644 --- a/main.c +++ b/main.c @@ -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 */ @@ -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; @@ -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); @@ -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(), From 9ad500deb9de7e0bfe2d1ed2578b24c19aa76a6a Mon Sep 17 00:00:00 2001 From: Michalis Pappas Date: Thu, 11 Apr 2024 15:45:16 +0200 Subject: [PATCH 2/2] Add ELF binfmt loader Add binfmt loader for ELF. This is the default loader used on execve(). Signed-off-by: Michalis Pappas --- Makefile.uk | 1 + elf_binfmt.c | 103 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+) create mode 100644 elf_binfmt.c diff --git a/Makefile.uk b/Makefile.uk index 5c42642..dcf10f1 100644 --- a/Makefile.uk +++ b/Makefile.uk @@ -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 diff --git a/elf_binfmt.c b/elf_binfmt.c new file mode 100644 index 0000000..bd3577d --- /dev/null +++ b/elf_binfmt.c @@ -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 +#include +#include +#include +#include +#include + +#if CONFIG_LIBUKRANDOM +#include +#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)) { + uk_pr_err("Could not load ELF (%d)\n", rc); + elf_unload(prog); + 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);