From 9e0bbd07ef5affa403d476b0bcdabfc5a5684cf9 Mon Sep 17 00:00:00 2001 From: Samuel Holland Date: Sun, 14 Jan 2018 20:30:39 -0600 Subject: loader: Several improvements * Handle programs that have a DT_NEEDED entry for glibc's ld.so. * Handle when LD_PRELOAD is already set. * Use the --argv0 option to properly set argv[0] in the target program. * Ensure the the argument list is terminated with a NULL sentinel. * Document the details of the loader's implementation. [NOTE: Better commit summary?] Signed-off-by: Samuel Holland --- loader/loader.c | 93 ++++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 75 insertions(+), 18 deletions(-) diff --git a/loader/loader.c b/loader/loader.c index 979937f..53a2c38 100644 --- a/loader/loader.c +++ b/loader/loader.c @@ -1,5 +1,6 @@ /* * Copyright (c) 2017 William Pitcock + * Copyright (c) 2018 Samuel Holland * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -10,43 +11,99 @@ * from the use of this software. */ -#include -#include -#include -#include -#include +#include /* PATH_MAX */ +#include /* NULL */ +#include /* fputs, fwrite, stderr */ +#include /* calloc, EXIT_FAILURE, getenv */ +#include /* strcpy, strlcpy */ +#include /* execve, ssize_t */ #ifndef PATH_MAX #define PATH_MAX 16384 #endif +#ifndef LIBGCOMPAT +#error LIBGCOMPAT must be defined +#endif + #ifndef LINKER #error LINKER must be defined #endif -#ifndef LIBGCOMPAT -#error LIBGCOMPAT must be defined +#ifndef LOADER +#error LOADER must be defined #endif +/* + * Given the argv { "foo", "bar", "baz" }, and the environment variable + * "LD_PRELOAD=/another/preload.so", the new argv will be { + * "ld-linux-$ARCH.so.N", + * "--argv0", + * "foo", + * "--preload", + * "/path/to/libgcompat.so /another/preload.so", + * "/path/to/foo", + * "bar", + * "baz" + * }. + * + * NOTE: The new argv needs 6 more entries than the original argc: five for + * arguments added at the beginning, and one for the NULL sentinel at the end + * not included in argc. Because calloc() is used, the sentinel is not + * explicitly set on its own, but *it is still there*! + * + * NOTE: The name given in argv[0] to musl *must* match glibc's ld.so filename. + * Many glibc-linked programs have an explicit dependency on its ld.so, but the + * file placed at that path (this loader) is not a real shared library. If musl + * tries to load this file as a shared library, it will fail. Thankfully, since + * musl treats argv[0] as "its" name when run directly, it considers a library + * by that name to already be loaded, and ignores the problematic dependency. + * + * NOTE: LD_PRELOAD entries are processed in order, and the environment variable + * is ignored if the parameter is given. In case the glibc-linked program needs + * another preloaded library, it should be appended to the argument so it gets + * loaded after libgcompat. Leave the environment variable as is, so if the + * program runs other glibc-linked programs, the same transformation gets + * applied there as well. + * + * NOTE: In order for the program to be able to re-exec itself it needs the + * fully-resolved path from /proc/self/exe ("target" below). This path is given + * to musl on the command line in new_argv[5], but it is not directly available + * to the program. An intercepted readlink("/proc/self/exe") must read it from + * /proc/self/cmdline. Thus, if its position in new_argv changes, that function + * must also be updated to match. + */ int main(int argc, char *argv[], char *envp[]) { - size_t i = 0; + char **new_argv = calloc(argc + 6, sizeof(char *)); + char preload[PATH_MAX]; char target[PATH_MAX]; - char **new_argv = calloc(sizeof(char *), argc + 5); + ssize_t i, j, len; - memset(target, 0, sizeof(target)); - if (readlink("/proc/self/exe", target, sizeof(target)) < 0) { + strcpy(preload, LIBGCOMPAT " "); + if (getenv("LD_PRELOAD") != NULL) { + len = strlcat(preload, getenv("LD_PRELOAD"), sizeof(preload)); + if ((size_t) len >= sizeof(preload)) { + fputs("too many preloaded libraries", stderr); + return EXIT_FAILURE; + } + } + + len = readlink("/proc/self/exe", target, sizeof(target)); + if (len < 0 || len == sizeof(target)) { perror("readlink"); return EXIT_FAILURE; } + target[len] = '\0'; - new_argv[0] = argv[0]; - new_argv[1] = "--preload"; - new_argv[2] = LIBGCOMPAT; - new_argv[3] = target; - - for (i = 1; i < argc; i++) { - new_argv[3 + i] = argv[i]; + new_argv[0] = LOADER; + new_argv[1] = "--argv0"; + new_argv[2] = argv[0]; + new_argv[3] = "--preload"; + new_argv[4] = preload; + new_argv[5] = target; + for (i = 6, j = 1; j < argc; ++i, ++j) { + new_argv[i] = argv[j]; } execve(LINKER, new_argv, envp); -- cgit v1.2.3-60-g2f50