/* * mprotect a segment to the indicated protection. If 'addr' is non-zero, * then it's the start address, else the value of 'start_sym' is the start. * The value of 'end_sym' is the end address. The start is rounded down * and the end is rounded up to page boundaries. Returns 'addr' or the * address of the start symbol. */ void * _dl_protect_segment(elf_object_t *object, Elf_Addr addr, const char *start_sym, const char *end_sym, int prot) { const Elf_Sym *this; Elf_Addr ooff, start, end; if (addr == 0) { this = NULL; ooff = _dl_find_symbol(start_sym, &this, SYM_SEARCH_OBJ | SYM_NOWARNNOTFOUND | SYM_PLT, NULL, object, NULL); /* If not found, nothing to do */ if (this == NULL) return (NULL); addr = ooff + this->st_value; } this = NULL; ooff = _dl_find_symbol(end_sym, &this, SYM_SEARCH_OBJ | SYM_NOWARNNOTFOUND | SYM_PLT, NULL, object, NULL); if (this == NULL) addr = 0; else { end = ooff + this->st_value; if (addr < end) { start = ELF_TRUNC(addr, _dl_pagesz); end = ELF_ROUND(end, _dl_pagesz); _dl_mprotect((void *)start, end - start, prot); } } return ((void *)addr); }
/* * Relocate the Global Offset Table (GOT). */ int _dl_md_reloc_got(elf_object_t *object, int lazy) { int fails = 0; Elf_Addr *pltgot; extern void _dl_bind_start(void); /* XXX */ Elf_Addr ooff; Elf_Addr plt_addr; const Elf_Sym *this; pltgot = (Elf_Addr *)object->Dyn.info[DT_PLTGOT]; object->got_addr = 0; object->got_size = 0; this = NULL; ooff = _dl_find_symbol("__got_start", &this, SYM_SEARCH_OBJ|SYM_NOWARNNOTFOUND|SYM_PLT, NULL, object, NULL); if (this != NULL) object->got_addr = ooff + this->st_value; this = NULL; ooff = _dl_find_symbol("__got_end", &this, SYM_SEARCH_OBJ|SYM_NOWARNNOTFOUND|SYM_PLT, NULL, object, NULL); if (this != NULL) object->got_size = ooff + this->st_value - object->got_addr; plt_addr = 0; object->plt_size = 0; this = NULL; ooff = _dl_find_symbol("__plt_start", &this, SYM_SEARCH_OBJ|SYM_NOWARNNOTFOUND|SYM_PLT, NULL, object, NULL); if (this != NULL) plt_addr = ooff + this->st_value; this = NULL; ooff = _dl_find_symbol("__plt_end", &this, SYM_SEARCH_OBJ|SYM_NOWARNNOTFOUND|SYM_PLT, NULL, object, NULL); if (this != NULL) object->plt_size = ooff + this->st_value - plt_addr; if (object->got_addr == 0) object->got_start = 0; else { object->got_start = ELF_TRUNC(object->got_addr, _dl_pagesz); object->got_size += object->got_addr - object->got_start; object->got_size = ELF_ROUND(object->got_size, _dl_pagesz); } if (plt_addr == 0) object->plt_start = 0; else { object->plt_start = ELF_TRUNC(plt_addr, _dl_pagesz); object->plt_size += plt_addr - object->plt_start; object->plt_size = ELF_ROUND(object->plt_size, _dl_pagesz); } if (object->obj_type == OBJTYPE_LDR || !lazy || pltgot == NULL) { fails = _dl_md_reloc(object, DT_JMPREL, DT_PLTRELSZ); } else { if (object->obj_base != 0) { int i, size; Elf_Addr *addr; Elf_RelA *rela; size = object->Dyn.info[DT_PLTRELSZ] / sizeof(Elf_RelA); rela = (Elf_RelA *)(object->Dyn.info[DT_JMPREL]); for (i = 0; i < size; i++) { addr = (Elf_Addr *)(object->obj_base + rela[i].r_offset); *addr += object->obj_base; } } } if (pltgot != NULL) { pltgot[2] = (Elf_Addr)_dl_bind_start; pltgot[3] = (Elf_Addr)object; } if (object->got_size != 0) _dl_mprotect((void*)object->got_start, object->got_size, PROT_READ); if (object->plt_size != 0) _dl_mprotect((void*)object->plt_start, object->plt_size, PROT_READ|PROT_EXEC); return (fails); }
int _dl_md_reloc_got(elf_object_t *object, int lazy) { Elf_RelA *rela; Elf_Addr ooff; int i, numrela, fails = 0; const Elf_Sym *this; if (object->dyn.pltrel != DT_RELA) return (0); object->got_addr = 0; object->got_size = 0; this = NULL; ooff = _dl_find_symbol("__got_start", &this, SYM_SEARCH_OBJ|SYM_NOWARNNOTFOUND|SYM_PLT, NULL, object, NULL ); if (this != NULL) object->got_addr = ooff + this->st_value; this = NULL; ooff = _dl_find_symbol("__got_end", &this, SYM_SEARCH_OBJ|SYM_NOWARNNOTFOUND|SYM_PLT, NULL, object, NULL); if (this != NULL) object->got_size = ooff + this->st_value - object->got_addr; if (object->got_addr == 0) object->got_start = 0; else { object->got_start = ELF_TRUNC(object->got_addr, _dl_pagesz); object->got_size += object->got_addr - object->got_start; object->got_size = ELF_ROUND(object->got_size, _dl_pagesz); } if (object->traced) lazy = 1; if (!lazy) { fails = _dl_md_reloc(object, DT_JMPREL, DT_PLTRELSZ); } else { register Elf_Addr ltp __asm ("%r19"); Elf_Addr *got = NULL; rela = (Elf_RelA *)(object->dyn.jmprel); numrela = object->dyn.pltrelsz / sizeof(Elf_RelA); ooff = object->obj_base; /* * Find the PLT stub by looking at all the * relocations. The PLT stub should be at the end of * the .plt section so we start with the last * relocation, since the linker should have emitted * them in order. */ for (i = numrela - 1; i >= 0; i--) { got = (Elf_Addr *)(ooff + rela[i].r_offset + PLT_ENTRY_SIZE + PLT_STUB_SIZE); if (got[-2] == PLT_STUB_MAGIC1 || got[-1] == PLT_STUB_MAGIC2) break; got = NULL; } if (got == NULL) return (1); /* * Patch up the PLT stub such that it doesn't clobber * %r22, which is used to pass on the errno values * from failed system calls to __cerrno() in libc. */ got[-7] = PLT_STUB_INSN1; got[-6] = PLT_STUB_INSN2; __asm volatile("fdc 0(%0)" :: "r" (&got[-7])); __asm volatile("fdc 0(%0)" :: "r" (&got[-6])); __asm volatile("sync"); __asm volatile("fic 0(%%sr0,%0)" :: "r" (&got[-7])); __asm volatile("fic 0(%%sr0,%0)" :: "r" (&got[-6])); __asm volatile("sync"); /* * Fill in the PLT stub such that it invokes the * _dl_bind_start() trampoline to fix up the * relocation. */ got[1] = (Elf_Addr)object; got[-2] = (Elf_Addr)&_dl_bind_start; got[-1] = ltp; /* * Even though we didn't modify any instructions it * seems we still need to syncronize the caches. * There may be instructions in the same cache line * and they end up being corrupted otherwise. */ __asm volatile("fdc 0(%0)" :: "r" (&got[-2])); __asm volatile("fdc 0(%0)" :: "r" (&got[-1])); __asm volatile("sync"); __asm volatile("fic 0(%%sr0,%0)" :: "r" (&got[-2])); __asm volatile("fic 0(%%sr0,%0)" :: "r" (&got[-1])); __asm volatile("sync"); for (i = 0; i < numrela; i++, rela++) { Elf_Addr *r_addr = (Elf_Addr *)(ooff + rela->r_offset); if (ELF_R_TYPE(rela->r_info) != RELOC_IPLT) { _dl_printf("unexpected reloc 0x%x\n", ELF_R_TYPE(rela->r_info)); return (1); } if (ELF_R_SYM(rela->r_info)) { r_addr[0] = (Elf_Addr)got - PLT_STUB_GOTOFF; r_addr[1] = (Elf_Addr) (rela - (Elf_RelA *)object->dyn.jmprel); } else { r_addr[0] = ooff + rela->r_addend; r_addr[1] = (Elf_Addr)object->dyn.pltgot; } } } if (object->got_size != 0) _dl_mprotect((void *)object->got_start, object->got_size, GOT_PERMS|PROT_EXEC); return (fails); }
/* * Relocate the Global Offset Table (GOT). * This is done by calling _dl_md_reloc on DT_JMPREL for DL_BIND_NOW, * otherwise the lazy binding plt initialization is performed. */ int _dl_md_reloc_got(elf_object_t *object, int lazy) { extern void _dl_bind_start(void); /* XXX */ int fails = 0; Elf_Addr *pltgot = (Elf_Addr *)object->Dyn.info[DT_PLTGOT]; Elf_Addr ooff; const Elf_Sym *this; if (pltgot == NULL) return (0); pltgot[1] = (Elf_Addr)object; pltgot[2] = (Elf_Addr)_dl_bind_start; if (object->Dyn.info[DT_PLTREL] != DT_RELA) return (0); object->got_addr = 0; object->got_size = 0; this = NULL; ooff = _dl_find_symbol("__got_start", &this, SYM_SEARCH_OBJ|SYM_NOWARNNOTFOUND|SYM_PLT, NULL, object, NULL); if (this != NULL) object->got_addr = ooff + this->st_value; this = NULL; ooff = _dl_find_symbol("__got_end", &this, SYM_SEARCH_OBJ|SYM_NOWARNNOTFOUND|SYM_PLT, NULL, object, NULL); if (this != NULL) object->got_size = ooff + this->st_value - object->got_addr; #if 0 plt_addr = 0; object->plt_size = 0; this = NULL; ooff = _dl_find_symbol("__plt_start", &this, SYM_SEARCH_OBJ|SYM_NOWARNNOTFOUND|SYM_PLT, NULL, object, NULL); if (this != NULL) plt_addr = ooff + this->st_value; this = NULL; ooff = _dl_find_symbol("__plt_end", &this, SYM_SEARCH_OBJ|SYM_NOWARNNOTFOUND|SYM_PLT, NULL, object, NULL); if (this != NULL) object->plt_size = ooff + this->st_value - plt_addr; #endif if (object->got_addr == 0) object->got_start = 0; else { object->got_start = ELF_TRUNC(object->got_addr, _dl_pagesz); object->got_size += object->got_addr - object->got_start; object->got_size = ELF_ROUND(object->got_size, _dl_pagesz); } #if 0 if (plt_addr == 0) object->plt_start = 0; else { object->plt_start = ELF_TRUNC(plt_addr, _dl_pagesz); object->plt_size += plt_addr - object->plt_start; object->plt_size = ELF_ROUND(object->plt_size, _dl_pagesz); } #endif if (object->traced) lazy = 1; if (!lazy) { fails = _dl_md_reloc(object, DT_JMPREL, DT_PLTRELSZ); } else { if (object->obj_base != 0) { int i, size; Elf_Addr *addr; Elf_RelA *rela; size = object->Dyn.info[DT_PLTRELSZ] / sizeof(Elf_RelA); rela = (Elf_RelA *)object->Dyn.info[DT_JMPREL]; for (i = 0; i < size; i++) { addr = (Elf_Addr *)(object->obj_base + rela[i].r_offset); *addr += object->obj_base; } } } if (object->got_size != 0) { _dl_mprotect((void*)object->got_start, object->got_size, PROT_READ); } /* PLT is already RO, no point in mprotecting it, just do GOT */ #if 0 if (object->plt_size != 0) _dl_mprotect((void*)object->plt_start, object->plt_size, PROT_READ|PROT_EXEC); #endif return (fails); }
/* * Load a file (interpreter/library) pointed to by path [stolen from * coff_load_shlib()]. Made slightly generic so it might be used externally. */ int ELFNAME(load_file)(struct proc *p, char *path, struct exec_package *epp, struct elf_args *ap, Elf_Addr *last) { int error, i; struct nameidata nd; Elf_Ehdr eh; Elf_Phdr *ph = NULL; u_long phsize; char *bp = NULL; Elf_Addr addr; struct vnode *vp; u_int8_t os; /* Just a dummy in this routine */ Elf_Phdr *base_ph = NULL; struct interp_ld_sec { Elf_Addr vaddr; u_long memsz; } loadmap[ELF_MAX_VALID_PHDR]; int nload, idx = 0; Elf_Addr pos = *last; int file_align; bp = path; NDINIT(&nd, LOOKUP, FOLLOW | LOCKLEAF, UIO_SYSSPACE, path, p); if ((error = namei(&nd)) != 0) { return (error); } vp = nd.ni_vp; if (vp->v_type != VREG) { error = EACCES; goto bad; } if ((error = VOP_GETATTR(vp, epp->ep_vap, p->p_ucred, p)) != 0) goto bad; if (vp->v_mount->mnt_flag & MNT_NOEXEC) { error = EACCES; goto bad; } if ((error = VOP_ACCESS(vp, VREAD, p->p_ucred, p)) != 0) goto bad1; if ((error = ELFNAME(read_from)(p, nd.ni_vp, 0, (caddr_t)&eh, sizeof(eh))) != 0) goto bad1; if (ELFNAME(check_header)(&eh, ET_DYN) && ELFNAME(olf_check_header)(&eh, ET_DYN, &os)) { error = ENOEXEC; goto bad1; } phsize = eh.e_phnum * sizeof(Elf_Phdr); ph = (Elf_Phdr *)malloc(phsize, M_TEMP, M_WAITOK); if ((error = ELFNAME(read_from)(p, nd.ni_vp, eh.e_phoff, (caddr_t)ph, phsize)) != 0) goto bad1; for (i = 0; i < eh.e_phnum; i++) { if (ph[i].p_type == PT_LOAD) { loadmap[idx].vaddr = trunc_page(ph[i].p_vaddr); loadmap[idx].memsz = round_page (ph[i].p_vaddr + ph[i].p_memsz - loadmap[idx].vaddr); file_align = ph[i].p_align; idx++; } } nload = idx; /* * If no position to load the interpreter was set by a probe * function, pick the same address that a non-fixed mmap(0, ..) * would (i.e. something safely out of the way). */ if (pos == ELFDEFNNAME(NO_ADDR)) { pos = uvm_map_hint(p, VM_PROT_EXECUTE); } pos = ELF_ROUND(pos, file_align); *last = epp->ep_interp_pos = pos; for (i = 0; i < nload;/**/) { vaddr_t addr; struct uvm_object *uobj; off_t uoff; size_t size; #ifdef this_needs_fixing if (i == 0) { uobj = &vp->v_uvm.u_obj; /* need to fix uoff */ } else { #endif uobj = NULL; uoff = 0; #ifdef this_needs_fixing } #endif addr = trunc_page(pos + loadmap[i].vaddr); size = round_page(addr + loadmap[i].memsz) - addr; /* CRAP - map_findspace does not avoid daddr+MAXDSIZ */ if ((addr + size > (vaddr_t)p->p_vmspace->vm_daddr) && (addr < (vaddr_t)p->p_vmspace->vm_daddr + MAXDSIZ)) addr = round_page((vaddr_t)p->p_vmspace->vm_daddr + MAXDSIZ); if (uvm_map_findspace(&p->p_vmspace->vm_map, addr, size, &addr, uobj, uoff, 0, UVM_FLAG_FIXED) == NULL) { if (uvm_map_findspace(&p->p_vmspace->vm_map, addr, size, &addr, uobj, uoff, 0, 0) == NULL) { error = ENOMEM; /* XXX */ goto bad1; } } if (addr != pos + loadmap[i].vaddr) { /* base changed. */ pos = addr - trunc_page(loadmap[i].vaddr); pos = ELF_ROUND(pos,file_align); epp->ep_interp_pos = *last = pos; i = 0; continue; } i++; } /* * Load all the necessary sections */ for (i = 0; i < eh.e_phnum; i++) { Elf_Addr size = 0; int prot = 0; int flags; switch (ph[i].p_type) { case PT_LOAD: if (base_ph == NULL) { flags = VMCMD_BASE; addr = *last; base_ph = &ph[i]; } else { flags = VMCMD_RELATIVE; addr = ph[i].p_vaddr - base_ph->p_vaddr; } ELFNAME(load_psection)(&epp->ep_vmcmds, nd.ni_vp, &ph[i], &addr, &size, &prot, flags); /* If entry is within this section it must be text */ if (eh.e_entry >= ph[i].p_vaddr && eh.e_entry < (ph[i].p_vaddr + size)) { epp->ep_entry = addr + eh.e_entry - ELF_TRUNC(ph[i].p_vaddr,ph[i].p_align); ap->arg_interp = addr; } addr += size; break; case PT_DYNAMIC: case PT_PHDR: case PT_NOTE: break; default: break; } } vn_marktext(nd.ni_vp); bad1: VOP_CLOSE(nd.ni_vp, FREAD, p->p_ucred, p); bad: if (ph != NULL) free((char *)ph, M_TEMP); *last = addr; vput(nd.ni_vp); return (error); }
/* * Load a psection at the appropriate address */ void ELFNAME(load_psection)(struct exec_vmcmd_set *vcset, struct vnode *vp, Elf_Phdr *ph, Elf_Addr *addr, Elf_Addr *size, int *prot, int flags) { u_long uaddr, msize, lsize, psize, rm, rf; long diff, offset, bdiff; Elf_Addr base; /* * If the user specified an address, then we load there. */ if (*addr != ELFDEFNNAME(NO_ADDR)) { if (ph->p_align > 1) { *addr = ELF_TRUNC(*addr, ph->p_align); diff = ph->p_vaddr - ELF_TRUNC(ph->p_vaddr, ph->p_align); /* page align vaddr */ base = *addr + trunc_page(ph->p_vaddr) - ELF_TRUNC(ph->p_vaddr, ph->p_align); bdiff = ph->p_vaddr - trunc_page(ph->p_vaddr); } else diff = 0; } else { *addr = uaddr = ph->p_vaddr; if (ph->p_align > 1) *addr = ELF_TRUNC(uaddr, ph->p_align); base = trunc_page(uaddr); bdiff = uaddr - base; diff = uaddr - *addr; } *prot |= (ph->p_flags & PF_R) ? VM_PROT_READ : 0; *prot |= (ph->p_flags & PF_W) ? VM_PROT_WRITE : 0; *prot |= (ph->p_flags & PF_X) ? VM_PROT_EXECUTE : 0; msize = ph->p_memsz + diff; offset = ph->p_offset - bdiff; lsize = ph->p_filesz + bdiff; psize = round_page(lsize); /* * Because the pagedvn pager can't handle zero fill of the last * data page if it's not page aligned we map the last page readvn. */ if (ph->p_flags & PF_W) { psize = trunc_page(lsize); if (psize > 0) NEW_VMCMD2(vcset, vmcmd_map_pagedvn, psize, base, vp, offset, *prot, flags); if (psize != lsize) { NEW_VMCMD2(vcset, vmcmd_map_readvn, lsize - psize, base + psize, vp, offset + psize, *prot, flags); } } else { NEW_VMCMD2(vcset, vmcmd_map_pagedvn, psize, base, vp, offset, *prot, flags); } /* * Check if we need to extend the size of the segment */ rm = round_page(*addr + ph->p_memsz + diff); rf = round_page(*addr + ph->p_filesz + diff); if (rm != rf) { NEW_VMCMD2(vcset, vmcmd_map_zero, rm - rf, rf, NULLVP, 0, *prot, flags); } *size = msize; }