aboutsummaryrefslogtreecommitdiffstats
path: root/elf.c
diff options
context:
space:
mode:
Diffstat (limited to 'elf.c')
-rw-r--r--elf.c287
1 files changed, 287 insertions, 0 deletions
diff --git a/elf.c b/elf.c
new file mode 100644
index 000000000000..d6b84f08f746
--- /dev/null
+++ b/elf.c
@@ -0,0 +1,287 @@
+/*
+ * 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/param.h>
+#include <sys/elf.h>
+
+#include <machine/reloc.h>
+
+#include <assert.h>
+#include <err.h>
+#include <string.h>
+
+#define ELF_SYMTAB ".symtab"
+#define Elf_RelA __CONCAT(__CONCAT(Elf,__ELF_WORD_SIZE),_Rela)
+
+static int elf_reloc_size(unsigned long);
+static void elf_reloc_apply(const char *, 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] != ELF_CLASS) {
+ 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] > ELFDATA2MSB) {
+ warnx("unexpected data format %u", eh->e_ident[EI_DATA]);
+ return 0;
+ }
+ if (eh->e_shoff > filesize) {
+ warnx("bogus section table offset 0x%lx", (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;
+
+ sh = (Elf_Shdr *)(p + eh->e_shoff + eh->e_shstrndx * eh->e_shentsize);
+ 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, const char *shstab, size_t shstabsz,
+ const Elf_Sym **symtab, size_t *nsymb)
+{
+ Elf_Ehdr *eh = (Elf_Ehdr *)p;
+ Elf_Shdr *sh;
+ size_t snlen;
+ ssize_t i;
+
+ snlen = strlen(ELF_SYMTAB);
+
+ for (i = 0; i < eh->e_shnum; i++) {
+ sh = (Elf_Shdr *)(p + eh->e_shoff + i * eh->e_shentsize);
+
+ if (sh->sh_type != SHT_SYMTAB)
+ continue;
+
+ if ((sh->sh_link >= eh->e_shnum) || (sh->sh_name >= shstabsz))
+ 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);
+
+ return i;
+ }
+ }
+
+ return -1;
+}
+
+ssize_t
+elf_getsection(char *p, 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, 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++) {
+ sh = (Elf_Shdr *)(p + eh->e_shoff + i * eh->e_shentsize);
+
+ if ((sh->sh_link >= eh->e_shnum) || (sh->sh_name >= shstabsz))
+ 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, 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, 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 i, j;
+ uint64_t value;
+ int rsize;
+
+ /* Find symbol table location and number of symbols. */
+ symtabidx = elf_getsymtab(p, shstab, shstabsz, &symtab, &nsymb);
+ if (symtabidx == -1) {
+ warnx("symbol table not found");
+ return;
+ }
+
+ /* Apply possible relocation. */
+ for (i = 0; i < eh->e_shnum; i++) {
+ sh = (Elf_Shdr *)(p + eh->e_shoff + i * eh->e_shentsize);
+
+ if (sh->sh_size == 0)
+ continue;
+
+ if ((sh->sh_info != sidx) || (sh->sh_link != symtabidx))
+ 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;
+ 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;
+ 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;
+ }
+ }
+}