diff options
Diffstat (limited to 'lib/libkldelf/elf.c')
-rw-r--r-- | lib/libkldelf/elf.c | 695 |
1 files changed, 695 insertions, 0 deletions
diff --git a/lib/libkldelf/elf.c b/lib/libkldelf/elf.c new file mode 100644 index 000000000000..cf43d9bfd5fd --- /dev/null +++ b/lib/libkldelf/elf.c @@ -0,0 +1,695 @@ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2021-2023 John Baldwin <jhb@FreeBSD.org> + * + * 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 <sys/param.h> +#include <sys/endian.h> + +#include <err.h> +#include <errno.h> +#include <fcntl.h> +#include <gelf.h> +#include <libelf.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include "kldelf.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)); +} + +int +elf_lookup_symbol(struct elf_file *efile, const char *name, GElf_Sym **sym, + bool see_local) +{ + return (EF_LOOKUP_SYMBOL(efile, name, sym, see_local)); +} |