diff options
Diffstat (limited to 'libkmod/libkmod-elf.c')
-rw-r--r-- | libkmod/libkmod-elf.c | 1219 |
1 files changed, 1219 insertions, 0 deletions
diff --git a/libkmod/libkmod-elf.c b/libkmod/libkmod-elf.c new file mode 100644 index 0000000..933825b --- /dev/null +++ b/libkmod/libkmod-elf.c @@ -0,0 +1,1219 @@ +/* + * libkmod - interface to kernel module operations + * + * Copyright (C) 2011-2013 ProFUSION embedded systems + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, see <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <elf.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +#include <shared/util.h> + +#include "libkmod.h" +#include "libkmod-internal.h" + +enum kmod_elf_class { + KMOD_ELF_32 = (1 << 1), + KMOD_ELF_64 = (1 << 2), + KMOD_ELF_LSB = (1 << 3), + KMOD_ELF_MSB = (1 << 4) +}; + +/* as defined in module-init-tools */ +struct kmod_modversion32 { + uint32_t crc; + char name[64 - sizeof(uint32_t)]; +}; + +struct kmod_modversion64 { + uint64_t crc; + char name[64 - sizeof(uint64_t)]; +}; + +struct kmod_elf { + const uint8_t *memory; + uint8_t *changed; + uint64_t size; + enum kmod_elf_class class; + struct kmod_elf_header { + struct { + uint64_t offset; + uint16_t count; + uint16_t entry_size; + } section; + struct { + uint16_t section; /* index of the strings section */ + uint64_t size; + uint64_t offset; + uint32_t nameoff; /* offset in strings itself */ + } strings; + uint16_t machine; + } header; +}; + +//#define ENABLE_ELFDBG 1 + +#if defined(ENABLE_LOGGING) && defined(ENABLE_ELFDBG) +#define ELFDBG(elf, ...) \ + _elf_dbg(elf, __FILE__, __LINE__, __func__, __VA_ARGS__); + +static inline void _elf_dbg(const struct kmod_elf *elf, const char *fname, unsigned line, const char *func, const char *fmt, ...) +{ + va_list args; + + fprintf(stderr, "ELFDBG-%d%c: %s:%u %s() ", + (elf->class & KMOD_ELF_32) ? 32 : 64, + (elf->class & KMOD_ELF_MSB) ? 'M' : 'L', + fname, line, func); + va_start(args, fmt); + vfprintf(stderr, fmt, args); + va_end(args); +} +#else +#define ELFDBG(elf, ...) +#endif + + +static int elf_identify(const void *memory, uint64_t size) +{ + const uint8_t *p = memory; + int class = 0; + + if (size <= EI_NIDENT || memcmp(p, ELFMAG, SELFMAG) != 0) + return -ENOEXEC; + + switch (p[EI_CLASS]) { + case ELFCLASS32: + if (size <= sizeof(Elf32_Ehdr)) + return -EINVAL; + class |= KMOD_ELF_32; + break; + case ELFCLASS64: + if (size <= sizeof(Elf64_Ehdr)) + return -EINVAL; + class |= KMOD_ELF_64; + break; + default: + return -EINVAL; + } + + switch (p[EI_DATA]) { + case ELFDATA2LSB: + class |= KMOD_ELF_LSB; + break; + case ELFDATA2MSB: + class |= KMOD_ELF_MSB; + break; + default: + return -EINVAL; + } + + return class; +} + +static inline uint64_t elf_get_uint(const struct kmod_elf *elf, uint64_t offset, uint16_t size) +{ + const uint8_t *p; + uint64_t ret = 0; + size_t i; + + assert(size <= sizeof(uint64_t)); + assert(offset + size <= elf->size); + if (offset + size > elf->size) { + ELFDBG(elf, "out of bounds: %"PRIu64" + %"PRIu16" = %"PRIu64"> %"PRIu64" (ELF size)\n", + offset, size, offset + size, elf->size); + return (uint64_t)-1; + } + + p = elf->memory + offset; + if (elf->class & KMOD_ELF_MSB) { + for (i = 0; i < size; i++) + ret = (ret << 8) | p[i]; + } else { + for (i = 1; i <= size; i++) + ret = (ret << 8) | p[size - i]; + } + + ELFDBG(elf, "size=%"PRIu16" offset=%"PRIu64" value=%"PRIu64"\n", + size, offset, ret); + + return ret; +} + +static inline int elf_set_uint(struct kmod_elf *elf, uint64_t offset, uint64_t size, uint64_t value) +{ + uint8_t *p; + size_t i; + + ELFDBG(elf, "size=%"PRIu16" offset=%"PRIu64" value=%"PRIu64" write memory=%p\n", + size, offset, value, elf->changed); + + assert(size <= sizeof(uint64_t)); + assert(offset + size <= elf->size); + if (offset + size > elf->size) { + ELFDBG(elf, "out of bounds: %"PRIu64" + %"PRIu16" = %"PRIu64"> %"PRIu64" (ELF size)\n", + offset, size, offset + size, elf->size); + return -1; + } + + if (elf->changed == NULL) { + elf->changed = malloc(elf->size); + if (elf->changed == NULL) + return -errno; + memcpy(elf->changed, elf->memory, elf->size); + elf->memory = elf->changed; + ELFDBG(elf, "copied memory to allow writing.\n"); + } + + p = elf->changed + offset; + if (elf->class & KMOD_ELF_MSB) { + for (i = 1; i <= size; i++) { + p[size - i] = value & 0xff; + value = (value & 0xffffffffffffff00) >> 8; + } + } else { + for (i = 0; i < size; i++) { + p[i] = value & 0xff; + value = (value & 0xffffffffffffff00) >> 8; + } + } + + return 0; +} + +static inline const void *elf_get_mem(const struct kmod_elf *elf, uint64_t offset) +{ + assert(offset < elf->size); + if (offset >= elf->size) { + ELFDBG(elf, "out-of-bounds: %"PRIu64" >= %"PRIu64" (ELF size)\n", + offset, elf->size); + return NULL; + } + return elf->memory + offset; +} + +static inline const void *elf_get_section_header(const struct kmod_elf *elf, uint16_t idx) +{ + assert(idx != SHN_UNDEF); + assert(idx < elf->header.section.count); + if (idx == SHN_UNDEF || idx >= elf->header.section.count) { + ELFDBG(elf, "invalid section number: %"PRIu16", last=%"PRIu16"\n", + idx, elf->header.section.count); + return NULL; + } + return elf_get_mem(elf, elf->header.section.offset + + (uint64_t)(idx * elf->header.section.entry_size)); +} + +static inline int elf_get_section_info(const struct kmod_elf *elf, uint16_t idx, uint64_t *offset, uint64_t *size, uint32_t *nameoff) +{ + const uint8_t *p = elf_get_section_header(elf, idx); + uint64_t min_size, off = p - elf->memory; + + if (p == NULL) { + ELFDBG(elf, "no section at %"PRIu16"\n", idx); + *offset = 0; + *size = 0; + *nameoff = 0; + return -EINVAL; + } + +#define READV(field) \ + elf_get_uint(elf, off + offsetof(typeof(*hdr), field), sizeof(hdr->field)) + + if (elf->class & KMOD_ELF_32) { + const Elf32_Shdr *hdr _unused_ = (const Elf32_Shdr *)p; + *size = READV(sh_size); + *offset = READV(sh_offset); + *nameoff = READV(sh_name); + } else { + const Elf64_Shdr *hdr _unused_ = (const Elf64_Shdr *)p; + *size = READV(sh_size); + *offset = READV(sh_offset); + *nameoff = READV(sh_name); + } +#undef READV + + if (addu64_overflow(*offset, *size, &min_size) + || min_size > elf->size) { + ELFDBG(elf, "out-of-bounds: %"PRIu64" >= %"PRIu64" (ELF size)\n", + min_size, elf->size); + return -EINVAL; + } + + ELFDBG(elf, "section=%"PRIu16" is: offset=%"PRIu64" size=%"PRIu64" nameoff=%"PRIu32"\n", + idx, *offset, *size, *nameoff); + + return 0; +} + +static const char *elf_get_strings_section(const struct kmod_elf *elf, uint64_t *size) +{ + *size = elf->header.strings.size; + return elf_get_mem(elf, elf->header.strings.offset); +} + +struct kmod_elf *kmod_elf_new(const void *memory, off_t size) +{ + struct kmod_elf *elf; + uint64_t min_size; + size_t shdrs_size, shdr_size; + int class; + + assert_cc(sizeof(uint16_t) == sizeof(Elf32_Half)); + assert_cc(sizeof(uint16_t) == sizeof(Elf64_Half)); + assert_cc(sizeof(uint32_t) == sizeof(Elf32_Word)); + assert_cc(sizeof(uint32_t) == sizeof(Elf64_Word)); + + if (!memory) { + errno = -EINVAL; + return NULL; + } + + class = elf_identify(memory, size); + if (class < 0) { + errno = -class; + return NULL; + } + + elf = malloc(sizeof(struct kmod_elf)); + if (elf == NULL) { + return NULL; + } + + elf->memory = memory; + elf->changed = NULL; + elf->size = size; + elf->class = class; + +#define READV(field) \ + elf_get_uint(elf, offsetof(typeof(*hdr), field), sizeof(hdr->field)) + +#define LOAD_HEADER \ + elf->header.section.offset = READV(e_shoff); \ + elf->header.section.count = READV(e_shnum); \ + elf->header.section.entry_size = READV(e_shentsize); \ + elf->header.strings.section = READV(e_shstrndx); \ + elf->header.machine = READV(e_machine) + if (elf->class & KMOD_ELF_32) { + const Elf32_Ehdr *hdr _unused_ = elf_get_mem(elf, 0); + LOAD_HEADER; + shdr_size = sizeof(Elf32_Shdr); + } else { + const Elf64_Ehdr *hdr _unused_ = elf_get_mem(elf, 0); + LOAD_HEADER; + shdr_size = sizeof(Elf64_Shdr); + } +#undef LOAD_HEADER +#undef READV + + ELFDBG(elf, "section: offset=%"PRIu64" count=%"PRIu16" entry_size=%"PRIu16" strings index=%"PRIu16"\n", + elf->header.section.offset, + elf->header.section.count, + elf->header.section.entry_size, + elf->header.strings.section); + + if (elf->header.section.entry_size != shdr_size) { + ELFDBG(elf, "unexpected section entry size: %"PRIu16", expected %"PRIu16"\n", + elf->header.section.entry_size, shdr_size); + goto invalid; + } + shdrs_size = shdr_size * elf->header.section.count; + if (addu64_overflow(shdrs_size, elf->header.section.offset, &min_size) + || min_size > elf->size) { + ELFDBG(elf, "file is too short to hold sections\n"); + goto invalid; + } + + if (elf_get_section_info(elf, elf->header.strings.section, + &elf->header.strings.offset, + &elf->header.strings.size, + &elf->header.strings.nameoff) < 0) { + ELFDBG(elf, "could not get strings section\n"); + goto invalid; + } else { + uint64_t slen; + const char *s = elf_get_strings_section(elf, &slen); + if (slen == 0 || s[slen - 1] != '\0') { + ELFDBG(elf, "strings section does not ends with \\0\n"); + goto invalid; + } + } + + return elf; + +invalid: + free(elf); + errno = EINVAL; + return NULL; +} + +void kmod_elf_unref(struct kmod_elf *elf) +{ + free(elf->changed); + free(elf); +} + +const void *kmod_elf_get_memory(const struct kmod_elf *elf) +{ + return elf->memory; +} + +static int elf_find_section(const struct kmod_elf *elf, const char *section) +{ + uint64_t nameslen; + const char *names = elf_get_strings_section(elf, &nameslen); + uint16_t i; + + for (i = 1; i < elf->header.section.count; i++) { + uint64_t off, size; + uint32_t nameoff; + const char *n; + int err = elf_get_section_info(elf, i, &off, &size, &nameoff); + if (err < 0) + continue; + if (nameoff >= nameslen) + continue; + n = names + nameoff; + if (!streq(section, n)) + continue; + + return i; + } + + return -ENODATA; +} + +int kmod_elf_get_section(const struct kmod_elf *elf, const char *section, const void **buf, uint64_t *buf_size) +{ + uint64_t nameslen; + const char *names = elf_get_strings_section(elf, &nameslen); + uint16_t i; + + *buf = NULL; + *buf_size = 0; + + for (i = 1; i < elf->header.section.count; i++) { + uint64_t off, size; + uint32_t nameoff; + const char *n; + int err = elf_get_section_info(elf, i, &off, &size, &nameoff); + if (err < 0) + continue; + if (nameoff >= nameslen) + continue; + n = names + nameoff; + if (!streq(section, n)) + continue; + + *buf = elf_get_mem(elf, off); + *buf_size = size; + return 0; + } + + return -ENODATA; +} + +/* array will be allocated with strings in a single malloc, just free *array */ +int kmod_elf_get_strings(const struct kmod_elf *elf, const char *section, char ***array) +{ + size_t i, j, count; + uint64_t size; + const void *buf; + const char *strings; + char *s, **a; + int err; + + *array = NULL; + + err = kmod_elf_get_section(elf, section, &buf, &size); + if (err < 0) + return err; + + strings = buf; + if (strings == NULL || size == 0) + return 0; + + /* skip zero padding */ + while (strings[0] == '\0' && size > 1) { + strings++; + size--; + } + + if (size <= 1) + return 0; + + for (i = 0, count = 0; i < size; ) { + if (strings[i] != '\0') { + i++; + continue; + } + + while (strings[i] == '\0' && i < size) + i++; + + count++; + } + + if (strings[i - 1] != '\0') + count++; + + *array = a = malloc(size + 1 + sizeof(char *) * (count + 1)); + if (*array == NULL) + return -errno; + + s = (char *)(a + count + 1); + memcpy(s, strings, size); + + /* make sure the last string is NULL-terminated */ + s[size] = '\0'; + a[count] = NULL; + a[0] = s; + + for (i = 0, j = 1; j < count && i < size; ) { + if (s[i] != '\0') { + i++; + continue; + } + + while (strings[i] == '\0' && i < size) + i++; + + a[j] = &s[i]; + j++; + } + + return count; +} + +/* array will be allocated with strings in a single malloc, just free *array */ +int kmod_elf_get_modversions(const struct kmod_elf *elf, struct kmod_modversion **array) +{ + size_t off, offcrc, slen; + uint64_t size; + struct kmod_modversion *a; + const void *buf; + char *itr; + int i, count, err; +#define MODVERSION_SEC_SIZE (sizeof(struct kmod_modversion64)) + + assert_cc(sizeof(struct kmod_modversion64) == + sizeof(struct kmod_modversion32)); + + if (elf->class & KMOD_ELF_32) + offcrc = sizeof(uint32_t); + else + offcrc = sizeof(uint64_t); + + *array = NULL; + + err = kmod_elf_get_section(elf, "__versions", &buf, &size); + if (err < 0) + return err; + + if (buf == NULL || size == 0) + return 0; + + if (size % MODVERSION_SEC_SIZE != 0) + return -EINVAL; + + count = size / MODVERSION_SEC_SIZE; + + off = (const uint8_t *)buf - elf->memory; + slen = 0; + + for (i = 0; i < count; i++, off += MODVERSION_SEC_SIZE) { + const char *symbol = elf_get_mem(elf, off + offcrc); + + if (symbol[0] == '.') + symbol++; + + slen += strlen(symbol) + 1; + } + + *array = a = malloc(sizeof(struct kmod_modversion) * count + slen); + if (*array == NULL) + return -errno; + + itr = (char *)(a + count); + off = (const uint8_t *)buf - elf->memory; + + for (i = 0; i < count; i++, off += MODVERSION_SEC_SIZE) { + uint64_t crc = elf_get_uint(elf, off, offcrc); + const char *symbol = elf_get_mem(elf, off + offcrc); + size_t symbollen; + + if (symbol[0] == '.') + symbol++; + + a[i].crc = crc; + a[i].bind = KMOD_SYMBOL_UNDEF; + a[i].symbol = itr; + symbollen = strlen(symbol) + 1; + memcpy(itr, symbol, symbollen); + itr += symbollen; + } + + return count; +} + +int kmod_elf_strip_section(struct kmod_elf *elf, const char *section) +{ + uint64_t off, size; + const void *buf; + int idx = elf_find_section(elf, section); + uint64_t val; + + if (idx < 0) + return idx; + + buf = elf_get_section_header(elf, idx); + off = (const uint8_t *)buf - elf->memory; + + if (elf->class & KMOD_ELF_32) { + off += offsetof(Elf32_Shdr, sh_flags); + size = sizeof(((Elf32_Shdr *)buf)->sh_flags); + } else { + off += offsetof(Elf64_Shdr, sh_flags); + size = sizeof(((Elf64_Shdr *)buf)->sh_flags); + } + + val = elf_get_uint(elf, off, size); + val &= ~(uint64_t)SHF_ALLOC; + + return elf_set_uint(elf, off, size, val); +} + +int kmod_elf_strip_vermagic(struct kmod_elf *elf) +{ + uint64_t i, size; + const void *buf; + const char *strings; + int err; + + err = kmod_elf_get_section(elf, ".modinfo", &buf, &size); + if (err < 0) + return err; + strings = buf; + if (strings == NULL || size == 0) + return 0; + + /* skip zero padding */ + while (strings[0] == '\0' && size > 1) { + strings++; + size--; + } + if (size <= 1) + return 0; + + for (i = 0; i < size; i++) { + const char *s; + size_t off, len; + + if (strings[i] == '\0') + continue; + if (i + 1 >= size) + continue; + + s = strings + i; + len = sizeof("vermagic=") - 1; + if (i + len >= size) + continue; + if (strncmp(s, "vermagic=", len) != 0) { + i += strlen(s); + continue; + } + off = (const uint8_t *)s - elf->memory; + + if (elf->changed == NULL) { + elf->changed = malloc(elf->size); + if (elf->changed == NULL) + return -errno; + memcpy(elf->changed, elf->memory, elf->size); + elf->memory = elf->changed; + ELFDBG(elf, "copied memory to allow writing.\n"); + } + + len = strlen(s); + ELFDBG(elf, "clear .modinfo vermagic \"%s\" (%zd bytes)\n", + s, len); + memset(elf->changed + off, '\0', len); + return 0; + } + + ELFDBG(elf, "no vermagic found in .modinfo\n"); + return -ENODATA; +} + + +static int kmod_elf_get_symbols_symtab(const struct kmod_elf *elf, struct kmod_modversion **array) +{ + uint64_t i, last, size; + const void *buf; + const char *strings; + char *itr; + struct kmod_modversion *a; + int count, err; + + *array = NULL; + + err = kmod_elf_get_section(elf, "__ksymtab_strings", &buf, &size); + if (err < 0) + return err; + strings = buf; + if (strings == NULL || size == 0) + return 0; + + /* skip zero padding */ + while (strings[0] == '\0' && size > 1) { + strings++; + size--; + } + if (size <= 1) + return 0; + + last = 0; + for (i = 0, count = 0; i < size; i++) { + if (strings[i] == '\0') { + if (last == i) { + last = i + 1; + continue; + } + count++; + last = i + 1; + } + } + if (strings[i - 1] != '\0') + count++; + + *array = a = malloc(size + 1 + sizeof(struct kmod_modversion) * count); + if (*array == NULL) + return -errno; + + itr = (char *)(a + count); + last = 0; + for (i = 0, count = 0; i < size; i++) { + if (strings[i] == '\0') { + size_t slen = i - last; + if (last == i) { + last = i + 1; + continue; + } + a[count].crc = 0; + a[count].bind = KMOD_SYMBOL_GLOBAL; + a[count].symbol = itr; + memcpy(itr, strings + last, slen); + itr[slen] = '\0'; + itr += slen + 1; + count++; + last = i + 1; + } + } + if (strings[i - 1] != '\0') { + size_t slen = i - last; + a[count].crc = 0; + a[count].bind = KMOD_SYMBOL_GLOBAL; + a[count].symbol = itr; + memcpy(itr, strings + last, slen); + itr[slen] = '\0'; + count++; + } + + return count; +} + +static inline uint8_t kmod_symbol_bind_from_elf(uint8_t elf_value) +{ + switch (elf_value) { + case STB_LOCAL: + return KMOD_SYMBOL_LOCAL; + case STB_GLOBAL: + return KMOD_SYMBOL_GLOBAL; + case STB_WEAK: + return KMOD_SYMBOL_WEAK; + default: + return KMOD_SYMBOL_NONE; + } +} + +static uint64_t kmod_elf_resolve_crc(const struct kmod_elf *elf, uint64_t crc, uint16_t shndx) +{ + int err; + uint64_t off, size; + uint32_t nameoff; + + if (shndx == SHN_ABS || shndx == SHN_UNDEF) + return crc; + + err = elf_get_section_info(elf, shndx, &off, &size, &nameoff); + if (err < 0) { + ELFDBG("Cound not find section index %"PRIu16" for crc", shndx); + return (uint64_t)-1; + } + + if (crc > (size - sizeof(uint32_t))) { + ELFDBG("CRC offset %"PRIu64" is too big, section %"PRIu16" size is %"PRIu64"\n", + crc, shndx, size); + return (uint64_t)-1; + } + + crc = elf_get_uint(elf, off + crc, sizeof(uint32_t)); + return crc; +} + +/* array will be allocated with strings in a single malloc, just free *array */ +int kmod_elf_get_symbols(const struct kmod_elf *elf, struct kmod_modversion **array) +{ + static const char crc_str[] = "__crc_"; + static const size_t crc_strlen = sizeof(crc_str) - 1; + uint64_t strtablen, symtablen, str_off, sym_off; + const void *strtab, *symtab; + struct kmod_modversion *a; + char *itr; + size_t slen, symlen; + int i, count, symcount, err; + + err = kmod_elf_get_section(elf, ".strtab", &strtab, &strtablen); + if (err < 0) { + ELFDBG(elf, "no .strtab found.\n"); + goto fallback; + } + + err = kmod_elf_get_section(elf, ".symtab", &symtab, &symtablen); + if (err < 0) { + ELFDBG(elf, "no .symtab found.\n"); + goto fallback; + } + + if (elf->class & KMOD_ELF_32) + symlen = sizeof(Elf32_Sym); + else + symlen = sizeof(Elf64_Sym); + + if (symtablen % symlen != 0) { + ELFDBG(elf, "unexpected .symtab of length %"PRIu64", not multiple of %"PRIu64" as expected.\n", symtablen, symlen); + goto fallback; + } + + symcount = symtablen / symlen; + count = 0; + slen = 0; + str_off = (const uint8_t *)strtab - elf->memory; + sym_off = (const uint8_t *)symtab - elf->memory + symlen; + for (i = 1; i < symcount; i++, sym_off += symlen) { + const char *name; + uint32_t name_off; + +#define READV(field) \ + elf_get_uint(elf, sym_off + offsetof(typeof(*s), field),\ + sizeof(s->field)) + if (elf->class & KMOD_ELF_32) { + Elf32_Sym *s; + name_off = READV(st_name); + } else { + Elf64_Sym *s; + name_off = READV(st_name); + } +#undef READV + if (name_off >= strtablen) { + ELFDBG(elf, ".strtab is %"PRIu64" bytes, but .symtab entry %d wants to access offset %"PRIu32".\n", strtablen, i, name_off); + goto fallback; + } + + name = elf_get_mem(elf, str_off + name_off); + + if (strncmp(name, crc_str, crc_strlen) != 0) + continue; + slen += strlen(name + crc_strlen) + 1; + count++; + } + + if (count == 0) + goto fallback; + + *array = a = malloc(sizeof(struct kmod_modversion) * count + slen); + if (*array == NULL) + return -errno; + + itr = (char *)(a + count); + count = 0; + str_off = (const uint8_t *)strtab - elf->memory; + sym_off = (const uint8_t *)symtab - elf->memory + symlen; + for (i = 1; i < symcount; i++, sym_off += symlen) { + const char *name; + uint32_t name_off; + uint64_t crc; + uint8_t info, bind; + uint16_t shndx; + +#define READV(field) \ + elf_get_uint(elf, sym_off + offsetof(typeof(*s), field),\ + sizeof(s->field)) + if (elf->class & KMOD_ELF_32) { + Elf32_Sym *s; + name_off = READV(st_name); + crc = READV(st_value); + info = READV(st_info); + shndx = READV(st_shndx); + } else { + Elf64_Sym *s; + name_off = READV(st_name); + crc = READV(st_value); + info = READV(st_info); + shndx = READV(st_shndx); + } +#undef READV + name = elf_get_mem(elf, str_off + name_off); + if (strncmp(name, crc_str, crc_strlen) != 0) + continue; + name += crc_strlen; + + if (elf->class & KMOD_ELF_32) + bind = ELF32_ST_BIND(info); + else + bind = ELF64_ST_BIND(info); + + a[count].crc = kmod_elf_resolve_crc(elf, crc, shndx); + a[count].bind = kmod_symbol_bind_from_elf(bind); + a[count].symbol = itr; + slen = strlen(name); + memcpy(itr, name, slen); + itr[slen] = '\0'; + itr += slen + 1; + count++; + } + return count; + +fallback: + ELFDBG(elf, "Falling back to __ksymtab_strings!\n"); + return kmod_elf_get_symbols_symtab(elf, array); +} + +static int kmod_elf_crc_find(const struct kmod_elf *elf, const void *versions, uint64_t versionslen, const char *name, uint64_t *crc) +{ + size_t verlen, crclen, off; + uint64_t i; + + if (elf->class & KMOD_ELF_32) { + struct kmod_modversion32 *mv; + verlen = sizeof(*mv); + crclen = sizeof(mv->crc); + } else { + struct kmod_modversion64 *mv; + verlen = sizeof(*mv); + crclen = sizeof(mv->crc); + } + + off = (const uint8_t *)versions - elf->memory; + for (i = 0; i < versionslen; i += verlen) { + const char *symbol = elf_get_mem(elf, off + i + crclen); + if (!streq(name, symbol)) + continue; + *crc = elf_get_uint(elf, off + i, crclen); + return i / verlen; + } + + ELFDBG(elf, "could not find crc for symbol '%s'\n", name); + *crc = 0; + return -1; +} + +/* from module-init-tools:elfops_core.c */ +#ifndef STT_REGISTER +#define STT_REGISTER 13 /* Global register reserved to app. */ +#endif + +/* array will be allocated with strings in a single malloc, just free *array */ +int kmod_elf_get_dependency_symbols(const struct kmod_elf *elf, struct kmod_modversion **array) +{ + uint64_t versionslen, strtablen, symtablen, str_off, sym_off, ver_off; + const void *versions, *strtab, *symtab; + struct kmod_modversion *a; + char *itr; + size_t slen, verlen, symlen, crclen; + int i, count, symcount, vercount, err; + bool handle_register_symbols; + uint8_t *visited_versions; + uint64_t *symcrcs; + + err = kmod_elf_get_section(elf, "__versions", &versions, &versionslen); + if (err < 0) { + versions = NULL; + versionslen = 0; + verlen = 0; + crclen = 0; + } else { + if (elf->class & KMOD_ELF_32) { + struct kmod_modversion32 *mv; + verlen = sizeof(*mv); + crclen = sizeof(mv->crc); + } else { + struct kmod_modversion64 *mv; + verlen = sizeof(*mv); + crclen = sizeof(mv->crc); + } + if (versionslen % verlen != 0) { + ELFDBG(elf, "unexpected __versions of length %"PRIu64", not multiple of %zd as expected.\n", versionslen, verlen); + versions = NULL; + versionslen = 0; + } + } + + err = kmod_elf_get_section(elf, ".strtab", &strtab, &strtablen); + if (err < 0) { + ELFDBG(elf, "no .strtab found.\n"); + return -EINVAL; + } + + err = kmod_elf_get_section(elf, ".symtab", &symtab, &symtablen); + if (err < 0) { + ELFDBG(elf, "no .symtab found.\n"); + return -EINVAL; + } + + if (elf->class & KMOD_ELF_32) + symlen = sizeof(Elf32_Sym); + else + symlen = sizeof(Elf64_Sym); + + if (symtablen % symlen != 0) { + ELFDBG(elf, "unexpected .symtab of length %"PRIu64", not multiple of %"PRIu64" as expected.\n", symtablen, symlen); + return -EINVAL; + } + + if (versionslen == 0) { + vercount = 0; + visited_versions = NULL; + } else { + vercount = versionslen / verlen; + visited_versions = calloc(vercount, sizeof(uint8_t)); + if (visited_versions == NULL) + return -ENOMEM; + } + + handle_register_symbols = (elf->header.machine == EM_SPARC || + elf->header.machine == EM_SPARCV9); + + symcount = symtablen / symlen; + count = 0; + slen = 0; + str_off = (const uint8_t *)strtab - elf->memory; + sym_off = (const uint8_t *)symtab - elf->memory + symlen; + + symcrcs = calloc(symcount, sizeof(uint64_t)); + if (symcrcs == NULL) { + free(visited_versions); + return -ENOMEM; + } + + for (i = 1; i < symcount; i++, sym_off += symlen) { + const char *name; + uint64_t crc; + uint32_t name_off; + uint16_t secidx; + uint8_t info; + int idx; + +#define READV(field) \ + elf_get_uint(elf, sym_off + offsetof(typeof(*s), field),\ + sizeof(s->field)) + if (elf->class & KMOD_ELF_32) { + Elf32_Sym *s; + name_off = READV(st_name); + secidx = READV(st_shndx); + info = READV(st_info); + } else { + Elf64_Sym *s; + name_off = READV(st_name); + secidx = READV(st_shndx); + info = READV(st_info); + } +#undef READV + if (secidx != SHN_UNDEF) + continue; + + if (handle_register_symbols) { + uint8_t type; + if (elf->class & KMOD_ELF_32) + type = ELF32_ST_TYPE(info); + else + type = ELF64_ST_TYPE(info); + + /* Not really undefined: sparc gcc 3.3 creates + * U references when you have global asm + * variables, to avoid anyone else misusing + * them. + */ + if (type == STT_REGISTER) + continue; + } + + if (name_off >= strtablen) { + ELFDBG(elf, ".strtab is %"PRIu64" bytes, but .symtab entry %d wants to access offset %"PRIu32".\n", strtablen, i, name_off); + free(visited_versions); + free(symcrcs); + return -EINVAL; + } + + name = elf_get_mem(elf, str_off + name_off); + if (name[0] == '\0') { + ELFDBG(elf, "empty symbol name at index %"PRIu64"\n", i); + continue; + } + + slen += strlen(name) + 1; + count++; + + idx = kmod_elf_crc_find(elf, versions, versionslen, name, &crc); + if (idx >= 0 && visited_versions != NULL) + visited_versions[idx] = 1; + symcrcs[i] = crc; + } + + if (visited_versions != NULL) { + /* module_layout/struct_module are not visited, but needed */ + ver_off = (const uint8_t *)versions - elf->memory; + for (i = 0; i < vercount; i++) { + if (visited_versions[i] == 0) { + const char *name; + name = elf_get_mem(elf, ver_off + i * verlen + crclen); + slen += strlen(name) + 1; + + count++; + } + } + } + + if (count == 0) { + free(visited_versions); + free(symcrcs); + *array = NULL; + return 0; + } + + *array = a = malloc(sizeof(struct kmod_modversion) * count + slen); + if (*array == NULL) { + free(visited_versions); + free(symcrcs); + return -errno; + } + + itr = (char *)(a + count); + count = 0; + str_off = (const uint8_t *)strtab - elf->memory; + sym_off = (const uint8_t *)symtab - elf->memory + symlen; + for (i = 1; i < symcount; i++, sym_off += symlen) { + const char *name; + uint64_t crc; + uint32_t name_off; + uint16_t secidx; + uint8_t info, bind; + +#define READV(field) \ + elf_get_uint(elf, sym_off + offsetof(typeof(*s), field),\ + sizeof(s->field)) + if (elf->class & KMOD_ELF_32) { + Elf32_Sym *s; + name_off = READV(st_name); + secidx = READV(st_shndx); + info = READV(st_info); + } else { + Elf64_Sym *s; + name_off = READV(st_name); + secidx = READV(st_shndx); + info = READV(st_info); + } +#undef READV + if (secidx != SHN_UNDEF) + continue; + + if (handle_register_symbols) { + uint8_t type; + if (elf->class & KMOD_ELF_32) + type = ELF32_ST_TYPE(info); + else + type = ELF64_ST_TYPE(info); + + /* Not really undefined: sparc gcc 3.3 creates + * U references when you have global asm + * variables, to avoid anyone else misusing + * them. + */ + if (type == STT_REGISTER) + continue; + } + + name = elf_get_mem(elf, str_off + name_off); + if (name[0] == '\0') { + ELFDBG(elf, "empty symbol name at index %"PRIu64"\n", i); + continue; + } + + if (elf->class & KMOD_ELF_32) + bind = ELF32_ST_BIND(info); + else + bind = ELF64_ST_BIND(info); + if (bind == STB_WEAK) + bind = KMOD_SYMBOL_WEAK; + else + bind = KMOD_SYMBOL_UNDEF; + + slen = strlen(name); + crc = symcrcs[i]; + + a[count].crc = crc; + a[count].bind = bind; + a[count].symbol = itr; + memcpy(itr, name, slen); + itr[slen] = '\0'; + itr += slen + 1; + + count++; + } + + free(symcrcs); + + if (visited_versions == NULL) + return count; + + /* add unvisited (module_layout/struct_module) */ + ver_off = (const uint8_t *)versions - elf->memory; + for (i = 0; i < vercount; i++) { + const char *name; + uint64_t crc; + + if (visited_versions[i] != 0) + continue; + + name = elf_get_mem(elf, ver_off + i * verlen + crclen); + slen = strlen(name); + crc = elf_get_uint(elf, ver_off + i * verlen, crclen); + + a[count].crc = crc; + a[count].bind = KMOD_SYMBOL_UNDEF; + a[count].symbol = itr; + memcpy(itr, name, slen); + itr[slen] = '\0'; + itr += slen + 1; + + count++; + } + free(visited_versions); + return count; +} |