From 18a206c81d9743481e364384affd43306911283d Mon Sep 17 00:00:00 2001 From: Elliott Hughes Date: Mon, 29 Oct 2012 17:37:13 -0700 Subject: [PATCH] More dynamic linker cleanup. I still want to break linker_format out into its own library so we can reuse it for malloc debugging and so forth. (There are many similar pieces of code in bionic, but the linker's one seems to be the most complete/functional.) Change-Id: If3721853d28937c8e821ca1d23cf200e228a409a --- linker/Android.mk | 11 +- linker/{debugger.c => debugger.cpp} | 85 +++---- linker/linker.cpp | 73 ++---- linker/linker.h | 12 +- linker/linker_debug.h | 8 - linker/linker_environ.c | 202 ---------------- linker/linker_environ.cpp | 222 ++++++++++++++++++ linker/linker_environ.h | 41 ++-- linker/{linker_format.c => linker_format.cpp} | 53 +++-- linker/linker_format.h | 18 +- linker/{linker_phdr.c => linker_phdr.cpp} | 1 - linker/linker_phdr.h | 10 - linker/{rt.c => rt.cpp} | 5 +- 13 files changed, 336 insertions(+), 405 deletions(-) rename linker/{debugger.c => debugger.cpp} (85%) delete mode 100644 linker/linker_environ.c create mode 100644 linker/linker_environ.cpp rename linker/{linker_format.c => linker_format.cpp} (96%) rename linker/{linker_phdr.c => linker_phdr.cpp} (99%) rename linker/{rt.c => rt.cpp} (89%) diff --git a/linker/Android.mk b/linker/Android.mk index 503da861e..e3ac7e9a0 100644 --- a/linker/Android.mk +++ b/linker/Android.mk @@ -3,20 +3,19 @@ include $(CLEAR_VARS) LOCAL_SRC_FILES:= \ arch/$(TARGET_ARCH)/begin.S \ - debugger.c \ + debugger.cpp \ dlfcn.cpp \ linker.cpp \ - linker_environ.c \ - linker_format.c \ - linker_phdr.c \ - rt.c + linker_environ.cpp \ + linker_format.cpp \ + linker_phdr.cpp \ + rt.cpp LOCAL_LDFLAGS := -shared LOCAL_CFLAGS += -fno-stack-protector \ -Wstrict-overflow=5 \ -fvisibility=hidden \ - -std=gnu99 \ -Wall -Wextra # Set LINKER_DEBUG to either 1 or 0 diff --git a/linker/debugger.c b/linker/debugger.cpp similarity index 85% rename from linker/debugger.c rename to linker/debugger.cpp index e4d4ae901..bba89b859 100644 --- a/linker/debugger.c +++ b/linker/debugger.cpp @@ -26,6 +26,9 @@ * SUCH DAMAGE. */ +#include "linker.h" +#include "linker_format.h" + #include #include #include @@ -36,45 +39,35 @@ #include #include -extern int tgkill(int tgid, int tid, int sig); +#include -void notify_gdb_of_libraries(); +extern "C" int tgkill(int tgid, int tid, int sig); #define DEBUGGER_SOCKET_NAME "android:debuggerd" -typedef enum { +enum debugger_action_t { // dump a crash DEBUGGER_ACTION_CRASH, // dump a tombstone file DEBUGGER_ACTION_DUMP_TOMBSTONE, // dump a backtrace only back to the socket DEBUGGER_ACTION_DUMP_BACKTRACE, -} debugger_action_t; +}; /* message sent over the socket */ -typedef struct { +struct debugger_msg_t { debugger_action_t action; pid_t tid; -} debugger_msg_t; - -#define RETRY_ON_EINTR(ret,cond) \ - do { \ - ret = (cond); \ - } while (ret < 0 && errno == EINTR) +}; // see man(2) prctl, specifically the section about PR_GET_NAME #define MAX_TASK_NAME_LEN (16) -static int socket_abstract_client(const char *name, int type) -{ - struct sockaddr_un addr; - size_t namelen; - socklen_t alen; - int s, err; - - namelen = strlen(name); +static int socket_abstract_client(const char* name, int type) { + sockaddr_un addr; // Test with length +1 for the *initial* '\0'. + size_t namelen = strlen(name); if ((namelen + 1) > sizeof(addr.sun_path)) { errno = EINVAL; return -1; @@ -86,18 +79,20 @@ static int socket_abstract_client(const char *name, int type) * Note: The path in this case is *not* supposed to be * '\0'-terminated. ("man 7 unix" for the gory details.) */ - memset (&addr, 0, sizeof addr); + memset(&addr, 0, sizeof(addr)); addr.sun_family = AF_LOCAL; addr.sun_path[0] = 0; memcpy(addr.sun_path + 1, name, namelen); - alen = namelen + offsetof(struct sockaddr_un, sun_path) + 1; + socklen_t alen = namelen + offsetof(sockaddr_un, sun_path) + 1; - s = socket(AF_LOCAL, type, 0); - if(s < 0) return -1; + int s = socket(AF_LOCAL, type, 0); + if (s == -1) { + return -1; + } - RETRY_ON_EINTR(err,connect(s, (struct sockaddr *) &addr, alen)); - if (err < 0) { + int err = TEMP_FAILURE_RETRY(connect(s, (sockaddr*) &addr, alen)); + if (err == -1) { close(s); s = -1; } @@ -105,9 +100,6 @@ static int socket_abstract_client(const char *name, int type) return s; } -#include "linker_format.h" -#include <../libc/private/logd.h> - /* * Writes a summary of the signal to the log file. We do this so that, if * for some reason we're not able to contact debuggerd, there is still some @@ -116,15 +108,9 @@ static int socket_abstract_client(const char *name, int type) * We could be here as a result of native heap corruption, or while a * mutex is being held, so we don't want to use any libc functions that * could allocate memory or hold a lock. - * - * "info" will be NULL if the siginfo_t information was not available. */ -static void logSignalSummary(int signum, const siginfo_t* info) -{ - char buffer[128]; - char threadname[MAX_TASK_NAME_LEN + 1]; // one more for termination - - char* signame; +static void logSignalSummary(int signum, const siginfo_t* info) { + const char* signame; switch (signum) { case SIGILL: signame = "SIGILL"; break; case SIGABRT: signame = "SIGABRT"; break; @@ -138,6 +124,7 @@ static void logSignalSummary(int signum, const siginfo_t* info) default: signame = "???"; break; } + char threadname[MAX_TASK_NAME_LEN + 1]; // one more for termination if (prctl(PR_GET_NAME, (unsigned long)threadname, 0, 0, 0) != 0) { strcpy(threadname, ""); } else { @@ -145,6 +132,9 @@ static void logSignalSummary(int signum, const siginfo_t* info) // implies that 16 byte names are not. threadname[MAX_TASK_NAME_LEN] = 0; } + + char buffer[128]; + // "info" will be NULL if the siginfo_t information was not available. if (info != NULL) { format_buffer(buffer, sizeof(buffer), "Fatal signal %d (%s) at 0x%08x (code=%d), thread %d (%s)", @@ -161,8 +151,7 @@ static void logSignalSummary(int signum, const siginfo_t* info) /* * Returns true if the handler for signal "signum" has SA_SIGINFO set. */ -static bool haveSiginfo(int signum) -{ +static bool haveSiginfo(int signum) { struct sigaction oldact, newact; memset(&newact, 0, sizeof(newact)); @@ -188,11 +177,8 @@ static bool haveSiginfo(int signum) * Catches fatal signals so we can ask debuggerd to ptrace us before * we crash. */ -void debugger_signal_handler(int n, siginfo_t* info, void* unused __attribute__((unused))) -{ +void debugger_signal_handler(int n, siginfo_t* info, void*) { char msgbuf[128]; - unsigned tid; - int s; /* * It's possible somebody cleared the SA_SIGINFO flag, which would mean @@ -204,8 +190,8 @@ void debugger_signal_handler(int n, siginfo_t* info, void* unused __attribute__( logSignalSummary(n, info); - tid = gettid(); - s = socket_abstract_client(DEBUGGER_SOCKET_NAME, SOCK_STREAM); + pid_t tid = gettid(); + int s = socket_abstract_client(DEBUGGER_SOCKET_NAME, SOCK_STREAM); if (s >= 0) { /* debugger knows our pid from the credentials on the @@ -217,14 +203,14 @@ void debugger_signal_handler(int n, siginfo_t* info, void* unused __attribute__( debugger_msg_t msg; msg.action = DEBUGGER_ACTION_CRASH; msg.tid = tid; - RETRY_ON_EINTR(ret, write(s, &msg, sizeof(msg))); + ret = TEMP_FAILURE_RETRY(write(s, &msg, sizeof(msg))); if (ret == sizeof(msg)) { /* if the write failed, there is no point to read on * the file descriptor. */ - RETRY_ON_EINTR(ret, read(s, &tid, 1)); - int savedErrno = errno; + ret = TEMP_FAILURE_RETRY(read(s, &tid, 1)); + int saved_errno = errno; notify_gdb_of_libraries(); - errno = savedErrno; + errno = saved_errno; } if (ret < 0) { @@ -266,8 +252,7 @@ void debugger_signal_handler(int n, siginfo_t* info, void* unused __attribute__( } } -void debugger_init() -{ +void debugger_init() { struct sigaction act; memset(&act, 0, sizeof(act)); act.sa_sigaction = debugger_signal_handler; diff --git a/linker/linker.cpp b/linker/linker.cpp index 4ffa1e74f..4c52f3d58 100644 --- a/linker/linker.cpp +++ b/linker/linker.cpp @@ -93,7 +93,6 @@ static soinfo *sonext = &libdl_info; static soinfo *somain; /* main process, always the one after libdl_info */ #endif - static char ldpaths_buf[LDPATH_BUFSIZE]; static const char *ldpaths[LDPATH_MAX + 1]; @@ -108,9 +107,6 @@ int debug_verbosity; static int pid; -/* This boolean is set if the program being loaded is setuid */ -static bool program_is_setuid; - enum RelocationKind { kRelocAbsolute = 0, kRelocRelative, @@ -257,8 +253,7 @@ static void notify_gdb_of_unload(soinfo* info) { rtld_db_dlactivity(); } -extern "C" void notify_gdb_of_libraries() -{ +void notify_gdb_of_libraries() { _r_debug.r_state = RT_ADD; rtld_db_dlactivity(); _r_debug.r_state = RT_CONSISTENT; @@ -1689,7 +1684,7 @@ static int soinfo_link_image(soinfo *si) ftp://ftp.freebsd.org/pub/FreeBSD/CERT/advisories/FreeBSD-SA-02:23.stdio.asc */ - if (program_is_setuid) { + if (get_AT_SECURE()) { nullify_closed_stdio(); } notify_gdb_of_load(si); @@ -1750,11 +1745,6 @@ static unsigned __linker_init_post_relocation(unsigned **elfdata, unsigned linke int argc = (int) *elfdata; char **argv = (char**) (elfdata + 1); unsigned *vecs = (unsigned*) (argv + argc + 1); - unsigned *v; - soinfo *si; - int i; - const char *ldpath_env = NULL; - const char *ldpreload_env = NULL; /* NOTE: we store the elfdata pointer on a special location * of the temporary TLS area in order to pass it to @@ -1773,53 +1763,36 @@ static unsigned __linker_init_post_relocation(unsigned **elfdata, unsigned linke gettimeofday(&t0, 0); #endif - /* Initialize environment functions, and get to the ELF aux vectors table */ + // Initialize environment functions, and get to the ELF aux vectors table. vecs = linker_env_init(vecs); - /* Check auxv for AT_SECURE first to see if program is setuid, setgid, - has file caps, or caused a SELinux/AppArmor domain transition. */ - for (v = vecs; v[0]; v += 2) { - if (v[0] == AT_SECURE) { - /* kernel told us whether to enable secure mode */ - program_is_setuid = v[1]; - goto sanitize; - } - } - - /* Kernel did not provide AT_SECURE - fall back on legacy test. */ - program_is_setuid = (getuid() != geteuid()) || (getgid() != getegid()); - -sanitize: - /* Sanitize environment if we're loading a setuid program */ - if (program_is_setuid) { - linker_env_secure(); - } - debugger_init(); - /* Get a few environment variables */ - { + // Get a few environment variables. #if LINKER_DEBUG - const char* env; - env = linker_env_get("DEBUG"); /* XXX: TODO: Change to LD_DEBUG */ - if (env) + { + const char* env = linker_env_get("LD_DEBUG"); + if (env != NULL) { debug_verbosity = atoi(env); + } + } #endif - /* Normally, these are cleaned by linker_env_secure, but the test - * against program_is_setuid doesn't cost us anything */ - if (!program_is_setuid) { - ldpath_env = linker_env_get("LD_LIBRARY_PATH"); - ldpreload_env = linker_env_get("LD_PRELOAD"); - } + // Normally, these are cleaned by linker_env_init, but the test + // doesn't cost us anything. + const char* ldpath_env = NULL; + const char* ldpreload_env = NULL; + if (!get_AT_SECURE()) { + ldpath_env = linker_env_get("LD_LIBRARY_PATH"); + ldpreload_env = linker_env_get("LD_PRELOAD"); } INFO("[ android linker & debugger ]\n"); DEBUG("%5d elfdata @ 0x%08x\n", pid, (unsigned)elfdata); - si = soinfo_alloc(argv[0]); - if(si == 0) { - exit(-1); + soinfo* si = soinfo_alloc(argv[0]); + if (si == NULL) { + exit(EXIT_FAILURE); } /* bootstrap the link map, the main exe always needs to be first */ @@ -1858,7 +1831,7 @@ sanitize: insert_soinfo_into_debug_map(&linker_soinfo); /* extract information passed from the kernel */ - while(vecs[0] != 0){ + while (vecs[0] != 0){ switch(vecs[0]){ case AT_PHDR: si->phdr = (Elf32_Phdr*) vecs[1]; @@ -1899,12 +1872,12 @@ sanitize: char errmsg[] = "CANNOT LINK EXECUTABLE\n"; write(2, __linker_dl_err_buf, strlen(__linker_dl_err_buf)); write(2, errmsg, sizeof(errmsg)); - exit(-1); + exit(EXIT_FAILURE); } soinfo_call_preinit_constructors(si); - for(i = 0; preloads[i] != NULL; i++) { + for (size_t i = 0; preloads[i] != NULL; ++i) { soinfo_call_constructors(preloads[i]); } @@ -2049,7 +2022,7 @@ extern "C" unsigned __linker_init(unsigned **elfdata) { // // This situation should never occur unless the linker itself // is corrupt. - exit(-1); + exit(EXIT_FAILURE); } // We have successfully fixed our own relocations. It's safe to run diff --git a/linker/linker.h b/linker/linker.h index 8ed433cf1..54563bb80 100644 --- a/linker/linker.h +++ b/linker/linker.h @@ -34,9 +34,6 @@ #include #include -#ifdef __cplusplus -extern "C" { -#endif #include #undef PAGE_MASK @@ -89,8 +86,6 @@ struct r_debug uintptr_t r_ldbase; }; -typedef struct soinfo soinfo; - #define FLAG_LINKED 0x00000001 #define FLAG_ERROR 0x00000002 #define FLAG_EXE 0x00000004 // The main executable @@ -98,8 +93,7 @@ typedef struct soinfo soinfo; #define SOINFO_NAME_LEN 128 -struct soinfo -{ +struct soinfo { char name[SOINFO_NAME_LEN]; const Elf32_Phdr *phdr; int phnum; @@ -232,8 +226,6 @@ Elf32_Sym *soinfo_find_symbol(soinfo* si, const void *addr); Elf32_Sym *soinfo_lookup(soinfo *si, const char *name); void soinfo_call_constructors(soinfo *si); -#ifdef __cplusplus -}; -#endif +extern "C" void notify_gdb_of_libraries(); #endif diff --git a/linker/linker_debug.h b/linker/linker_debug.h index 48a7abf0d..b9dfe34f9 100644 --- a/linker/linker_debug.h +++ b/linker/linker_debug.h @@ -62,10 +62,6 @@ #if LINKER_DEBUG #include "linker_format.h" -#ifdef __cplusplus -extern "C" { -#endif - extern int debug_verbosity; #if LINKER_DEBUG_TO_LOG extern int format_log(int, const char *, const char *, ...); @@ -81,10 +77,6 @@ extern int format_fd(int, const char *, ...); } while (0) #endif /* !LINKER_DEBUG_TO_LOG */ -#ifdef __cplusplus -}; -#endif - #else /* !LINKER_DEBUG */ #define _PRINTVF(v,f,x...) do {} while(0) #endif /* LINKER_DEBUG */ diff --git a/linker/linker_environ.c b/linker/linker_environ.c deleted file mode 100644 index fadcb6086..000000000 --- a/linker/linker_environ.c +++ /dev/null @@ -1,202 +0,0 @@ -/* - * Copyright (C) 2010 The Android Open Source Project - * All rights reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS - * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE - * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, - * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS - * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED - * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - */ -#include "linker_environ.h" -#include - -static char** _envp; - -/* Returns 1 if 'str' points to a valid environment variable definition. - * For now, we check that: - * - It is smaller than MAX_ENV_LEN (to detect non-zero terminated strings) - * - It contains at least one equal sign that is not the first character - */ -static int -_is_valid_definition(const char* str) -{ - int pos = 0; - int first_equal_pos = -1; - - /* According to its sources, the kernel uses 32*PAGE_SIZE by default - * as the maximum size for an env. variable definition. - */ - const int MAX_ENV_LEN = 32*4096; - - if (str == NULL) - return 0; - - /* Parse the string, looking for the first '=' there, and its size */ - do { - if (str[pos] == '\0') - break; - if (str[pos] == '=' && first_equal_pos < 0) - first_equal_pos = pos; - pos++; - } while (pos < MAX_ENV_LEN); - - if (pos >= MAX_ENV_LEN) /* Too large */ - return 0; - - if (first_equal_pos < 1) /* No equal sign, or it is the first character */ - return 0; - - return 1; -} - -unsigned* -linker_env_init(unsigned* vecs) -{ - /* Store environment pointer - can't be NULL */ - _envp = (char**) vecs; - - /* Skip over all definitions */ - while (vecs[0] != 0) - vecs++; - /* The end of the environment block is marked by two NULL pointers */ - vecs++; - - /* As a sanity check, we're going to remove all invalid variable - * definitions from the environment array. - */ - { - char** readp = _envp; - char** writep = _envp; - for ( ; readp[0] != NULL; readp++ ) { - if (!_is_valid_definition(readp[0])) - continue; - writep[0] = readp[0]; - writep++; - } - writep[0] = NULL; - } - - /* Return the address of the aux vectors table */ - return vecs; -} - -/* Check if the environment variable definition at 'envstr' - * starts with '=', and if so return the address of the - * first character after the equal sign. Otherwise return NULL. - */ -static char* -env_match(char* envstr, const char* name) -{ - size_t cnt = 0; - - while (envstr[cnt] == name[cnt] && name[cnt] != '\0') - cnt++; - - if (name[cnt] == '\0' && envstr[cnt] == '=') - return envstr + cnt + 1; - - return NULL; -} - -#define MAX_ENV_LEN (16*4096) - -const char* -linker_env_get(const char* name) -{ - char** readp = _envp; - - if (name == NULL || name[0] == '\0') - return NULL; - - for ( ; readp[0] != NULL; readp++ ) { - char* val = env_match(readp[0], name); - if (val != NULL) { - /* Return NULL for empty strings, or if it is too large */ - if (val[0] == '\0') - val = NULL; - return val; - } - } - return NULL; -} - - -void -linker_env_unset(const char* name) -{ - char** readp = _envp; - char** writep = readp; - - if (name == NULL || name[0] == '\0') - return; - - for ( ; readp[0] != NULL; readp++ ) { - if (env_match(readp[0], name)) - continue; - writep[0] = readp[0]; - writep++; - } - /* end list with a NULL */ - writep[0] = NULL; -} - - - -/* Remove unsafe environment variables. This should be used when - * running setuid programs. */ -void -linker_env_secure(void) -{ - /* The same list than GLibc at this point */ - static const char* const unsec_vars[] = { - "GCONV_PATH", - "GETCONF_DIR", - "HOSTALIASES", - "LD_AUDIT", - "LD_DEBUG", - "LD_DEBUG_OUTPUT", - "LD_DYNAMIC_WEAK", - "LD_LIBRARY_PATH", - "LD_ORIGIN_PATH", - "LD_PRELOAD", - "LD_PROFILE", - "LD_SHOW_AUXV", - "LD_USE_LOAD_BIAS", - "LOCALDOMAIN", - "LOCPATH", - "MALLOC_TRACE", - "MALLOC_CHECK_", - "NIS_PATH", - "NLSPATH", - "RESOLV_HOST_CONF", - "RES_OPTIONS", - "TMPDIR", - "TZDIR", - "LD_AOUT_LIBRARY_PATH", - "LD_AOUT_PRELOAD", - NULL - }; - - int count; - for (count = 0; unsec_vars[count] != NULL; count++) { - linker_env_unset(unsec_vars[count]); - } -} diff --git a/linker/linker_environ.cpp b/linker/linker_environ.cpp new file mode 100644 index 000000000..357be6d58 --- /dev/null +++ b/linker/linker_environ.cpp @@ -0,0 +1,222 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "linker_environ.h" + +#include +#include +#include +#include + +static char** _envp; +static bool _AT_SECURE_value = true; + +bool get_AT_SECURE() { + return _AT_SECURE_value; +} + +/* Returns 1 if 'str' points to a valid environment variable definition. + * For now, we check that: + * - It is smaller than MAX_ENV_LEN (to detect non-zero terminated strings) + * - It contains at least one equal sign that is not the first character + */ +static int _is_valid_definition(const char* str) { + int pos = 0; + int first_equal_pos = -1; + + // According to its sources, the kernel uses 32*PAGE_SIZE by default + // as the maximum size for an env. variable definition. + const int MAX_ENV_LEN = 32*4096; + + if (str == NULL) { + return 0; + } + + // Parse the string, looking for the first '=' there, and its size. + while (pos < MAX_ENV_LEN) { + if (str[pos] == '\0') { + break; + } + if (str[pos] == '=' && first_equal_pos < 0) { + first_equal_pos = pos; + } + pos++; + } + + if (pos >= MAX_ENV_LEN) { + return 0; // Too large. + } + + if (first_equal_pos < 1) { + return 0; // No equals sign, or it's the first character. + } + + return 1; +} + +static void __init_AT_SECURE(unsigned* auxv) { + // Check auxv for AT_SECURE first to see if program is setuid, setgid, + // has file caps, or caused a SELinux/AppArmor domain transition. + for (unsigned* v = auxv; v[0]; v += 2) { + if (v[0] == AT_SECURE) { + // Kernel told us whether to enable secure mode. + _AT_SECURE_value = v[1]; + return; + } + } + + // We don't support ancient kernels. + const char* msg = "FATAL: kernel did not supply AT_SECURE\n"; + write(2, msg, strlen(msg)); + exit(EXIT_FAILURE); +} + +static void __remove_unsafe_environment_variables() { + // None of these should be allowed in setuid programs. + static const char* const UNSAFE_VARIABLE_NAMES[] = { + "GCONV_PATH", + "GETCONF_DIR", + "HOSTALIASES", + "LD_AOUT_LIBRARY_PATH", + "LD_AOUT_PRELOAD", + "LD_AUDIT", + "LD_DEBUG", + "LD_DEBUG_OUTPUT", + "LD_DYNAMIC_WEAK", + "LD_LIBRARY_PATH", + "LD_ORIGIN_PATH", + "LD_PRELOAD", + "LD_PROFILE", + "LD_SHOW_AUXV", + "LD_USE_LOAD_BIAS", + "LOCALDOMAIN", + "LOCPATH", + "MALLOC_CHECK_", + "MALLOC_TRACE", + "NIS_PATH", + "NLSPATH", + "RESOLV_HOST_CONF", + "RES_OPTIONS", + "TMPDIR", + "TZDIR", + NULL + }; + for (size_t i = 0; UNSAFE_VARIABLE_NAMES[i] != NULL; ++i) { + linker_env_unset(UNSAFE_VARIABLE_NAMES[i]); + } +} + +static void __remove_invalid_environment_variables() { + char** src = _envp; + char** dst = _envp; + for (; src[0] != NULL; ++src) { + if (!_is_valid_definition(src[0])) { + continue; + } + dst[0] = src[0]; + ++dst; + } + dst[0] = NULL; +} + +unsigned* linker_env_init(unsigned* environment_and_aux_vectors) { + // Store environment pointer - can't be NULL. + _envp = reinterpret_cast(environment_and_aux_vectors); + + // Skip over all environment variable definitions. + // The end of the environment block is marked by two NULL pointers. + unsigned* aux_vectors = environment_and_aux_vectors; + while (aux_vectors[0] != 0) { + ++aux_vectors; + } + ++aux_vectors; + + __remove_invalid_environment_variables(); + __init_AT_SECURE(aux_vectors); + + // Sanitize environment if we're loading a setuid program. + if (get_AT_SECURE()) { + __remove_unsafe_environment_variables(); + } + + return aux_vectors; +} + +/* Check if the environment variable definition at 'envstr' + * starts with '=', and if so return the address of the + * first character after the equal sign. Otherwise return NULL. + */ +static char* env_match(char* envstr, const char* name) { + size_t i = 0; + + while (envstr[i] == name[i] && name[i] != '\0') { + ++i; + } + + if (name[i] == '\0' && envstr[i] == '=') { + return envstr + i + 1; + } + + return NULL; +} + +const char* linker_env_get(const char* name) { + if (name == NULL || name[0] == '\0') { + return NULL; + } + + for (char** p = _envp; p[0] != NULL; ++p) { + char* val = env_match(p[0], name); + if (val != NULL) { + if (val[0] == '\0') { + return NULL; // Return NULL for empty strings. + } + return val; + } + } + return NULL; +} + +void linker_env_unset(const char* name) { + char** readp = _envp; + char** writep = readp; + + if (name == NULL || name[0] == '\0') { + return; + } + + for ( ; readp[0] != NULL; readp++ ) { + if (env_match(readp[0], name)) { + continue; + } + writep[0] = readp[0]; + writep++; + } + /* end list with a NULL */ + writep[0] = NULL; +} diff --git a/linker/linker_environ.h b/linker/linker_environ.h index d5f75a1f8..a0bd69f57 100644 --- a/linker/linker_environ.h +++ b/linker/linker_environ.h @@ -25,38 +25,27 @@ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ + #ifndef LINKER_ENVIRON_H #define LINKER_ENVIRON_H -#ifdef __cplusplus -extern "C" { -#endif +// Call this function before anything else. 'environment_and_aux_vectors' +// must point to the environment block in the ELF data block. The function +// returns the start of the aux vectors after the environment block. +extern unsigned* linker_env_init(unsigned* environment_and_aux_vectors); -/* Call this function before anything else. 'vecs' must be the pointer - * to the environment block in the ELF data block. The function returns - * the start of the aux vectors after the env block. - */ -extern unsigned* linker_env_init(unsigned* vecs); +// Unset a given environment variable. In case the variable is defined +// multiple times, unset all instances. This modifies the environment +// block, so any pointer returned by linker_env_get() after this call +// might become invalid. +extern void linker_env_unset(const char* name); -/* Unset a given environment variable. In case the variable is defined - * multiple times, unset all instances. This modifies the environment - * block, so any pointer returned by linker_env_get() after this call - * might become invalid */ -extern void linker_env_unset(const char* name); - - -/* Returns the value of environment variable 'name' if defined and not - * empty, or NULL otherwise. Note that the returned pointer may become - * invalid if linker_env_unset() or linker_env_secure() are called - * after this function. */ +// Returns the value of environment variable 'name' if defined and not +// empty, or NULL otherwise. Note that the returned pointer may become +// invalid if linker_env_unset() is called after this function. extern const char* linker_env_get(const char* name); -/* Remove insecure environment variables. This should be used when - * running setuid programs. */ -extern void linker_env_secure(void); - -#ifdef __cplusplus -}; -#endif +// Returns the value of this program's AT_SECURE variable. +extern bool get_AT_SECURE(); #endif /* LINKER_ENVIRON_H */ diff --git a/linker/linker_format.c b/linker/linker_format.cpp similarity index 96% rename from linker/linker_format.c rename to linker/linker_format.cpp index f60e2593d..cc70a0345 100644 --- a/linker/linker_format.c +++ b/linker/linker_format.cpp @@ -26,6 +26,7 @@ * SUCH DAMAGE. */ +#include #include #include #include @@ -43,14 +44,12 @@ /*** Generic output sink ***/ -typedef struct { - void *opaque; - void (*send)(void *opaque, const char *data, int len); -} Out; +struct Out { + void *opaque; + void (*send)(void *opaque, const char *data, int len); +}; -static void -out_send(Out *o, const void *data, size_t len) -{ +static void out_send(Out *o, const char *data, size_t len) { o->send(o->opaque, data, (int)len); } @@ -72,27 +71,25 @@ out_send_repeat(Out *o, char ch, int count) } /* forward declaration */ -static void -out_vformat(Out *o, const char *format, va_list args); +static void out_vformat(Out* o, const char* format, va_list args); /*** Bounded buffer output ***/ -typedef struct { - Out out[1]; - char *buffer; - char *pos; - char *end; - int total; -} BufOut; +struct BufOut { + Out out[1]; + char *buffer; + char *pos; + char *end; + int total; +}; -static void -buf_out_send(void *opaque, const char *data, int len) -{ - BufOut *bo = opaque; +static void buf_out_send(void *opaque, const char *data, int len) { + BufOut *bo = reinterpret_cast(opaque); - if (len < 0) + if (len < 0) { len = strlen(data); + } bo->total += len; @@ -194,11 +191,11 @@ snprintf(char* buff, size_t bufsize, const char* format, ...) /*** File descriptor output ***/ -typedef struct { - Out out[1]; - int fd; - int total; -} FdOut; +struct FdOut { + Out out[1]; + int fd; + int total; +}; static void fd_out_send(void *opaque, const char *data, int len) @@ -593,6 +590,10 @@ out_vformat(Out *o, const char *format, va_list args) slen = strlen(str); + if (sign != '\0' || prec != -1) { + __assert(__FILE__, __LINE__, "sign/precision unsupported"); + } + if (slen < width && !padLeft) { char padChar = padZero ? '0' : ' '; out_send_repeat(o, padChar, width - slen); diff --git a/linker/linker_format.h b/linker/linker_format.h index 3766b628a..b8873c095 100644 --- a/linker/linker_format.h +++ b/linker/linker_format.h @@ -25,25 +25,17 @@ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ + #ifndef _LINKER_FORMAT_H #define _LINKER_FORMAT_H #include #include -#ifdef __cplusplus -extern "C" { -#endif - -/* Formatting routines for the dynamic linker's debug traces */ -/* We want to avoid dragging the whole C library fprintf() */ -/* implementation into the dynamic linker since this creates */ -/* issues (it uses malloc()/free()) and increases code size */ - +// Formatting routines for the dynamic linker's debug traces +// We want to avoid dragging the whole C library fprintf() +// implementation into the dynamic linker since this creates +// issues (it uses malloc()/free()) and increases code size. int format_buffer(char *buffer, size_t bufsize, const char *format, ...); -#ifdef __cplusplus -}; -#endif - #endif /* _LINKER_FORMAT_H */ diff --git a/linker/linker_phdr.c b/linker/linker_phdr.cpp similarity index 99% rename from linker/linker_phdr.c rename to linker/linker_phdr.cpp index 250ca201b..199036645 100644 --- a/linker/linker_phdr.c +++ b/linker/linker_phdr.cpp @@ -301,7 +301,6 @@ phdr_table_load_segments(const Elf32_Phdr* phdr_table, Elf32_Addr file_end = file_start + phdr->p_filesz; Elf32_Addr file_page_start = PAGE_START(file_start); - Elf32_Addr file_page_end = PAGE_END(file_end); seg_addr = mmap((void*)seg_page_start, file_end - file_page_start, diff --git a/linker/linker_phdr.h b/linker/linker_phdr.h index a75926298..2d4d73544 100644 --- a/linker/linker_phdr.h +++ b/linker/linker_phdr.h @@ -37,12 +37,6 @@ #include "linker.h" -#ifdef __cplusplus -extern "C" { -#endif - -/* See linker_phdr.c for all usage documentation */ - int phdr_table_load(int fd, Elf32_Addr phdr_offset, @@ -107,8 +101,4 @@ phdr_table_get_dynamic_section(const Elf32_Phdr* phdr_table, Elf32_Addr** dynamic, size_t* dynamic_count); -#ifdef __cplusplus -}; -#endif - #endif /* LINKER_PHDR_H */ diff --git a/linker/rt.c b/linker/rt.cpp similarity index 89% rename from linker/rt.c rename to linker/rt.cpp index afbd65146..710892a03 100644 --- a/linker/rt.c +++ b/linker/rt.cpp @@ -28,9 +28,8 @@ /* * This function is an empty stub where GDB locates a breakpoint to get notified - * about linker activity. It canʼt be inlined away, canʼt be hidden. + * about linker activity. It canʼt be inlined away, can't be hidden. */ -void __attribute__((noinline)) __attribute__((visibility("default"))) rtld_db_dlactivity(void) -{ +extern "C" void __attribute__((noinline)) __attribute__((visibility("default"))) rtld_db_dlactivity() { }