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
This commit is contained in:
Elliott Hughes 2012-10-29 17:37:13 -07:00
parent 06b596104a
commit 18a206c81d
13 changed files with 336 additions and 405 deletions

View File

@ -3,20 +3,19 @@ include $(CLEAR_VARS)
LOCAL_SRC_FILES:= \ LOCAL_SRC_FILES:= \
arch/$(TARGET_ARCH)/begin.S \ arch/$(TARGET_ARCH)/begin.S \
debugger.c \ debugger.cpp \
dlfcn.cpp \ dlfcn.cpp \
linker.cpp \ linker.cpp \
linker_environ.c \ linker_environ.cpp \
linker_format.c \ linker_format.cpp \
linker_phdr.c \ linker_phdr.cpp \
rt.c rt.cpp
LOCAL_LDFLAGS := -shared LOCAL_LDFLAGS := -shared
LOCAL_CFLAGS += -fno-stack-protector \ LOCAL_CFLAGS += -fno-stack-protector \
-Wstrict-overflow=5 \ -Wstrict-overflow=5 \
-fvisibility=hidden \ -fvisibility=hidden \
-std=gnu99 \
-Wall -Wextra -Wall -Wextra
# Set LINKER_DEBUG to either 1 or 0 # Set LINKER_DEBUG to either 1 or 0

View File

