diff --git a/src/stub/l_lx_elf.c b/src/stub/l_lx_elf.c index a34f7c64..87f0f3fc 100644 --- a/src/stub/l_lx_elf.c +++ b/src/stub/l_lx_elf.c @@ -236,44 +236,87 @@ auxv_up(Elf32_auxv_t *av, int const type, unsigned const value) } } +// The PF_* and PROT_* bits are {1,2,4}; the conversion table fits in 32 bits. +#define REP8(x) \ + ((x)|((x)<<4)|((x)<<8)|((x)<<12)|((x)<<16)|((x)<<20)|((x)<<24)|((x)<<28)) +#define EXP8(y) \ + ((1&(y)) ? 0xf0f0f0f0 : (2&(y)) ? 0xff00ff00 : (4&(y)) ? 0xffff0000 : 0) + +static inline unsigned +__attribute__((regparm(1), stdcall)) +pf_to_prot(unsigned const pf) +{ + return (PROT_READ|PROT_WRITE|PROT_EXEC) & ( + ( (REP8(PROT_EXEC ) & EXP8(PF_X)) + |(REP8(PROT_READ ) & EXP8(PF_R)) + |(REP8(PROT_WRITE) & EXP8(PF_W)) + ) >> ((pf & (PF_R|PF_W|PF_X))<<2) ); +} + + +// Find convex hull of PT_LOAD (the minimal interval which covers all PT_LOAD), +// and mmap that much, to be sure that a kernel using exec-shield-randomize +// won't place the first piece in a way that leaves no room for the rest. +static unsigned long // returns relocation constant +__attribute__((regparm(3), stdcall)) +xfind_pages(unsigned mflags, Elf32_Phdr const *phdr, int phnum, + char **const p_brk +) +{ + size_t lo= ~0, hi= 0, szlo= 0; + char *addr; + mflags += MAP_PRIVATE | MAP_ANONYMOUS; // '+' can optimize better than '|' + for (; --phnum>=0; ++phdr) if (PT_LOAD==phdr->p_type) { + if (phdr->p_vaddr < lo) { + lo = phdr->p_vaddr; + szlo = phdr->p_filesz; + } + if (hi < (phdr->p_memsz + phdr->p_vaddr)) { + hi = phdr->p_memsz + phdr->p_vaddr; + } + } + szlo += ~PAGE_MASK & lo; // page fragment on lo edge + lo -= ~PAGE_MASK & lo; // round down to page boundary + hi = PAGE_MASK & (hi - lo - PAGE_MASK -1); // page length + szlo = PAGE_MASK & (szlo - PAGE_MASK -1); // page length + addr = do_mmap((void *)lo, hi, PROT_READ|PROT_WRITE|PROT_EXEC, mflags, 0, 0); + *p_brk = hi + addr; // the logical value of brk(0) + munmap(szlo + addr, hi - szlo); // desirable if PT_LOAD non-contiguous + return (unsigned long)addr - lo; +} + static Elf32_Addr // entry address do_xmap(int const fdi, Elf32_Ehdr const *const ehdr, struct Extent *const xi, Elf32_auxv_t *const av) { Elf32_Phdr const *phdr = (Elf32_Phdr const *) (ehdr->e_phoff + (char const *)ehdr); - unsigned long base = (ET_DYN==ehdr->e_type) ? 0x40000000 : 0; + char *v_brk; + unsigned long const reloc = xfind_pages( + ((ET_DYN!=ehdr->e_type) ? MAP_FIXED : 0), phdr, ehdr->e_phnum, &v_brk); int j; for (j=0; j < ehdr->e_phnum; ++phdr, ++j) if (PT_PHDR==phdr->p_type) { - auxv_up(av, AT_PHDR, phdr->p_vaddr); + auxv_up(av, AT_PHDR, phdr->p_vaddr + reloc); } else if (PT_LOAD==phdr->p_type) { + unsigned const prot = pf_to_prot(phdr->p_flags); struct Extent xo; size_t mlen = xo.size = phdr->p_filesz; char *addr = xo.buf = (char *)phdr->p_vaddr; - char *haddr = phdr->p_memsz + (char *)phdr->p_vaddr; + char *haddr = phdr->p_memsz + addr; size_t frag = (int)addr &~ PAGE_MASK; mlen += frag; addr -= frag; - if (ET_DYN==ehdr->e_type) { - addr += base; - haddr += base; - } - else { // There is only one brk, the one for the ET_EXEC - // Not needed if compressed a.elf is invoked directly. - // Needed only if compressed shell script invokes compressed shell. - do_brk(haddr+OVERHEAD); // Also takes care of whole pages of .bss - } + addr += reloc; + haddr += reloc; + // Decompressor can overrun the destination by 3 bytes. if (addr != do_mmap(addr, mlen + (xi ? 3 : 0), PROT_READ | PROT_WRITE, MAP_FIXED | MAP_PRIVATE | (xi ? MAP_ANONYMOUS : 0), fdi, phdr->p_offset - frag) ) { err_exit(8); } - if (0==base) { - base = (unsigned long)addr; - } if (xi) { unpackExtent(xi, &xo, (f_expand *)fdi, ((phdr->p_flags & PF_X) ? (f_unfilter *)(2+ fdi) : 0)); @@ -288,52 +331,36 @@ do_xmap(int const fdi, Elf32_Ehdr const *const ehdr, struct Extent *const xi, auxv_up((Elf32_auxv_t *)(~1 & (int)av), AT_NULL, (unsigned)hatch); } } - if (phdr->p_memsz != phdr->p_filesz) { // .bss - if (ET_DYN==ehdr->e_type) { // PT_INTERP whole pages of .bss? - addr += frag + mlen; - mlen = haddr - addr; - if (0 < (int)mlen) { // need more pages, too - if (addr != do_mmap(addr, mlen, PROT_READ | PROT_WRITE, - MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, 0, 0 ) ) { - err_exit(9); + if (0!=mprotect(addr, mlen, prot)) { + err_exit(10); ERR_LAB - } - } + } + addr += mlen + frag; /* page boundary on hi end */ + if (addr < haddr) { // need pages for .bss + if (addr != do_mmap(addr, haddr - addr, prot, + MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, 0, 0 ) ) { + err_exit(9); } } - else { // no .bss - int prot = 0; - if (phdr->p_flags & PF_X) { prot |= PROT_EXEC; } - if (phdr->p_flags & PF_W) { prot |= PROT_WRITE; } - if (phdr->p_flags & PF_R) { prot |= PROT_READ; } - if (0!=mprotect(addr, mlen, prot)) { - err_exit(10); + else if (xi) { // cleanup if decompressor overrun crosses page boundary + mlen = ~PAGE_MASK & (3+ mlen); + if (mlen<=3) { // page fragment was overrun buffer only + munmap(addr, mlen); } - if (xi) { // cleanup if decompressor overrun crosses page boundary - mlen += 3; - addr += mlen; - mlen &= ~PAGE_MASK; - if (mlen<=3) { // page fragment was overrun buffer only - munmap(addr - mlen, mlen); - } - } - } - if (ET_DYN!=ehdr->e_type) { - // Needed only if compressed shell script invokes compressed shell. - do_brk(haddr); } } - if (!xi) { + if (!xi) { // 2nd call (PT_INTERP); close()+check is smaller here if (0!=close(fdi)) { err_exit(11); } } - if (ET_DYN==ehdr->e_type) { - return ehdr->e_entry + base; - } - else { - return ehdr->e_entry; + else { // 1st call (main); also have (0!=av) here + if (ET_DYN!=ehdr->e_type) { + // Needed only if compressed shell script invokes compressed shell. + do_brk(v_brk); + } } + return ehdr->e_entry + reloc; } diff --git a/src/stub/l_lx_sh.c b/src/stub/l_lx_sh.c index 43b308ff..ab993731 100644 --- a/src/stub/l_lx_sh.c +++ b/src/stub/l_lx_sh.c @@ -168,11 +168,72 @@ bzero(char *p, size_t len) } while (--len); } +#define REP8(x) \ + ((x)|((x)<<4)|((x)<<8)|((x)<<12)|((x)<<16)|((x)<<20)|((x)<<24)|((x)<<28)) +#define EXP8(y) \ + ((1&(y)) ? 0xf0f0f0f0 : (2&(y)) ? 0xff00ff00 : (4&(y)) ? 0xffff0000 : 0) + +static inline unsigned +__attribute__((regparm(1), stdcall)) +pf_to_prot(unsigned const pf) +{ + return 7 & (( (REP8(PROT_EXEC ) & EXP8(PF_X)) + |(REP8(PROT_READ ) & EXP8(PF_R)) + |(REP8(PROT_WRITE) & EXP8(PF_W)) ) + >> ((pf & (PF_R|PF_W|PF_X))<<2) ); +} + + +// Find convex hull of PT_LOAD (the minimal interval which covers all PT_LOAD), +// and mmap that much, to be sure that a kernel using exec-shield-randomize +// won't place the first piece in a way that leaves no room for the rest. +static unsigned long // returns relocation constant +__attribute__((regparm(3), stdcall)) +xfind_pages(unsigned mflags, Elf32_Phdr const *phdr, int phnum) +{ + size_t lo= ~0, hi= 0, szlo= 0; + char *addr; + mflags += MAP_PRIVATE | MAP_ANONYMOUS; // '+' can optimize better than '|' + for (; --phnum>=0; ++phdr) if (PT_LOAD==phdr->p_type) { + if (phdr->p_vaddr < lo) { + lo = phdr->p_vaddr; + szlo = phdr->p_filesz; + } + if (hi < (phdr->p_memsz + phdr->p_vaddr)) { + hi = phdr->p_memsz + phdr->p_vaddr; + } + } + if (MAP_FIXED & mflags) { // the "shell", and not the PT_INTERP + // This is a dirty hack to set the proper value for brk(0) as seen by + // the "shell" which we will mmap() soon, upon return to do_xmap(). + // It depends on our own brk() starting out at 0x08048000, which is the + // default base address used by /bin/ld for an ET_EXEC. We must brk() + // now. If we wait until after mmap() of shell pages, then the kernel + // "vma" containing our original brk() of 0x08048000 will not be contiguous + // with hi [the mmap'ed pages from the shell will be in between], + // and various linux kernels will not move the brk() in this case; + // the typical symptom is SIGSEGV early in ld-linux.so.2 (the PT_INTERP). + do_brk((void *)hi); + } + szlo += ~PAGE_MASK & lo; // page fragment on lo edge + lo -= ~PAGE_MASK & lo; // round down to page boundary + hi = PAGE_MASK & (hi - lo - PAGE_MASK -1); // page length + szlo = PAGE_MASK & (szlo - PAGE_MASK -1); // page length + addr = do_mmap((void *)lo, hi, PROT_READ|PROT_WRITE|PROT_EXEC, mflags, 0, 0); + + // Doing this may destroy the brk() that we set so carefully above. + // The munmap() is "needed" only for discontiguous PT_LOAD, + // and neither shells nor ld-linux.so.2 have that. + // munmap(szlo + addr, hi - szlo); + + return (unsigned long)addr - lo; +} + // This do_xmap() has no Extent *xi input because it doesn't decompress anything; // it only maps the shell and its PT_INTERP. So, it was specialized by hand -// to reduce compiled instruction size. gdb 2.91.66 does not notice that +// to reduce compiled instruction size. gcc 2.91.66 does not notice that // there is only one call to this static function (from getexec(), which -// would specify 0 for xi), so gdb does not propagate the constant parameter. +// would specify 0 for xi), so gcc does not propagate the constant parameter. // Notice there is no make_hatch(), either. static Elf32_Addr // entry address @@ -180,77 +241,52 @@ do_xmap(int const fdi, Elf32_Ehdr const *const ehdr, Elf32_auxv_t *const av) { Elf32_Phdr const *phdr = (Elf32_Phdr const *) (ehdr->e_phoff + (char const *)ehdr); - unsigned long base = (ET_DYN==ehdr->e_type) ? 0x40000000 : 0; + unsigned long const reloc = xfind_pages( + ((ET_DYN!=ehdr->e_type) ? MAP_FIXED : 0), phdr, ehdr->e_phnum); int j; for (j=0; j < ehdr->e_phnum; ++phdr, ++j) if (PT_PHDR==phdr->p_type) { av[AT_PHDR -1].a_un.a_val = phdr->p_vaddr; } else if (PT_LOAD==phdr->p_type) { + unsigned const prot = pf_to_prot(phdr->p_flags); struct Extent xo; size_t mlen = xo.size = phdr->p_filesz; char *addr = xo.buf = (char *)phdr->p_vaddr; - char *haddr = phdr->p_memsz + (char *)phdr->p_vaddr; + char *haddr = phdr->p_memsz + addr; size_t frag = (int)addr &~ PAGE_MASK; mlen += frag; addr -= frag; - if (ET_DYN==ehdr->e_type) { - addr += base; - haddr += base; - } - else { // There is only one brk, the one for the ET_EXEC - do_brk(haddr+OVERHEAD); // Also takes care of whole pages of .bss - } + addr += reloc; + haddr += reloc; + // Decompressor can overrun the destination by 3 bytes. if (addr != do_mmap(addr, mlen, PROT_READ | PROT_WRITE, MAP_FIXED | MAP_PRIVATE, fdi, phdr->p_offset - frag) ) { err_exit(8); } - if (0==base) { - base = (unsigned long)addr; - } bzero(addr, frag); // fragment at lo end frag = (-mlen) &~ PAGE_MASK; // distance to next page boundary bzero(mlen+addr, frag); // fragment at hi end - if (phdr->p_memsz != phdr->p_filesz) { // .bss - if (ET_DYN==ehdr->e_type) { // PT_INTERP whole pages of .bss? - addr += frag + mlen; - mlen = haddr - addr; - if (0 < (int)mlen) { // need more pages, too - if (addr != do_mmap(addr, mlen, PROT_READ | PROT_WRITE, - MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, 0, 0 ) ) { - err_exit(9); + if (0!=mprotect(addr, mlen, prot)) { + err_exit(10); ERR_LAB - } - } + } + addr += mlen + frag; /* page boundary on hi end */ + if (addr < haddr) { // need pages for .bss + if (addr != do_mmap(addr, haddr - addr, prot, + MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, 0, 0 ) ) { + err_exit(9); } } - else { // no .bss - int prot = 0; - if (phdr->p_flags & PF_X) { prot |= PROT_EXEC; } - if (phdr->p_flags & PF_W) { prot |= PROT_WRITE; } - if (phdr->p_flags & PF_R) { prot |= PROT_READ; } - if (0!=mprotect(addr, mlen, prot)) { - err_exit(10); - } - } - if (ET_DYN!=ehdr->e_type) { - do_brk(haddr); - } } - if (0!=close(fdi)) { - err_exit(11); - } - if (ET_DYN==ehdr->e_type) { - return ehdr->e_entry + base; - } - else { - return ehdr->e_entry; + if (0!=close(fdi)) { + err_exit(11); } + return ehdr->e_entry + reloc; } - static Elf32_Addr // entry address getexec(char const *const fname, Elf32_Ehdr *const ehdr, Elf32_auxv_t *const av) {