/* * 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)); 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 -ENOENT; } 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 -ENOENT; } /* 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 -ENOENT; } 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; }