/*- * SPDX-License-Identifier: BSD-2-Clause * * Copyright (c) 2021-2023 John Baldwin * * This software was developed by SRI International and the University * of Cambridge Computer Laboratory (Department of Computer Science * and Technology) under Defense Advanced Research Projects Agency * (DARPA) contract HR0011-18-C-0016 ("ECATS"), as part of the DARPA * SSITH research programme and under DARPA Contract No. HR001123C0031 * ("MTSS"). * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #include #include #include #include #include #include #include #include #include #include "ef.h" SET_DECLARE(elf_reloc, struct elf_reloc_data); static elf_reloc_t * elf_find_reloc(const GElf_Ehdr *hdr) { struct elf_reloc_data **erd; SET_FOREACH(erd, elf_reloc) { if (hdr->e_ident[EI_CLASS] == (*erd)->class && hdr->e_ident[EI_DATA] == (*erd)->data && hdr->e_machine == (*erd)->machine) return ((*erd)->reloc); } return (NULL); } int elf_open_file(struct elf_file *efile, const char *filename, int verbose) { int error; memset(efile, 0, sizeof(*efile)); efile->ef_filename = filename; efile->ef_fd = open(filename, O_RDONLY); if (efile->ef_fd == -1) { if (verbose) warn("open(%s)", filename); return (errno); } efile->ef_elf = elf_begin(efile->ef_fd, ELF_C_READ, NULL); if (efile->ef_elf == NULL) { if (verbose) warnx("elf_begin(%s): %s", filename, elf_errmsg(0)); elf_close_file(efile); return (EINVAL); } if (elf_kind(efile->ef_elf) != ELF_K_ELF) { if (verbose) warnx("%s: not an ELF file", filename); elf_close_file(efile); return (EINVAL); } if (gelf_getehdr(efile->ef_elf, &efile->ef_hdr) == NULL) { if (verbose) warnx("gelf_getehdr(%s): %s", filename, elf_errmsg(0)); elf_close_file(efile); return (EINVAL); } efile->ef_reloc = elf_find_reloc(&efile->ef_hdr); if (efile->ef_reloc == NULL) { if (verbose) warnx("%s: unsupported architecture", filename); elf_close_file(efile); return (EFTYPE); } error = ef_open(efile, verbose); if (error != 0) { error = ef_obj_open(efile, verbose); if (error != 0) { if (verbose) warnc(error, "%s: not a valid DSO or object file", filename); elf_close_file(efile); return (error); } } efile->ef_pointer_size = elf_object_size(efile, ELF_T_ADDR); return (0); } void elf_close_file(struct elf_file *efile) { if (efile->ef_ops != NULL) { EF_CLOSE(efile); } if (efile->ef_elf != NULL) { elf_end(efile->ef_elf); efile->ef_elf = NULL; } if (efile->ef_fd > 0) { close(efile->ef_fd); efile->ef_fd = -1; } } bool elf_compatible(struct elf_file *efile, const GElf_Ehdr *hdr) { if (efile->ef_hdr.e_ident[EI_CLASS] != hdr->e_ident[EI_CLASS] || efile->ef_hdr.e_ident[EI_DATA] != hdr->e_ident[EI_DATA] || efile->ef_hdr.e_machine != hdr->e_machine) return (false); return (true); } size_t elf_object_size(struct elf_file *efile, Elf_Type type) { return (gelf_fsize(efile->ef_elf, type, 1, efile->ef_hdr.e_version)); } /* * The number of objects of 'type' in region of the file of size * 'file_size'. */ static size_t elf_object_count(struct elf_file *efile, Elf_Type type, size_t file_size) { return (file_size / elf_object_size(efile, type)); } int elf_read_raw_data(struct elf_file *efile, off_t offset, void *dst, size_t len) { ssize_t nread; nread = pread(efile->ef_fd, dst, len, offset); if (nread == -1) return (errno); if (nread != len) return (EIO); return (0); } int elf_read_raw_data_alloc(struct elf_file *efile, off_t offset, size_t len, void **out) { void *buf; int error; buf = malloc(len); if (buf == NULL) return (ENOMEM); error = elf_read_raw_data(efile, offset, buf, len); if (error != 0) { free(buf); return (error); } *out = buf; return (0); } int elf_read_raw_string(struct elf_file *efile, off_t offset, char *dst, size_t len) { ssize_t nread; nread = pread(efile->ef_fd, dst, len, offset); if (nread == -1) return (errno); if (nread == 0) return (EIO); /* A short read is ok so long as the data contains a terminator. */ if (strnlen(dst, nread) == nread) return (EFAULT); return (0); } int elf_read_data(struct elf_file *efile, Elf_Type type, off_t offset, size_t len, void **out) { Elf_Data dst, src; void *buf; int error; buf = malloc(len); if (buf == NULL) return (ENOMEM); error = elf_read_raw_data(efile, offset, buf, len); if (error != 0) { free(buf); return (error); } memset(&dst, 0, sizeof(dst)); memset(&src, 0, sizeof(src)); src.d_buf = buf; src.d_size = len; src.d_type = type; src.d_version = efile->ef_hdr.e_version; dst.d_buf = buf; dst.d_size = len; dst.d_version = EV_CURRENT; if (gelf_xlatetom(efile->ef_elf, &dst, &src, elf_encoding(efile)) == NULL) { free(buf); return (ENXIO); } if (dst.d_size != len) warnx("elf_read_data: translation of type %u size mismatch", type); *out = buf; return (0); } int elf_read_relocated_data(struct elf_file *efile, GElf_Addr address, size_t len, void **buf) { int error; void *p; p = malloc(len); if (p == NULL) return (ENOMEM); error = EF_SEG_READ_REL(efile, address, len, p); if (error != 0) { free(p); return (error); } *buf = p; return (0); } int elf_read_phdrs(struct elf_file *efile, size_t *nphdrp, GElf_Phdr **phdrp) { GElf_Phdr *phdr; size_t nphdr, i; int error; if (elf_getphdrnum(efile->ef_elf, &nphdr) == -1) return (EFTYPE); phdr = calloc(nphdr, sizeof(*phdr)); if (phdr == NULL) return (ENOMEM); for (i = 0; i < nphdr; i++) { if (gelf_getphdr(efile->ef_elf, i, &phdr[i]) == NULL) { error = EFTYPE; goto out; } } *nphdrp = nphdr; *phdrp = phdr; return (0); out: free(phdr); return (error); } int elf_read_shdrs(struct elf_file *efile, size_t *nshdrp, GElf_Shdr **shdrp) { GElf_Shdr *shdr; Elf_Scn *scn; size_t nshdr, i; int error; if (elf_getshdrnum(efile->ef_elf, &nshdr) == -1) return (EFTYPE); shdr = calloc(nshdr, sizeof(*shdr)); if (shdr == NULL) return (ENOMEM); for (i = 0; i < nshdr; i++) { scn = elf_getscn(efile->ef_elf, i); if (scn == NULL) { error = EFTYPE; goto out; } if (gelf_getshdr(scn, &shdr[i]) == NULL) { error = EFTYPE; goto out; } } *nshdrp = nshdr; *shdrp = shdr; return (0); out: free(shdr); return (error); } int elf_read_dynamic(struct elf_file *efile, int section_index, size_t *ndynp, GElf_Dyn **dynp) { GElf_Shdr shdr; Elf_Scn *scn; Elf_Data *data; GElf_Dyn *dyn; long i, ndyn; scn = elf_getscn(efile->ef_elf, section_index); if (scn == NULL) return (EINVAL); if (gelf_getshdr(scn, &shdr) == NULL) return (EINVAL); data = elf_getdata(scn, NULL); if (data == NULL) return (EINVAL); ndyn = elf_object_count(efile, ELF_T_DYN, shdr.sh_size); dyn = calloc(ndyn, sizeof(*dyn)); if (dyn == NULL) return (ENOMEM); for (i = 0; i < ndyn; i++) { if (gelf_getdyn(data, i, &dyn[i]) == NULL) { free(dyn); return (EINVAL); } } *ndynp = ndyn; *dynp = dyn; return (0); } int elf_read_symbols(struct elf_file *efile, int section_index, size_t *nsymp, GElf_Sym **symp) { GElf_Shdr shdr; Elf_Scn *scn; Elf_Data *data; GElf_Sym *sym; size_t i, nsym; scn = elf_getscn(efile->ef_elf, section_index); if (scn == NULL) return (EINVAL); if (gelf_getshdr(scn, &shdr) == NULL) return (EINVAL); data = elf_getdata(scn, NULL); if (data == NULL) return (EINVAL); nsym = elf_object_count(efile, ELF_T_SYM, shdr.sh_size); sym = calloc(nsym, sizeof(*sym)); if (sym == NULL) return (ENOMEM); for (i = 0; i < nsym; i++) { if (gelf_getsym(data, i, &sym[i]) == NULL) { free(sym); return (EINVAL); } } *nsymp = nsym; *symp = sym; return (0); } int elf_read_string_table(struct elf_file *efile, const GElf_Shdr *shdr, long *strcnt, char **strtab) { int error; if (shdr->sh_type != SHT_STRTAB) return (EINVAL); error = elf_read_raw_data_alloc(efile, shdr->sh_offset, shdr->sh_size, (void **)strtab); if (error != 0) return (error); *strcnt = shdr->sh_size; return (0); } int elf_read_rel(struct elf_file *efile, int section_index, long *nrelp, GElf_Rel **relp) { GElf_Shdr shdr; Elf_Scn *scn; Elf_Data *data; GElf_Rel *rel; long i, nrel; scn = elf_getscn(efile->ef_elf, section_index); if (scn == NULL) return (EINVAL); if (gelf_getshdr(scn, &shdr) == NULL) return (EINVAL); data = elf_getdata(scn, NULL); if (data == NULL) return (EINVAL); nrel = elf_object_count(efile, ELF_T_REL, shdr.sh_size); rel = calloc(nrel, sizeof(*rel)); if (rel == NULL) return (ENOMEM); for (i = 0; i < nrel; i++) { if (gelf_getrel(data, i, &rel[i]) == NULL) { free(rel); return (EINVAL); } } *nrelp = nrel; *relp = rel; return (0); } int elf_read_rela(struct elf_file *efile, int section_index, long *nrelap, GElf_Rela **relap) { GElf_Shdr shdr; Elf_Scn *scn; Elf_Data *data; GElf_Rela *rela; long i, nrela; scn = elf_getscn(efile->ef_elf, section_index); if (scn == NULL) return (EINVAL); if (gelf_getshdr(scn, &shdr) == NULL) return (EINVAL); data = elf_getdata(scn, NULL); if (data == NULL) return (EINVAL); nrela = elf_object_count(efile, ELF_T_RELA, shdr.sh_size); rela = calloc(nrela, sizeof(*rela)); if (rela == NULL) return (ENOMEM); for (i = 0; i < nrela; i++) { if (gelf_getrela(data, i, &rela[i]) == NULL) { free(rela); return (EINVAL); } } *nrelap = nrela; *relap = rela; return (0); } size_t elf_pointer_size(struct elf_file *efile) { return (efile->ef_pointer_size); } int elf_int(struct elf_file *efile, const void *p) { if (elf_encoding(efile) == ELFDATA2LSB) return (le32dec(p)); else return (be32dec(p)); } GElf_Addr elf_address_from_pointer(struct elf_file *efile, const void *p) { switch (elf_class(efile)) { case ELFCLASS32: if (elf_encoding(efile) == ELFDATA2LSB) return (le32dec(p)); else return (be32dec(p)); case ELFCLASS64: if (elf_encoding(efile) == ELFDATA2LSB) return (le64dec(p)); else return (be64dec(p)); default: __unreachable(); } } int elf_read_string(struct elf_file *efile, GElf_Addr address, void *dst, size_t len) { return (EF_SEG_READ_STRING(efile, address, len, dst)); } int elf_read_linker_set(struct elf_file *efile, const char *name, GElf_Addr **bufp, long *countp) { GElf_Addr *buf, start, stop; char *p; void *raw; long i, count; int error; error = EF_LOOKUP_SET(efile, name, &start, &stop, &count); if (error != 0) return (error); error = elf_read_relocated_data(efile, start, count * elf_pointer_size(efile), &raw); if (error != 0) return (error); buf = calloc(count, sizeof(*buf)); if (buf == NULL) { free(raw); return (ENOMEM); } p = raw; for (i = 0; i < count; i++) { buf[i] = elf_address_from_pointer(efile, p); p += elf_pointer_size(efile); } free(raw); *bufp = buf; *countp = count; return (0); } int elf_read_mod_depend(struct elf_file *efile, GElf_Addr addr, struct Gmod_depend *mdp) { int *p; int error; error = elf_read_relocated_data(efile, addr, sizeof(int) * 3, (void **)&p); if (error != 0) return (error); memset(mdp, 0, sizeof(*mdp)); mdp->md_ver_minimum = elf_int(efile, p); mdp->md_ver_preferred = elf_int(efile, p + 1); mdp->md_ver_maximum = elf_int(efile, p + 2); free(p); return (0); } int elf_read_mod_version(struct elf_file *efile, GElf_Addr addr, struct Gmod_version *mdv) { int error, value; error = EF_SEG_READ_REL(efile, addr, sizeof(int), &value); if (error != 0) return (error); memset(mdv, 0, sizeof(*mdv)); mdv->mv_version = elf_int(efile, &value); return (0); } int elf_read_mod_metadata(struct elf_file *efile, GElf_Addr addr, struct Gmod_metadata *md) { char *p; size_t len, offset, pointer_size; int error; pointer_size = elf_pointer_size(efile); len = 2 * sizeof(int); len = roundup(len, pointer_size); len += 2 * pointer_size; error = elf_read_relocated_data(efile, addr, len, (void **)&p); if (error != 0) return (error); memset(md, 0, sizeof(*md)); offset = 0; md->md_version = elf_int(efile, p + offset); offset += sizeof(int); md->md_type = elf_int(efile, p + offset); offset += sizeof(int); offset = roundup(offset, pointer_size); md->md_data = elf_address_from_pointer(efile, p + offset); offset += pointer_size; md->md_cval = elf_address_from_pointer(efile, p + offset); free(p); return (0); } int elf_read_mod_pnp_match_info(struct elf_file *efile, GElf_Addr addr, struct Gmod_pnp_match_info *pnp) { char *p; size_t len, offset, pointer_size; int error; pointer_size = elf_pointer_size(efile); len = 3 * pointer_size; len = roundup(len, pointer_size); len += 2 * sizeof(int); error = elf_read_relocated_data(efile, addr, len, (void **)&p); if (error != 0) return (error); memset(pnp, 0, sizeof(*pnp)); offset = 0; pnp->descr = elf_address_from_pointer(efile, p + offset); offset += pointer_size; pnp->bus = elf_address_from_pointer(efile, p + offset); offset += pointer_size; pnp->table = elf_address_from_pointer(efile, p + offset); offset += pointer_size; offset = roundup(offset, pointer_size); pnp->entry_len = elf_int(efile, p + offset); offset += sizeof(int); pnp->num_entry = elf_int(efile, p + offset); free(p); return (0); } int elf_reloc(struct elf_file *efile, const void *reldata, Elf_Type reltype, GElf_Addr relbase, GElf_Addr dataoff, size_t len, void *dest) { return (efile->ef_reloc(efile, reldata, reltype, relbase, dataoff, len, dest)); }