/* $OpenBSD: elf.c,v 1.9 2017/11/14 09:14:50 mpi Exp $ */ /* * Copyright (c) 2016 Martin Pieuchot <mpi@openbsd.org> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include <sys/types.h> #include <machine/reloc.h> #include <assert.h> #include <elf.h> #include <err.h> #include <string.h> static int elf_reloc_size(unsigned long); static void elf_reloc_apply(const char *, size_t, const char *, size_t, ssize_t, char *, size_t); int iself(const char *p, size_t filesize) { Elf_Ehdr *eh = (Elf_Ehdr *)p; if (filesize < (off_t)sizeof(Elf_Ehdr)) { warnx("file too small to be ELF"); return 0; } if (eh->e_ehsize < sizeof(Elf_Ehdr) || !IS_ELF(*eh)) return 0; if (eh->e_ident[EI_CLASS] != ELFCLASS) { warnx("unexpected word size %u", eh->e_ident[EI_CLASS]); return 0; } if (eh->e_ident[EI_VERSION] != ELF_TARG_VER) { warnx("unexpected version %u", eh->e_ident[EI_VERSION]); return 0; } if (eh->e_ident[EI_DATA] >= ELFDATANUM) { warnx("unexpected data format %u", eh->e_ident[EI_DATA]); return 0; } if (eh->e_shoff > filesize) { warnx("bogus section table offset 0x%llx", (off_t)eh->e_shoff); return 0; } if (eh->e_shentsize < sizeof(Elf_Shdr)) { warnx("bogus section header size %u", eh->e_shentsize); return 0; } if (eh->e_shnum > (filesize - eh->e_shoff) / eh->e_shentsize) { warnx("bogus section header count %u", eh->e_shnum); return 0; } if (eh->e_shstrndx >= eh->e_shnum) { warnx("bogus string table index %u", eh->e_shstrndx); return 0; } return 1; } int elf_getshstab(const char *p, size_t filesize, const char **shstab, size_t *shstabsize) { Elf_Ehdr *eh = (Elf_Ehdr *)p; Elf_Shdr *sh; size_t shoff; shoff = eh->e_shoff + eh->e_shstrndx * eh->e_shentsize; if (shoff > (filesize - sizeof(*sh))) { warnx("unexpected string table size"); return -1; } sh = (Elf_Shdr *)(p + shoff); if (sh->sh_type != SHT_STRTAB) { warnx("unexpected string table type"); return -1; } if (sh->sh_offset > filesize) { warnx("bogus string table offset"); return -1; } if (sh->sh_size > filesize - sh->sh_offset) { warnx("bogus string table size"); return -1; } if (shstab != NULL) *shstab = p + sh->sh_offset; if (shstabsize != NULL) *shstabsize = sh->sh_size; return 0; } ssize_t elf_getsymtab(const char *p, size_t filesize, const char *shstab, size_t shstabsz, const Elf_Sym **symtab, size_t *nsymb, const char **strtab, size_t *strtabsz) { Elf_Ehdr *eh = (Elf_Ehdr *)p; Elf_Shdr *sh, *symsh; size_t snlen, shoff; ssize_t i; snlen = strlen(ELF_SYMTAB); symsh = NULL; for (i = 0; i < eh->e_shnum; i++) { shoff = eh->e_shoff + i * eh->e_shentsize; if (shoff > (filesize - sizeof(*sh))) continue; sh = (Elf_Shdr *)(p + shoff); if (sh->sh_type != SHT_SYMTAB) continue; if ((sh->sh_link >= eh->e_shnum) || (sh->sh_name >= shstabsz)) continue; if (sh->sh_offset > filesize) continue; if (sh->sh_size > (filesize - sh->sh_offset)) continue; if (sh->sh_entsize == 0) continue; if (strncmp(shstab + sh->sh_name, ELF_SYMTAB, snlen) == 0) { if (symtab != NULL) *symtab = (Elf_Sym *)(p + sh->sh_offset); if (nsymb != NULL) *nsymb = (sh->sh_size / sh->sh_entsize); symsh = sh; break; } } if (symsh == NULL || (symsh->sh_link >= eh->e_shnum)) return -1; shoff = eh->e_shoff + symsh->sh_link * eh->e_shentsize; if (shoff > (filesize - sizeof(*sh))) return -1; sh = (Elf_Shdr *)(p + shoff); if ((sh->sh_offset + sh->sh_size) > filesize) return -1; if (strtab != NULL) *strtab = p + sh->sh_offset; if (strtabsz != NULL) *strtabsz = sh->sh_size; return i; } ssize_t elf_getsection(char *p, size_t filesize, const char *sname, const char *shstab, size_t shstabsz, const char **psdata, size_t *pssz) { Elf_Ehdr *eh = (Elf_Ehdr *)p; Elf_Shdr *sh; char *sdata = NULL; size_t snlen, shoff, ssz = 0; ssize_t sidx, i; snlen = strlen(sname); if (snlen == 0) return -1; /* Find the given section. */ for (i = 0; i < eh->e_shnum; i++) { shoff = eh->e_shoff + i * eh->e_shentsize; if (shoff > (filesize - sizeof(*sh))) continue; sh = (Elf_Shdr *)(p + shoff); if ((sh->sh_link >= eh->e_shnum) || (sh->sh_name >= shstabsz)) continue; if (sh->sh_offset > filesize) continue; if (sh->sh_size > (filesize - sh->sh_offset)) continue; if (strncmp(shstab + sh->sh_name, sname, snlen) == 0) { sidx = i; sdata = p + sh->sh_offset; ssz = sh->sh_size; elf_reloc_apply(p, filesize, shstab, shstabsz, sidx, sdata, ssz); break; } } if (sdata == NULL) return -1; if (psdata != NULL) *psdata = sdata; if (pssz != NULL) *pssz = ssz; return sidx; } static int elf_reloc_size(unsigned long type) { switch (type) { #ifdef R_X86_64_64 case R_X86_64_64: return sizeof(uint64_t); #endif #ifdef R_X86_64_32 case R_X86_64_32: return sizeof(uint32_t); #endif #ifdef RELOC_32 case RELOC_32: return sizeof(uint32_t); #endif default: break; } return -1; } #define ELF_WRITE_RELOC(buf, val, rsize) \ do { \ if (rsize == 4) { \ uint32_t v32 = val; \ memcpy(buf, &v32, sizeof(v32)); \ } else { \ uint64_t v64 = val; \ memcpy(buf, &v64, sizeof(v64)); \ } \ } while (0) static void elf_reloc_apply(const char *p, size_t filesize, const char *shstab, size_t shstabsz, ssize_t sidx, char *sdata, size_t ssz) { Elf_Ehdr *eh = (Elf_Ehdr *)p; Elf_Shdr *sh; Elf_Rel *rel = NULL; Elf_RelA *rela = NULL; const Elf_Sym *symtab, *sym; ssize_t symtabidx; size_t nsymb, rsym, rtyp, roff; size_t shoff, i, j; uint64_t value; int rsize; /* Find symbol table location and number of symbols. */ symtabidx = elf_getsymtab(p, filesize, shstab, shstabsz, &symtab, &nsymb, NULL, NULL); if (symtabidx == -1) { warnx("symbol table not found"); return; } /* Apply possible relocation. */ for (i = 0; i < eh->e_shnum; i++) { shoff = eh->e_shoff + i * eh->e_shentsize; if (shoff > (filesize - sizeof(*sh))) continue; sh = (Elf_Shdr *)(p + shoff); if (sh->sh_size == 0) continue; if ((sh->sh_info != sidx) || (sh->sh_link != symtabidx)) continue; if (sh->sh_offset > filesize) continue; if (sh->sh_size > (filesize - sh->sh_offset)) continue; switch (sh->sh_type) { case SHT_RELA: rela = (Elf_RelA *)(p + sh->sh_offset); for (j = 0; j < (sh->sh_size / sizeof(Elf_RelA)); j++) { rsym = ELF_R_SYM(rela[j].r_info); rtyp = ELF_R_TYPE(rela[j].r_info); roff = rela[j].r_offset; if (rsym >= nsymb) continue; if (roff >= filesize) continue; sym = &symtab[rsym]; value = sym->st_value + rela[j].r_addend; rsize = elf_reloc_size(rtyp); if (rsize == -1 || roff + rsize >= ssz) continue; ELF_WRITE_RELOC(sdata + roff, value, rsize); } break; case SHT_REL: rel = (Elf_Rel *)(p + sh->sh_offset); for (j = 0; j < (sh->sh_size / sizeof(Elf_Rel)); j++) { rsym = ELF_R_SYM(rel[j].r_info); rtyp = ELF_R_TYPE(rel[j].r_info); roff = rel[j].r_offset; if (rsym >= nsymb) continue; if (roff >= filesize) continue; sym = &symtab[rsym]; value = sym->st_value; rsize = elf_reloc_size(rtyp); if (rsize == -1 || roff + rsize >= ssz) continue; ELF_WRITE_RELOC(sdata + roff, value, rsize); } break; default: continue; } } }