aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--libsandbox/wrapper-funcs/__wrapper_exec.c28
-rwxr-xr-xtests/execv-4.sh21
-rw-r--r--tests/execv.at1
3 files changed, 43 insertions, 7 deletions
diff --git a/libsandbox/wrapper-funcs/__wrapper_exec.c b/libsandbox/wrapper-funcs/__wrapper_exec.c
index dfe1340..7b5d647 100644
--- a/libsandbox/wrapper-funcs/__wrapper_exec.c
+++ b/libsandbox/wrapper-funcs/__wrapper_exec.c
@@ -20,6 +20,12 @@ static WRAPPER_RET_TYPE (*WRAPPER_TRUE_NAME)(WRAPPER_ARGS_PROTO) = NULL;
#ifndef SB_EXEC_COMMON
#define SB_EXEC_COMMON
+/* Is [off, off + len) inside the file we mapped? */
+static bool elf_in_range(int64_t size, uint64_t off, uint64_t len)
+{
+ return off <= (uint64_t)size && len <= (uint64_t)size - off;
+}
+
/* 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).
*/
@@ -83,25 +89,31 @@ static bool sb_check_exec(const char *filename, char *const argv[])
#define PARSE_ELF(n) \
({ \
Elf##n##_Ehdr *ehdr = (void *)elf; \
- Elf##n##_Phdr *phdr = (void *)(elf + ehdr->e_phoff); \
+ Elf##n##_Phdr *phdr; \
Elf##n##_Addr vaddr, filesz, vsym = 0, vstr = 0, vhash = 0, vgnuhash = 0; \
Elf##n##_Off offset, symoff = 0, stroff = 0, hashoff = 0, gnuhashoff = 0; \
- Elf##n##_Dyn *dyn; \
+ Elf##n##_Dyn *dyn, *dynend; \
Elf##n##_Sym *sym, *symend; \
uint##n##_t ent_size = 0, str_size = 0; \
bool dynamic = false; \
size_t i; \
\
- if (size < ehdr->e_phoff + ehdr->e_phentsize * ehdr->e_phnum) \
+ if (ehdr->e_phentsize != sizeof(*phdr) || \
+ !elf_in_range(size, ehdr->e_phoff, \
+ (uint64_t)ehdr->e_phentsize * ehdr->e_phnum)) \
goto out_mmap; \
+ phdr = (void *)(elf + ehdr->e_phoff); \
\
/* 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: \
+ if (!elf_in_range(size, phdr[i].p_offset, phdr[i].p_filesz)) \
+ goto out_mmap; \
dyn = (void *)(elf + phdr[i].p_offset); \
- while (dyn->d_tag != DT_NULL) { \
+ dynend = dyn + phdr[i].p_filesz / sizeof(*dyn); \
+ while (dyn < dynend && 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; \
@@ -136,7 +148,9 @@ static bool sb_check_exec(const char *filename, char *const argv[])
* we only look at exported symbols, and the vast majority of exes \
* out there do not export any symbols at all. \
*/ \
- if (symoff && stroff) { \
+ if (symoff && stroff && \
+ elf_in_range(size, stroff, str_size) && \
+ elf_in_range(size, symoff, ent_size)) { \
/* Nowhere is the # of symbols recorded, or the size of the symbol \
* table. Instead, we do what glibc does: use the gnu or sysv hash \
* table if it exists, else assume that the string table always directly \
@@ -144,8 +158,8 @@ static bool sb_check_exec(const char *filename, char *const argv[])
* make, but glibc has gotten by this long. See determine_info in \
* glibc's elf/dl-addr.c. \
* \
- * We don't sanity check the ranges here as you aren't executing \
- * corrupt programs in the sandbox. \
+ * The tables are known to lie inside the file by this point, \
+ * but the walk below still trusts what it reads out of them. \
*/ \
sym = (void *)(elf + symoff); \
if (vgnuhash) { \
diff --git a/tests/execv-4.sh b/tests/execv-4.sh
new file mode 100755
index 0000000..96fcf16
--- /dev/null
+++ b/tests/execv-4.sh
@@ -0,0 +1,21 @@
+#!/bin/sh
+# an ELF whose headers point outside the file must not be followed there
+
+addwrite $PWD
+
+# real magic, so this gets as far as the program headers, and one of them
+# claims a PT_DYNAMIC nowhere near the mapping
+dd if=/dev/zero of=badelf bs=1 count=128 2>/dev/null || exit 1
+poke() { printf "$1" | dd of=badelf bs=1 seek=$2 conv=notrunc 2>/dev/null; }
+poke '\177ELF' 0 # e_ident magic
+poke '\002' 4 # e_ident[EI_CLASS] = ELFCLASS64
+poke '\100' 32 # e_phoff = 64
+poke '\070' 54 # e_phentsize = 56
+poke '\001' 56 # e_phnum = 1
+poke '\002' 64 # phdr[0].p_type = PT_DYNAMIC
+poke '\377\377\377\377\177' 74 # phdr[0].p_offset = 0x7fffffff0000
+
+# not executable, so the exec fails once the wrapper has had its look
+chmod a-x badelf
+./badelf
+test $? -eq 126
diff --git a/tests/execv.at b/tests/execv.at
index eec4638..1909650 100644
--- a/tests/execv.at
+++ b/tests/execv.at
@@ -1,3 +1,4 @@
SB_CHECK(1)
SB_CHECK(2)
SB_CHECK(3)
+SB_CHECK(4)