@ -26,6 +26,9 @@
* SUCH DAMAGE. * SUCH DAMAGE.
*/ */
#include "linker.h"
#include "linker_format.h"
#include <stdio.h> #include <stdio.h>
#include <stdlib.h> #include <stdlib.h>
#include <unistd.h> #include <unistd.h>
@ -36,45 +39,35 @@
#include <sys/socket.h> #include <sys/socket.h>
#include <sys/un.h> #include <sys/un.h>
extern int tgkill(int tgid, int tid, int sig); #include <private/logd.h>
void notify_gdb_of_libraries(); extern "C" int tgkill(int tgid, int tid, int sig);
#define DEBUGGER_SOCKET_NAME "android:debuggerd" #define DEBUGGER_SOCKET_NAME "android:debuggerd"
typedef enum { enum debugger_action_t {
// dump a crash // dump a crash
DEBUGGER_ACTION_CRASH, DEBUGGER_ACTION_CRASH,
// dump a tombstone file // dump a tombstone file
DEBUGGER_ACTION_DUMP_TOMBSTONE, DEBUGGER_ACTION_DUMP_TOMBSTONE,
// dump a backtrace only back to the socket // dump a backtrace only back to the socket
DEBUGGER_ACTION_DUMP_BACKTRACE, DEBUGGER_ACTION_DUMP_BACKTRACE,
} debugger_action_t; };
/* message sent over the socket */ /* message sent over the socket */
typedef struct { struct debugger_msg_t {
debugger_action_t action; debugger_action_t action;
pid_t tid; 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 // see man(2) prctl, specifically the section about PR_GET_NAME
#define MAX_TASK_NAME_LEN (16) #define MAX_TASK_NAME_LEN (16)
static int socket_abstract_client(const char *name, int type) static int socket_abstract_client(const char* name, int type) {
{ sockaddr_un addr;
struct sockaddr_un addr;
size_t namelen;
socklen_t alen;
int s, err;
namelen = strlen(name);
// Test with length +1 for the *initial* '\0'. // Test with length +1 for the *initial* '\0'.
size_t namelen = strlen(name);
if ((namelen + 1) > sizeof(addr.sun_path)) { if ((namelen + 1) > sizeof(addr.sun_path)) {
errno = EINVAL; errno = EINVAL;
return -1; 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 * Note: The path in this case is *not* supposed to be
* '\0'-terminated. ("man 7 unix" for the gory details.) * '\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_family = AF_LOCAL;
addr.sun_path[0] = 0; addr.sun_path[0] = 0;
memcpy(addr.sun_path + 1, name, namelen); 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); int s = socket(AF_LOCAL, type, 0);
if(s < 0) return -1; if (s == -1) {
return -1;
}
RETRY_ON_EINTR(err,connect(s, (struct sockaddr *) &addr, alen)); int err = TEMP_FAILURE_RETRY(connect(s, (sockaddr*) &addr, alen));
if (err < 0) { if (err == -1) {
close(s); close(s);
s = -1; s = -1;
} }
@ -105,9 +100,6 @@ static int socket_abstract_client(const char *name, int type)
return s; 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 * 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 * 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 * 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 * mutex is being held, so we don't want to use any libc functions that
* could allocate memory or hold a lock. * 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) static void logSignalSummary(int signum, const siginfo_t* info) {
{ const char* signame;
char buffer[128];
char threadname[MAX_TASK_NAME_LEN + 1]; // one more for termination
char* signame;
switch (signum) { switch (signum) {
case SIGILL: signame = "SIGILL"; break; case SIGILL: signame = "SIGILL"; break;
case SIGABRT: signame = "SIGABRT"; break; case SIGABRT: signame = "SIGABRT"; break;
@ -138,6 +124,7 @@ static void logSignalSummary(int signum, const siginfo_t* info)
default: signame = "???"; break; 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) { if (prctl(PR_GET_NAME, (unsigned long)threadname, 0, 0, 0) != 0) {
strcpy(threadname, "<name unknown>"); strcpy(threadname, "<name unknown>");
} else { } else {
@ -145,6 +132,9 @@ static void logSignalSummary(int signum, const siginfo_t* info)
// implies that 16 byte names are not. // implies that 16 byte names are not.
threadname[MAX_TASK_NAME_LEN] = 0; threadname[MAX_TASK_NAME_LEN] = 0;
} }
char buffer[128];
// "info" will be NULL if the siginfo_t information was not available.
if (info != NULL) { if (info != NULL) {
format_buffer(buffer, sizeof(buffer), format_buffer(buffer, sizeof(buffer),
"Fatal signal %d (%s) at 0x%08x (code=%d), thread %d (%s)", "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. * 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; struct sigaction oldact, newact;
memset(&newact, 0, sizeof(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 * Catches fatal signals so we can ask debuggerd to ptrace us before
* we crash. * 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]; char msgbuf[128];
unsigned tid;
int s;
/* /*
* It's possible somebody cleared the SA_SIGINFO flag, which would mean * 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); logSignalSummary(n, info);
tid = gettid(); pid_t tid = gettid();
s = socket_abstract_client(DEBUGGER_SOCKET_NAME, SOCK_STREAM); int s = socket_abstract_client(DEBUGGER_SOCKET_NAME, SOCK_STREAM);
if (s >= 0) { if (s >= 0) {
/* debugger knows our pid from the credentials on the /* 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; debugger_msg_t msg;
msg.action = DEBUGGER_ACTION_CRASH; msg.action = DEBUGGER_ACTION_CRASH;
msg.tid = tid; 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 (ret == sizeof(msg)) {
/* if the write failed, there is no point to read on /* if the write failed, there is no point to read on
* the file descriptor. */ * the file descriptor. */
RETRY_ON_EINTR(ret, read(s, &tid, 1)); ret = TEMP_FAILURE_RETRY(read(s, &tid, 1));
int savedErrno = errno; int saved_errno = errno;
notify_gdb_of_libraries(); notify_gdb_of_libraries();
errno = savedErrno; errno = saved_errno;
} }
if (ret < 0) { 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; struct sigaction act;
memset(&act, 0, sizeof(act)); memset(&act, 0, sizeof(act));
act.sa_sigaction = debugger_signal_handler; act.sa_sigaction = debugger_signal_handler;

View File

@ -93,7 +93,6 @@ static soinfo *sonext = &libdl_info;
static soinfo *somain; /* main process, always the one after libdl_info */ static soinfo *somain; /* main process, always the one after libdl_info */
#endif #endif
static char ldpaths_buf[LDPATH_BUFSIZE]; static char ldpaths_buf[LDPATH_BUFSIZE];
static const char *ldpaths[LDPATH_MAX + 1]; static const char *ldpaths[LDPATH_MAX + 1];
@ -108,9 +107,6 @@ int debug_verbosity;
static int pid; static int pid;
/* This boolean is set if the program being loaded is setuid */
static bool program_is_setuid;
enum RelocationKind { enum RelocationKind {
kRelocAbsolute = 0, kRelocAbsolute = 0,
kRelocRelative, kRelocRelative,
@ -257,8 +253,7 @@ static void notify_gdb_of_unload(soinfo* info) {
rtld_db_dlactivity(); rtld_db_dlactivity();
} }
extern "C" void notify_gdb_of_libraries() void notify_gdb_of_libraries() {
{
_r_debug.r_state = RT_ADD; _r_debug.r_state = RT_ADD;
rtld_db_dlactivity(); rtld_db_dlactivity();
_r_debug.r_state = RT_CONSISTENT; _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 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(); nullify_closed_stdio();
} }
notify_gdb_of_load(si); notify_gdb_of_load(si);
@ -1750,11 +1745,6 @@ static unsigned __linker_init_post_relocation(unsigned **elfdata, unsigned linke
int argc = (int) *elfdata; int argc = (int) *elfdata;
char **argv = (char**) (elfdata + 1); char **argv = (char**) (elfdata + 1);
unsigned *vecs = (unsigned*) (argv + argc + 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 /* NOTE: we store the elfdata pointer on a special location
* of the temporary TLS area in order to pass it to * 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); gettimeofday(&t0, 0);
#endif #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); 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(); debugger_init();
/* Get a few environment variables */ // Get a few environment variables.
{
#if LINKER_DEBUG #if LINKER_DEBUG
const char* env; {
env = linker_env_get("DEBUG"); /* XXX: TODO: Change to LD_DEBUG */ const char* env = linker_env_get("LD_DEBUG");
if (env) if (env != NULL) {
debug_verbosity = atoi(env); debug_verbosity = atoi(env);
}
}
#endif #endif
/* Normally, these are cleaned by linker_env_secure, but the test // Normally, these are cleaned by linker_env_init, but the test
* against program_is_setuid doesn't cost us anything */ // doesn't cost us anything.
if (!program_is_setuid) { const char* ldpath_env = NULL;
ldpath_env = linker_env_get("LD_LIBRARY_PATH"); const char* ldpreload_env = NULL;
ldpreload_env = linker_env_get("LD_PRELOAD"); if (!get_AT_SECURE()) {
} ldpath_env = linker_env_get("LD_LIBRARY_PATH");
ldpreload_env = linker_env_get("LD_PRELOAD");
} }
INFO("[ android linker & debugger ]\n"); INFO("[ android linker & debugger ]\n");
DEBUG("%5d elfdata @ 0x%08x\n", pid, (unsigned)elfdata); DEBUG("%5d elfdata @ 0x%08x\n", pid, (unsigned)elfdata);
si = soinfo_alloc(argv[0]); soinfo* si = soinfo_alloc(argv[0]);
if(si == 0) { if (si == NULL) {
exit(-1); exit(EXIT_FAILURE);
} }
/* bootstrap the link map, the main exe always needs to be first */ /* bootstrap the link map, the main exe always needs to be first */
@ -1858,7 +1831,7 @@ sanitize:
insert_soinfo_into_debug_map(&linker_soinfo); insert_soinfo_into_debug_map(&linker_soinfo);
/* extract information passed from the kernel */ /* extract information passed from the kernel */
while(vecs[0] != 0){ while (vecs[0] != 0){
switch(vecs[0]){ switch(vecs[0]){
case AT_PHDR: case AT_PHDR:
si->phdr = (Elf32_Phdr*) vecs[1]; si->phdr = (Elf32_Phdr*) vecs[1];
@ -1899,12 +1872,12 @@ sanitize:
char errmsg[] = "CANNOT LINK EXECUTABLE\n"; char errmsg[] = "CANNOT LINK EXECUTABLE\n";
write(2, __linker_dl_err_buf, strlen(__linker_dl_err_buf)); write(2, __linker_dl_err_buf, strlen(__linker_dl_err_buf));
write(2, errmsg, sizeof(errmsg)); write(2, errmsg, sizeof(errmsg));
exit(-1); exit(EXIT_FAILURE);
} }
soinfo_call_preinit_constructors(si); 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]); 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 // This situation should never occur unless the linker itself
// is corrupt. // is corrupt.
exit(-1); exit(EXIT_FAILURE);
} }
// We have successfully fixed our own relocations. It's safe to run // We have successfully fixed our own relocations. It's safe to run

View File

@ -34,9 +34,6 @@
#include <elf.h> #include <elf.h>
#include <sys/exec_elf.h> #include <sys/exec_elf.h>
#ifdef __cplusplus
extern "C" {
#endif
#include <link.h> #include <link.h>
#undef PAGE_MASK #undef PAGE_MASK
@ -89,8 +86,6 @@ struct r_debug
uintptr_t r_ldbase; uintptr_t r_ldbase;
}; };
typedef struct soinfo soinfo;
#define FLAG_LINKED 0x00000001 #define FLAG_LINKED 0x00000001
#define FLAG_ERROR 0x00000002 #define FLAG_ERROR 0x00000002
#define FLAG_EXE 0x00000004 // The main executable #define FLAG_EXE 0x00000004 // The main executable
@ -98,8 +93,7 @@ typedef struct soinfo soinfo;
#define SOINFO_NAME_LEN 128 #define SOINFO_NAME_LEN 128
struct soinfo struct soinfo {
{
char name[SOINFO_NAME_LEN]; char name[SOINFO_NAME_LEN];
const Elf32_Phdr *phdr; const Elf32_Phdr *phdr;
int phnum; 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); Elf32_Sym *soinfo_lookup(soinfo *si, const char *name);
void soinfo_call_constructors(soinfo *si); void soinfo_call_constructors(soinfo *si);
#ifdef __cplusplus extern "C" void notify_gdb_of_libraries();
};
#endif
#endif #endif

View File

@ -62,10 +62,6 @@
#if LINKER_DEBUG #if LINKER_DEBUG
#include "linker_format.h" #include "linker_format.h"
#ifdef __cplusplus
extern "C" {
#endif
extern int debug_verbosity; extern int debug_verbosity;
#if LINKER_DEBUG_TO_LOG #if LINKER_DEBUG_TO_LOG
extern int format_log(int, const char *, const char *, ...); extern int format_log(int, const char *, const char *, ...);
@ -81,10 +77,6 @@ extern int format_fd(int, const char *, ...);
} while (0) } while (0)
#endif /* !LINKER_DEBUG_TO_LOG */ #endif /* !LINKER_DEBUG_TO_LOG */
#ifdef __cplusplus
};
#endif
#else /* !LINKER_DEBUG */ #else /* !LINKER_DEBUG */
#define _PRINTVF(v,f,x...) do {} while(0) #define _PRINTVF(v,f,x...) do {} while(0)
#endif /* LINKER_DEBUG */ #endif /* LINKER_DEBUG */

View File

@ -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 <stddef.h>
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 '<name>=', 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]);
}
}

222
linker/linker_environ.cpp Normal file
View File

@ -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 <linux/auxvec.h>
#include <stddef.h>
#include <stdlib.h>
#include <unistd.h>
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<char**>(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 '<name>=', 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;
}

View File

@ -25,38 +25,27 @@
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE. * SUCH DAMAGE.
*/ */
#ifndef LINKER_ENVIRON_H #ifndef LINKER_ENVIRON_H
#define LINKER_ENVIRON_H #define LINKER_ENVIRON_H
#ifdef __cplusplus // Call this function before anything else. 'environment_and_aux_vectors'
extern "C" { // must point to the environment block in the ELF data block. The function
#endif // 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 // Unset a given environment variable. In case the variable is defined
* to the environment block in the ELF data block. The function returns // multiple times, unset all instances. This modifies the environment
* the start of the aux vectors after the env block. // block, so any pointer returned by linker_env_get() after this call
*/ // might become invalid.
extern unsigned* linker_env_init(unsigned* vecs); extern void linker_env_unset(const char* name);
/* Unset a given environment variable. In case the variable is defined // Returns the value of environment variable 'name' if defined and not
* multiple times, unset all instances. This modifies the environment // empty, or NULL otherwise. Note that the returned pointer may become
* block, so any pointer returned by linker_env_get() after this call // invalid if linker_env_unset() is called after this function.
* 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. */
extern const char* linker_env_get(const char* name); extern const char* linker_env_get(const char* name);
/* Remove insecure environment variables. This should be used when // Returns the value of this program's AT_SECURE variable.
* running setuid programs. */ extern bool get_AT_SECURE();
extern void linker_env_secure(void);
#ifdef __cplusplus
};
#endif
#endif /* LINKER_ENVIRON_H */ #endif /* LINKER_ENVIRON_H */

View File

@ -26,6 +26,7 @@
* SUCH DAMAGE. * SUCH DAMAGE.
*/ */
#include <assert.h>
#include <stdarg.h> #include <stdarg.h>
#include <string.h> #include <string.h>
#include <errno.h> #include <errno.h>
@ -43,14 +44,12 @@
/*** Generic output sink /*** Generic output sink
***/ ***/
typedef struct { struct Out {
void *opaque; void *opaque;
void (*send)(void *opaque, const char *data, int len); void (*send)(void *opaque, const char *data, int len);
} Out; };
static void static void out_send(Out *o, const char *data, size_t len) {
out_send(Out *o, const void *data, size_t len)
{
o->send(o->opaque, data, (int)len); o->send(o->opaque, data, (int)len);
} }
@ -72,27 +71,25 @@ out_send_repeat(Out *o, char ch, int count)
} }
/* forward declaration */ /* forward declaration */
static void static void out_vformat(Out* o, const char* format, va_list args);
out_vformat(Out *o, const char *format, va_list args);
/*** Bounded buffer output /*** Bounded buffer output
***/ ***/
typedef struct { struct BufOut {
Out out[1]; Out out[1];
char *buffer; char *buffer;
char *pos; char *pos;
char *end; char *end;
int total; int total;
} BufOut; };
static void static void buf_out_send(void *opaque, const char *data, int len) {
buf_out_send(void *opaque, const char *data, int len) BufOut *bo = reinterpret_cast<BufOut*>(opaque);
{
BufOut *bo = opaque;
if (len < 0) if (len < 0) {
len = strlen(data); len = strlen(data);
}
bo->total += len; bo->total += len;
@ -194,11 +191,11 @@ snprintf(char* buff, size_t bufsize, const char* format, ...)
/*** File descriptor output /*** File descriptor output
***/ ***/
typedef struct { struct FdOut {
Out out[1]; Out out[1];
int fd; int fd;
int total; int total;
} FdOut; };
static void static void
fd_out_send(void *opaque, const char *data, int len) 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); slen = strlen(str);
if (sign != '\0' || prec != -1) {
__assert(__FILE__, __LINE__, "sign/precision unsupported");
}
if (slen < width && !padLeft) { if (slen < width && !padLeft) {
char padChar = padZero ? '0' : ' '; char padChar = padZero ? '0' : ' ';
out_send_repeat(o, padChar, width - slen); out_send_repeat(o, padChar, width - slen);

View File

@ -25,25 +25,17 @@
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE. * SUCH DAMAGE.
*/ */
#ifndef _LINKER_FORMAT_H #ifndef _LINKER_FORMAT_H
#define _LINKER_FORMAT_H #define _LINKER_FORMAT_H
#include <stdarg.h> #include <stdarg.h>
#include <stddef.h> #include <stddef.h>
#ifdef __cplusplus // Formatting routines for the dynamic linker's debug traces
extern "C" { // We want to avoid dragging the whole C library fprintf()
#endif // 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, ...); int format_buffer(char *buffer, size_t bufsize, const char *format, ...);
#ifdef __cplusplus
};
#endif
#endif /* _LINKER_FORMAT_H */ #endif /* _LINKER_FORMAT_H */

View File

@ -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_end = file_start + phdr->p_filesz;
Elf32_Addr file_page_start = PAGE_START(file_start); 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, seg_addr = mmap((void*)seg_page_start,
file_end - file_page_start, file_end - file_page_start,

View File

@ -37,12 +37,6 @@
#include "linker.h" #include "linker.h"
#ifdef __cplusplus
extern "C" {
#endif
/* See linker_phdr.c for all usage documentation */
int int
phdr_table_load(int fd, phdr_table_load(int fd,
Elf32_Addr phdr_offset, Elf32_Addr phdr_offset,
@ -107,8 +101,4 @@ phdr_table_get_dynamic_section(const Elf32_Phdr* phdr_table,
Elf32_Addr** dynamic, Elf32_Addr** dynamic,
size_t* dynamic_count); size_t* dynamic_count);
#ifdef __cplusplus
};
#endif
#endif /* LINKER_PHDR_H */ #endif /* LINKER_PHDR_H */

View File

@ -28,9 +28,8 @@
/* /*
* This function is an empty stub where GDB locates a breakpoint to get notified * 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() {
{
} }