diff options
| -rw-r--r-- | libsandbox/wrapper-funcs/__wrapper_exec.c | 28 | ||||
| -rwxr-xr-x | tests/execv-4.sh | 21 | ||||
| -rw-r--r-- | tests/execv.at | 1 |
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) |
