aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'libsandbox/wrapper-funcs/__wrapper_exec.c')
-rw-r--r--libsandbox/wrapper-funcs/__wrapper_exec.c126
1 files changed, 112 insertions, 14 deletions
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();