aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMike Frysinger <vapier@gentoo.org>2016-02-16 19:23:53 -0500
committerMike Frysinger <vapier@gentoo.org>2016-02-16 19:23:53 -0500
commit55087abd8dc9802cf68cade776fe612a3f19f6a1 (patch)
tree96186b6916d064dd8e655732fc92f3706fc1f882 /libsandbox
parenttests: add test for overriding mmap (diff)
downloadsandbox-55087abd8dc9802cf68cade776fe612a3f19f6a1.tar.gz
sandbox-55087abd8dc9802cf68cade776fe612a3f19f6a1.tar.bz2
sandbox-55087abd8dc9802cf68cade776fe612a3f19f6a1.zip
libsandbox: use ptrace on apps that interpose their own allocator
If an app installs its own memory allocator by overriding the internal glibc symbols, then we can easily hit a loop that cannot be broken: the dlsym functions can attempt to allocate memory, and sandbox relies on them to find the "real" functions. So when someone calls a symbol that the sandbox protects, we call dlsym, and that calls malloc, which calls back into the app, and their allocator might use another symbol such as open ... which is protected by the sandbox. So we hit the loop like: -> open -> libsandbox:open -> dlsym -> malloc -> open -> libsandbox:open -> dlsym -> malloc -> ... Change the exec checking logic to scan the ELF instead. If it exports these glibc symbols, then we have to assume it can trigger a loop, so scrub the sandbox environment to prevent us from being loaded. Then we use the out-of-process tracer (i.e. ptrace). This should generally be as robust anyways ... if it's not, that's a bug we want to fix as this is the same code used for static apps. URL: http://crbug.com/586444 Reported-by: Ryo Hashimoto <hashimoto@chromium.org> Signed-off-by: Mike Frysinger <vapier@gentoo.org>
Diffstat (limited to 'libsandbox')
-rw-r--r--libsandbox/libsandbox.c72
-rw-r--r--libsandbox/libsandbox.h2
-rw-r--r--libsandbox/wrapper-funcs/__wrapper_exec.c126
3 files changed, 158 insertions, 42 deletions
diff --git a/libsandbox/libsandbox.c b/libsandbox/libsandbox.c
index 2bcff95..7555862 100644
--- a/libsandbox/libsandbox.c
+++ b/libsandbox/libsandbox.c
@@ -1137,7 +1137,7 @@ typedef struct {
* XXX: Might be much nicer if we could serialize these vars behind the back of
* the program. Might be hard to handle LD_PRELOAD though ...
*/
-char **sb_check_envp(char **envp, size_t *mod_cnt)
+char **sb_check_envp(char **envp, size_t *mod_cnt, bool insert)
{
char **my_env;
char *entry;
@@ -1194,7 +1194,8 @@ char **sb_check_envp(char **envp, size_t *mod_cnt)
}
/* If we found everything, there's nothing to do! */
- if (num_vars == found_var_cnt)
+ if ((insert && num_vars == found_var_cnt) ||
+ (!insert && found_var_cnt == 0))
/* Use the user's envp */
return envp;
@@ -1217,33 +1218,50 @@ char **sb_check_envp(char **envp, size_t *mod_cnt)
}
my_env = NULL;
- if (mod_cnt) {
- /* Count directly due to variability with LD_PRELOAD and the value
- * logic below. Getting out of sync can mean memory corruption. */
- *mod_cnt = 0;
- if (unlikely(merge_ld_preload)) {
- str_list_add_item(my_env, ld_preload, error);
- (*mod_cnt)++;
- }
- for (i = 0; i < num_vars; ++i) {
- if (found_vars[i] || !vars[i].value)
- continue;
- str_list_add_item_env(my_env, vars[i].name, vars[i].value, error);
- (*mod_cnt)++;
- }
-
- str_list_for_each_item(envp, entry, count) {
- if (unlikely(merge_ld_preload && is_env_var(entry, vars[0].name, vars[0].len)))
- continue;
- str_list_add_item(my_env, entry, error);
+ if (!insert) {
+ if (mod_cnt) {
+ str_list_for_each_item(envp, entry, count) {
+ for (i = 0; i < num_vars; ++i)
+ if (is_env_var(entry, vars[i].name, vars[i].len)) {
+ (*mod_cnt)++;
+ goto skip;
+ }
+ str_list_add_item(my_env, entry, error);
+ skip: ;
+ }
+ } else {
+ for (i = 0; i < num_vars; ++i)
+ unsetenv(vars[i].name);
}
} else {
- if (unlikely(merge_ld_preload))
- putenv(ld_preload);
- for (i = 0; i < num_vars; ++i) {
- if (found_vars[i] || !vars[i].value)
- continue;
- setenv(vars[i].name, vars[i].value, 1);
+ if (mod_cnt) {
+ /* Count directly due to variability with LD_PRELOAD and the value
+ * logic below. Getting out of sync can mean memory corruption. */
+ *mod_cnt = 0;
+ if (unlikely(merge_ld_preload)) {
+ str_list_add_item(my_env, ld_preload, error);
+ (*mod_cnt)++;
+ }
+ for (i = 0; i < num_vars; ++i) {
+ if (found_vars[i] || !vars[i].value)
+ continue;
+ str_list_add_item_env(my_env, vars[i].name, vars[i].value, error);
+ (*mod_cnt)++;
+ }
+
+ str_list_for_each_item(envp, entry, count) {
+ if (unlikely(merge_ld_preload && is_env_var(entry, vars[0].name, vars[0].len)))
+ continue;
+ str_list_add_item(my_env, entry, error);
+ }
+ } else {
+ if (unlikely(merge_ld_preload))
+ putenv(ld_preload);
+ for (i = 0; i < num_vars; ++i) {
+ if (found_vars[i] || !vars[i].value)
+ continue;
+ setenv(vars[i].name, vars[i].value, 1);
+ }
}
}
diff --git a/libsandbox/libsandbox.h b/libsandbox/libsandbox.h
index 596084d..63882e7 100644
--- a/libsandbox/libsandbox.h
+++ b/libsandbox/libsandbox.h
@@ -56,7 +56,7 @@ void *get_dlsym(const char *symname, const char *symver);
extern char sandbox_lib[SB_PATH_MAX];
extern bool sandbox_on;
-char **sb_check_envp(char **envp, size_t *mod_cnt);
+char **sb_check_envp(char **envp, size_t *mod_cnt, bool insert);
void sb_cleanup_envp(char **envp, size_t mod_cnt);
extern pid_t trace_pid;
diff --git a/libsandbox/wrapper-funcs/__wrapper_exec.c b/libsandbox/wrapper-funcs/__wrapper_exec.c
index 7107c21..f7f51ab 100644
--- a/libsandbox/wrapper-funcs/__wrapper_exec.c
+++ b/libsandbox/wrapper-funcs/__wrapper_exec.c
@@ -20,17 +20,20 @@ static WRAPPER_RET_TYPE (*WRAPPER_TRUE_NAME)(WRAPPER_ARGS_PROTO) = NULL;
#ifndef SB_EXEC_COMMON
#define SB_EXEC_COMMON
-/* Check to see if this a static ELF and if so, protect using trace mechanisms */
-static void sb_check_exec(const char *filename, char *const argv[])
+/* Check to see if we can run this program in-process. If not, try to fall back
+ * tracing it out-of-process via some trace mechanisms (e.g. ptrace).
+ */
+static bool sb_check_exec(const char *filename, char *const argv[])
{
int fd;
unsigned char *elf;
struct stat st;
bool do_trace = false;
+ bool run_in_process = true;
- fd = open(filename, O_RDONLY|O_CLOEXEC);
+ fd = sb_unwrapped_open_DEFAULT(filename, O_RDONLY|O_CLOEXEC, 0);
if (fd == -1)
- return;
+ return true;
if (fstat(fd, &st))
goto out_fd;
if (st.st_size < sizeof(Elf64_Ehdr))
@@ -57,19 +60,110 @@ static void sb_check_exec(const char *filename, char *const argv[])
* gains root just to preload libsandbox.so. That unfortunately
* could easily open up people to root vulns.
*/
- if (getuid() == 0 || !(st.st_mode & (S_ISUID | S_ISGID))) {
+ if (st.st_mode & (S_ISUID | S_ISGID))
+ if (getuid() != 0)
+ run_in_process = false;
+
+ /* We also need to ptrace programs that interpose their own allocator.
+ * http://crbug.com/586444
+ */
+ if (run_in_process) {
+ static const char * const libc_alloc_syms[] = {
+ "__libc_calloc",
+ "__libc_free",
+ "__libc_malloc",
+ "__libc_realloc",
+ "__malloc_hook",
+ "__realloc_hook",
+ "__free_hook",
+ "__memalign_hook",
+ "__malloc_initialize_hook",
+ };
#define PARSE_ELF(n) \
({ \
Elf##n##_Ehdr *ehdr = (void *)elf; \
Elf##n##_Phdr *phdr = (void *)(elf + ehdr->e_phoff); \
- uint16_t p; \
- if (st.st_size < sizeof(*ehdr)) \
- goto out_mmap; \
+ Elf##n##_Addr vaddr, filesz, vsym = 0, vstr = 0; \
+ Elf##n##_Off offset, symoff = 0, stroff = 0; \
+ Elf##n##_Dyn *dyn; \
+ Elf##n##_Sym *sym; \
+ uint##n##_t ent_size = 0, str_size = 0; \
+ bool dynamic = false; \
+ size_t i; \
+ \
if (st.st_size < ehdr->e_phoff + ehdr->e_phentsize * ehdr->e_phnum) \
goto out_mmap; \
- for (p = 0; p < ehdr->e_phnum; ++p) \
- if (phdr[p].p_type == PT_INTERP) \
- goto done; \
+ \
+ /* First gather the tags we care about. */ \
+ for (i = 0; i < ehdr->e_phnum; ++i) { \
+ switch (phdr[i].p_type) { \
+ case PT_INTERP: dynamic = true; break; \
+ case PT_DYNAMIC: \
+ dyn = (void *)(elf + phdr[i].p_offset); \
+ while (dyn->d_tag != DT_NULL) { \
+ switch (dyn->d_tag) { \
+ case DT_SYMTAB: vsym = dyn->d_un.d_val; break; \
+ case DT_SYMENT: ent_size = dyn->d_un.d_val; break; \
+ case DT_STRTAB: vstr = dyn->d_un.d_val; break; \
+ case DT_STRSZ: str_size = dyn->d_un.d_val; break; \
+ } \
+ ++dyn; \
+ } \
+ break; \
+ } \
+ } \
+ \
+ if (dynamic && vsym && ent_size && vstr && str_size) { \
+ /* Figure out where in the file these tables live. */ \
+ for (i = 0; i < ehdr->e_phnum; ++i) { \
+ vaddr = phdr[i].p_vaddr; \
+ filesz = phdr[i].p_filesz; \
+ offset = phdr[i].p_offset; \
+ if (vsym >= vaddr && vsym < vaddr + filesz) \
+ symoff = offset + (vsym - vaddr); \
+ if (vstr >= vaddr && vstr < vaddr + filesz) \
+ stroff = offset + (vstr - vaddr); \
+ } \
+ \
+ /* Finally walk the symbol table. This should generally be fast as \
+ * we only look at exported symbols, and the vast majority of exes \
+ * out there do not export any symbols at all. \
+ */ \
+ if (symoff && stroff) { \
+ sym = (void *)(elf + symoff); \
+ /* Nowhere is the # of symbols recorded, or the size of the symbol \
+ * table. Instead, we do what glibc does: assume that the string \
+ * table always follows the symbol table. This seems like a poor \
+ * assumption to make, but glibc has gotten by this long. We could \
+ * rely on DT_HASH and walking all the buckets to find the largest \
+ * symbol index, but that's also a bit hacky. \
+ * \
+ * We don't sanity check the ranges here as you aren't executing \
+ * corrupt programs in the sandbox. \
+ */ \
+ for (i = 0; i < (vstr - vsym) / ent_size; ++i) { \
+ char *symname = (void *)(elf + stroff + sym->st_name); \
+ if (ELF##n##_ST_VISIBILITY(sym->st_other) == STV_DEFAULT && \
+ sym->st_shndx != SHN_UNDEF && sym->st_shndx < SHN_LORESERVE && \
+ sym->st_name && \
+ /* Minor optimization to avoid strcmp. */ \
+ symname[0] == '_' && symname[1] == '_') { \
+ /* Blacklist internal C library symbols. */ \
+ size_t j; \
+ for (j = 0; j < ARRAY_SIZE(libc_alloc_syms); ++j) \
+ if (!strcmp(symname, libc_alloc_syms[j])) { \
+ run_in_process = false; \
+ goto use_trace; \
+ } \
+ } \
+ ++sym; \
+ } \
+ } \
+ \
+ } \
+ \
+ if (dynamic) \
+ goto done; \
})
if (elf[EI_CLASS] == ELFCLASS32)
PARSE_ELF(32);
@@ -78,6 +172,7 @@ static void sb_check_exec(const char *filename, char *const argv[])
#undef PARSE_ELF
}
+ use_trace:
do_trace = trace_possible(filename, argv, elf);
/* Now that we're done with stuff, clean up before forking */
@@ -90,6 +185,8 @@ static void sb_check_exec(const char *filename, char *const argv[])
if (do_trace)
trace_main(filename, argv);
+
+ return run_in_process;
}
#endif
@@ -107,6 +204,7 @@ WRAPPER_RET_TYPE SB_HIDDEN_FUNC(WRAPPER_NAME)(WRAPPER_ARGS_PROTO_FULL)
WRAPPER_RET_TYPE WRAPPER_NAME(WRAPPER_ARGS_PROTO)
{
WRAPPER_RET_TYPE result = WRAPPER_RET_DEFAULT;
+ bool run_in_process = true;
/* The C library may implement some exec funcs by calling other
* exec funcs. So we might get a little sandbox recursion going
@@ -151,15 +249,15 @@ WRAPPER_RET_TYPE WRAPPER_NAME(WRAPPER_ARGS_PROTO)
if (!SB_SAFE(check_path))
goto done;
- sb_check_exec(check_path, argv);
+ run_in_process = sb_check_exec(check_path, argv);
}
#endif
#ifdef EXEC_MY_ENV
size_t mod_cnt;
- char **my_env = sb_check_envp((char **)envp, &mod_cnt);
+ char **my_env = sb_check_envp((char **)envp, &mod_cnt, run_in_process);
#else
- sb_check_envp(environ, NULL);
+ sb_check_envp(environ, NULL, run_in_process);
#endif
restore_errno();