/
githubmirror
/
kmod
Обзор
Документация
Войти
/
githubmirror
/
kmod
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
libkmod/libkmod-elf.c
1 310 строк
30 KB
Emil Velikov
Remove double-space in Copyright boilerplate
28 июл 2026, 17:11
28 июл 2026, 17:11
30d2108
Код
Авторство
О чём код?
// SPDX-License-Identifier: LGPL-2.1-or-later /* * Copyright (C) 2011-2013 ProFUSION embedded systems */ #include <assert.h> #include <elf.h> #include <endian.h> #include <limits.h> #include <stdlib.h> #include <string.h> #include <shared/util.h> #include "libkmod.h" #include "libkmod-internal.h" /* 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)]; }; enum kmod_elf_section { KMOD_ELF_SECTION_KSYMTAB, KMOD_ELF_SECTION_MODINFO, KMOD_ELF_SECTION_STRTAB, KMOD_ELF_SECTION_SYMTAB, KMOD_ELF_SECTION_VERSIONS, KMOD_ELF_SECTION_BUILD_ID, KMOD_ELF_SECTION_MAX, }; static const char *const section_name_map[] = { [KMOD_ELF_SECTION_KSYMTAB] = "__ksymtab_strings", [KMOD_ELF_SECTION_MODINFO] = ".modinfo", [KMOD_ELF_SECTION_STRTAB] = ".strtab", [KMOD_ELF_SECTION_SYMTAB] = ".symtab", [KMOD_ELF_SECTION_VERSIONS] = "__versions", [KMOD_ELF_SECTION_BUILD_ID] = ".note.gnu.build-id", }; struct kmod_elf { const uint8_t *memory; uint64_t size; bool x32; bool msb; struct { 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; } strings; uint16_t machine; } header; struct { uint64_t offset; uint64_t size; } sections[KMOD_ELF_SECTION_MAX]; }; //#undef ENABLE_ELFDBG //#define ENABLE_ELFDBG 1 #define ELFDBG(elf, ...) \ do { \ if (ENABLE_LOGGING == 1 && ENABLE_ELFDBG == 1) \ _elf_dbg(elf, __FILE__, __LINE__, __func__, __VA_ARGS__); \ } while (0); _printf_format_(5, 6) 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->x32 ? 32 : 64, elf->msb ? 'M' : 'L', fname, line, func); va_start(args, fmt); vfprintf(stderr, fmt, args); va_end(args); } static int elf_identify(struct kmod_elf *elf, const void *memory, uint64_t size) { const uint8_t *p = memory; if (size <= EI_NIDENT || memcmp(p, ELFMAG, SELFMAG) != 0) return -ENOEXEC; switch (p[EI_CLASS]) { case ELFCLASS32: if (size <= sizeof(Elf32_Ehdr)) return -EINVAL; elf->x32 = true; break; case ELFCLASS64: if (size <= sizeof(Elf64_Ehdr)) return -EINVAL; elf->x32 = false; break; default: return -EINVAL; } switch (p[EI_DATA]) { case ELFDATA2LSB: elf->msb = false; break; case ELFDATA2MSB: elf->msb = true; break; default: return -EINVAL; } return 0; } static bool elf_range_valid(const struct kmod_elf *elf, uint64_t offset, uint64_t size) { uint64_t min_size; if (uadd64_overflow(offset, size, &min_size) || min_size > elf->size) { ELFDBG(elf, "out of bounds: %" PRIu64 " + %" PRIu64 " > %" PRIu64 " (ELF size)\n", offset, size, elf->size); return false; } return true; } static uint64_t elf_get_uint(const struct kmod_elf *elf, uint64_t offset, uint16_t size) { const uint8_t *p; uint64_t ret = 0; assert(size <= sizeof(uint64_t)); p = elf->memory + offset; if (elf->msb) { memcpy((char *)&ret + sizeof(ret) - size, p, size); ret = be64toh(ret); } else { memcpy(&ret, p, size); ret = le64toh(ret); } ELFDBG(elf, "size=%" PRIu16 " offset=%" PRIu64 " value=%" PRIu64 "\n", size, offset, ret); return ret; } static inline uint32_t elf_get_u32(const struct kmod_elf *elf, uint64_t offset) { return elf_get_uint(elf, offset, sizeof(uint32_t)); } static int elf_set_uint(const struct kmod_elf *elf, uint64_t offset, uint64_t size, uint64_t value, uint8_t *changed) { uint8_t *p; size_t i; ELFDBG(elf, "size=%" PRIu64 " offset=%" PRIu64 " value=%" PRIu64 " write memory=%p\n", size, offset, value, changed); assert(size <= sizeof(uint64_t)); p = changed + offset; if (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) { return elf->memory + offset; } /* * Returns offset to section header for section with given index or 0 on error * (offset 0 cannot be a valid section offset because ELF header is located there). */ static uint64_t elf_get_section_header_offset(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 0; } return elf->header.section.offset + (uint64_t)(idx * elf->header.section.entry_size); } static int elf_get_section_info(const struct kmod_elf *elf, uint16_t idx, uint64_t *offset, uint64_t *size, const char **name) { uint64_t nameoff; uint64_t off = elf_get_section_header_offset(elf, idx); if (off == 0) { ELFDBG(elf, "no section at %" PRIu16 "\n", idx); goto fail; } #define READV(field) \ elf_get_uint(elf, off + offsetof(typeof(*hdr), field), sizeof(hdr->field)) if (elf->x32) { Elf32_Shdr *hdr; if (!elf_range_valid(elf, off, sizeof(*hdr))) goto fail; *size = READV(sh_size); *offset = READV(sh_offset); nameoff = READV(sh_name); } else { Elf64_Shdr *hdr; if (!elf_range_valid(elf, off, sizeof(*hdr))) goto fail; *size = READV(sh_size); *offset = READV(sh_offset); nameoff = READV(sh_name); } #undef READV if (!elf_range_valid(elf, *offset, *size)) goto fail; if (nameoff >= elf->header.strings.size) goto fail; *name = elf_get_mem(elf, elf->header.strings.offset + nameoff); ELFDBG(elf, "section=%" PRIu16 " is: offset=%" PRIu64 " size=%" PRIu64 " name=%s\n", idx, *offset, *size, *name); return 0; fail: *offset = 0; *size = 0; *name = NULL; return -EINVAL; } static void kmod_elf_save_sections(struct kmod_elf *elf) { const uint16_t all_sec = (1 << KMOD_ELF_SECTION_MAX) - 1; uint16_t found_sec = 0; enum kmod_elf_section sec; for (uint16_t i = 1; i < elf->header.section.count && found_sec != all_sec; i++) { uint64_t off, size; const char *n; int err = elf_get_section_info(elf, i, &off, &size, &n); if (err < 0) continue; for (sec = KMOD_ELF_SECTION_KSYMTAB; sec < KMOD_ELF_SECTION_MAX; sec++) { if (found_sec & (1 << sec)) continue; if (streq(section_name_map[sec], n)) { elf->sections[sec].offset = off; elf->sections[sec].size = size; found_sec |= 1 << sec; break; } } } for (sec = KMOD_ELF_SECTION_KSYMTAB; sec < KMOD_ELF_SECTION_MAX; sec++) { if (found_sec & (1 << sec)) continue; ELFDBG(elf, "section %s not found\n", section_name_map[sec]); elf->sections[sec].offset = 0; elf->sections[sec].size = 0; } } int kmod_elf_new(const void *memory, off_t size, struct kmod_elf **out_elf) { struct kmod_elf *elf; size_t shdrs_size, shdr_size; int err; const char *name; 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)); elf = malloc(sizeof(struct kmod_elf)); if (elf == NULL) return -ENOMEM; err = elf_identify(elf, memory, size); if (err < 0) { free(elf); return err; } elf->memory = memory; elf->size = size; #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->x32) { Elf32_Ehdr *hdr; shdr_size = sizeof(Elf32_Shdr); if (!elf_range_valid(elf, 0, sizeof(*hdr))) goto invalid; LOAD_HEADER; } else { Elf64_Ehdr *hdr; shdr_size = sizeof(Elf64_Shdr); if (!elf_range_valid(elf, 0, sizeof(*hdr))) goto invalid; LOAD_HEADER; } #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 %zu\n", elf->header.section.entry_size, shdr_size); goto invalid; } shdrs_size = shdr_size * elf->header.section.count; if (!elf_range_valid(elf, elf->header.section.offset, shdrs_size)) goto invalid; if (elf_get_section_info(elf, elf->header.strings.section, &elf->header.strings.offset, &elf->header.strings.size, &name) < 0) { ELFDBG(elf, "could not get strings section\n"); goto invalid; } else { uint64_t slen = elf->header.strings.size; const char *s = elf_get_mem(elf, elf->header.strings.offset); if (slen == 0 || s[slen - 1] != '\0') { ELFDBG(elf, "strings section does not end with \\0\n"); goto invalid; } } kmod_elf_save_sections(elf); *out_elf = elf; return 0; invalid: free(elf); return -EINVAL; } void kmod_elf_unref(struct kmod_elf *elf) { free(elf); } /* * Returns section index on success, negative value otherwise. * On success, sec_off and sec_size are range checked and valid. */ int kmod_elf_get_section(const struct kmod_elf *elf, const char *section, uint64_t *sec_off, uint64_t *sec_size) { uint16_t i; *sec_off = 0; *sec_size = 0; for (i = 1; i < elf->header.section.count; i++) { uint64_t off, size; const char *n; int err = elf_get_section_info(elf, i, &off, &size, &n); if (err < 0) continue; if (!streq(section, n)) continue; *sec_off = off; *sec_size = size; return i; } return -ENODATA; } /* array will be allocated with strings in a single malloc, just free *array */ int kmod_elf_get_modinfo_strings(const struct kmod_elf *elf, char ***array) { size_t i, j, count; size_t tmp_size, vec_size, total_size; uint64_t off, size; const char *strings; char *s, **a; *array = NULL; off = elf->sections[KMOD_ELF_SECTION_MODINFO].offset; size = elf->sections[KMOD_ELF_SECTION_MODINFO].size; if (off == 0) return -ENODATA; strings = elf_get_mem(elf, off); /* skip zero padding */ while (size > 1 && strings[0] == '\0') { 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++; /* (string vector + NULL) * sizeof(char *) + size + NUL */ if (uaddsz_overflow(count, 1, &tmp_size) || umulsz_overflow(sizeof(char *), tmp_size, &vec_size) || uaddsz_overflow(size, vec_size, &tmp_size) || uaddsz_overflow(1, tmp_size, &total_size)) { return -ENOMEM; } *array = a = malloc(total_size); if (*array == NULL) return -ENOMEM; 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 (i < size && s[i] == '\0') i++; a[j] = &s[i]; j++; } return count; } static inline void elf_get_modversion_lengths(const struct kmod_elf *elf, size_t *verlen, size_t *crclen, size_t *namlen) { assert_cc(sizeof(struct kmod_modversion64) == sizeof(struct kmod_modversion32)); if (elf->x32) { struct kmod_modversion32 *mv; *verlen = sizeof(*mv); *crclen = sizeof(mv->crc); *namlen = sizeof(mv->name); } else { struct kmod_modversion64 *mv; *verlen = sizeof(*mv); *crclen = sizeof(mv->crc); *namlen = sizeof(mv->name); } } /* 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 i, count, crclen, namlen, verlen; uint64_t off, sec_off, size; struct kmod_modversion *a; elf_get_modversion_lengths(elf, &verlen, &crclen, &namlen); *array = NULL; sec_off = elf->sections[KMOD_ELF_SECTION_VERSIONS].offset; size = elf->sections[KMOD_ELF_SECTION_VERSIONS].size; if (sec_off == 0) return -ENODATA; if (size == 0) return 0; if (size % verlen != 0) return -EINVAL; count = size / verlen; if (count > INT_MAX) { ELFDBG(elf, "too many modversions: %zu\n", count); return -EINVAL; } *array = a = malloc(sizeof(struct kmod_modversion) * count); if (*array == NULL) return -ENOMEM; for (i = 0, off = sec_off; i < count; i++, off += verlen) { uint64_t crc = elf_get_uint(elf, off, crclen); const char *symbol = elf_get_mem(elf, off + crclen); size_t nlen = strnlen(symbol, namlen); if (nlen == namlen) { ELFDBG(elf, "symbol name at index %zu too long\n", i); return -EINVAL; } if (symbol[0] == '.') symbol++; a[i].crc = crc; a[i].bind = KMOD_SYMBOL_UNDEF; a[i].symbol = symbol; } return count; } static int elf_strip_versions_section(const struct kmod_elf *elf, uint8_t *changed) { uint64_t off, size; const void *buf; /* the off and size values are not used, supply them as dummies */ int idx = kmod_elf_get_section(elf, "__versions", &off, &size); uint64_t val; if (idx < 0) return idx == -ENODATA ? 0 : idx; off = elf_get_section_header_offset(elf, idx); if (elf->x32) { 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, changed); } static int elf_strip_vermagic(const struct kmod_elf *elf, uint8_t *changed) { uint64_t i, sec_off, size; const char *strings; sec_off = elf->sections[KMOD_ELF_SECTION_MODINFO].offset; size = elf->sections[KMOD_ELF_SECTION_MODINFO].size; if (sec_off == 0) return 0; strings = elf_get_mem(elf, sec_off); /* skip zero padding */ while (size > 1 && strings[0] == '\0') { 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; if (i + strlen("vermagic=") >= size) continue; if (!strstartswith(s, "vermagic=")) { i += strlen(s); continue; } off = (const uint8_t *)s - elf->memory; len = strlen(s); ELFDBG(elf, "clear .modinfo vermagic \"%s\" (%zu bytes)\n", s, len); memset(changed + off, '\0', len); return 0; } ELFDBG(elf, "no vermagic found in .modinfo\n"); return -ENODATA; } int kmod_elf_strip(const struct kmod_elf *elf, unsigned int flags, const void **stripped) { uint8_t *changed; int err = 0; assert(flags & (KMOD_INSERT_FORCE_MODVERSION | KMOD_INSERT_FORCE_VERMAGIC)); changed = memdup(elf->memory, elf->size); if (changed == NULL) return -ENOMEM; ELFDBG(elf, "copied memory to allow writing.\n"); if (flags & KMOD_INSERT_FORCE_MODVERSION) { err = elf_strip_versions_section(elf, changed); if (err < 0) goto fail; } if (flags & KMOD_INSERT_FORCE_VERMAGIC) { err = elf_strip_vermagic(elf, changed); if (err < 0) goto fail; } *stripped = changed; return 0; fail: free(changed); return err; } static int kmod_elf_get_symbols_symtab(const struct kmod_elf *elf, struct kmod_modversion **array) { uint64_t i, last, off, size; const char *strings; struct kmod_modversion *a; size_t count, total_size; *array = NULL; off = elf->sections[KMOD_ELF_SECTION_KSYMTAB].offset; size = elf->sections[KMOD_ELF_SECTION_KSYMTAB].size; if (off == 0) return -ENODATA; strings = elf_get_mem(elf, off); /* skip zero padding */ while (size > 1 && strings[0] == '\0') { strings++; size--; } if (size <= 1) return 0; if (strings[size - 1] != '\0') { ELFDBG(elf, "section __ksymtab_strings does not end with \\0 byte"); return -EINVAL; } 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 (count > INT_MAX) { ELFDBG(elf, "too many symbols: %zu\n", count); return -EINVAL; } /* sizeof(struct kmod_modversion) * count */ if (umulsz_overflow(sizeof(struct kmod_modversion), count, &total_size)) { return -ENOMEM; } *array = a = malloc(total_size); if (*array == NULL) return -ENOMEM; last = 0; for (i = 0, count = 0; i < size; i++) { if (strings[i] == '\0') { if (last == i) { last = i + 1; continue; } a[count].crc = 0; a[count].bind = KMOD_SYMBOL_GLOBAL; a[count].symbol = strings + last; count++; last = i + 1; } } 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; const char *name; if (shndx == SHN_ABS || shndx == SHN_UNDEF) return crc; err = elf_get_section_info(elf, shndx, &off, &size, &name); if (err < 0) { ELFDBG(elf, "Could not find section index %" PRIu16 " for crc", shndx); return (uint64_t)-1; } if (size < sizeof(uint32_t) || crc > (size - sizeof(uint32_t))) { ELFDBG(elf, "CRC offset %" PRIu64 " is too big, section %" PRIu16 " size is %" PRIu64 "\n", crc, shndx, size); return (uint64_t)-1; } crc = elf_get_u32(elf, off + crc); 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_sec_off, sym_sec_off, str_off, sym_off; struct kmod_modversion *a; size_t i, count, symcount, symlen; str_sec_off = elf->sections[KMOD_ELF_SECTION_STRTAB].offset; strtablen = elf->sections[KMOD_ELF_SECTION_STRTAB].size; if (str_sec_off == 0) { ELFDBG(elf, "no .strtab found.\n"); goto fallback; } sym_sec_off = elf->sections[KMOD_ELF_SECTION_SYMTAB].offset; symtablen = elf->sections[KMOD_ELF_SECTION_SYMTAB].size; if (sym_sec_off == 0) { ELFDBG(elf, "no .symtab found.\n"); goto fallback; } if (elf->x32) symlen = sizeof(Elf32_Sym); else symlen = sizeof(Elf64_Sym); if (symtablen % symlen != 0) { ELFDBG(elf, "unexpected .symtab of length %" PRIu64 ", not multiple of %zu as expected.\n", symtablen, symlen); goto fallback; } symcount = symtablen / symlen; count = 0; str_off = str_sec_off; sym_off = sym_sec_off + 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->x32) { 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 %zu wants to access offset %" PRIu32 ".\n", strtablen, i, name_off); goto fallback; } name = elf_get_mem(elf, str_off + name_off); if (!strstartswith(name, crc_str)) continue; count++; } if (count == 0) goto fallback; *array = a = malloc(sizeof(struct kmod_modversion) * count); if (*array == NULL) return -ENOMEM; count = 0; str_off = str_sec_off; sym_off = sym_sec_off + 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->x32) { 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 (!strstartswith(name, crc_str)) continue; name += crc_strlen; if (elf->x32) 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 = name; 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, uint64_t off, uint64_t versionslen, const char *name, uint64_t *crc) { size_t namlen, verlen, crclen; uint64_t i; elf_get_modversion_lengths(elf, &verlen, &crclen, &namlen); for (i = 0; i < versionslen; i += verlen) { const char *symbol = elf_get_mem(elf, off + i + crclen); if (strnlen(symbol, namlen) == namlen || !streq(name, symbol)) { ELFDBG(elf, "symbol name at index %" PRIu64 " too long\n", i); 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; uint64_t str_sec_off, sym_sec_off; struct kmod_modversion *a; size_t i, count, namlen, vercount, verlen, symcount, symlen, crclen; bool handle_register_symbols; uint8_t *visited_versions; uint64_t *symcrcs; ver_off = elf->sections[KMOD_ELF_SECTION_VERSIONS].offset; versionslen = elf->sections[KMOD_ELF_SECTION_VERSIONS].size; if (ver_off == 0) { versionslen = 0; verlen = 0; crclen = 0; namlen = 0; } else { elf_get_modversion_lengths(elf, &verlen, &crclen, &namlen); if (versionslen % verlen != 0) { ELFDBG(elf, "unexpected __versions of length %" PRIu64 ", not multiple of %zu as expected.\n", versionslen, verlen); ver_off = 0; versionslen = 0; } } str_sec_off = elf->sections[KMOD_ELF_SECTION_STRTAB].offset; strtablen = elf->sections[KMOD_ELF_SECTION_STRTAB].size; if (str_sec_off == 0) { ELFDBG(elf, "no .strtab found.\n"); return -EINVAL; } sym_sec_off = elf->sections[KMOD_ELF_SECTION_SYMTAB].offset; symtablen = elf->sections[KMOD_ELF_SECTION_SYMTAB].size; if (sym_sec_off == 0) { ELFDBG(elf, "no .symtab found.\n"); return -EINVAL; } if (elf->x32) symlen = sizeof(Elf32_Sym); else symlen = sizeof(Elf64_Sym); if (symtablen % symlen != 0) { ELFDBG(elf, "unexpected .symtab of length %" PRIu64 ", not multiple of %zu 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; str_off = str_sec_off; sym_off = sym_sec_off + 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->x32) { 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->x32) 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 %zu 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 %zu\n", i); continue; } count++; idx = kmod_elf_crc_find(elf, ver_off, 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 */ for (i = 0; i < vercount; i++) { if (visited_versions[i] == 0) { const char *name; size_t nlen; name = elf_get_mem(elf, ver_off + i * verlen + crclen); nlen = strnlen(name, namlen); if (nlen == namlen) { ELFDBG(elf, "symbol name at index %zu too long\n", i); free(visited_versions); free(symcrcs); return -EINVAL; } count++; } } } if (count > INT_MAX) { ELFDBG(elf, "too many symbols: %zu\n", count); free(visited_versions); free(symcrcs); *array = NULL; return -EINVAL; } if (count == 0) { free(visited_versions); free(symcrcs); *array = NULL; return 0; } *array = a = malloc(sizeof(struct kmod_modversion) * count); if (*array == NULL) { free(visited_versions); free(symcrcs); return -ENOMEM; } count = 0; str_off = str_sec_off; sym_off = sym_sec_off + 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->x32) { 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->x32) 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 %zu\n", i); continue; } if (elf->x32) bind = ELF32_ST_BIND(info); else bind = ELF64_ST_BIND(info); if (bind == STB_WEAK) bind = KMOD_SYMBOL_WEAK; else bind = KMOD_SYMBOL_UNDEF; crc = symcrcs[i]; a[count].crc = crc; a[count].bind = bind; a[count].symbol = name; count++; } free(symcrcs); if (visited_versions == NULL) return count; /* add unvisited (module_layout/struct_module) */ 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); crc = elf_get_uint(elf, ver_off + i * verlen, crclen); a[count].crc = crc; a[count].bind = KMOD_SYMBOL_UNDEF; a[count].symbol = name; count++; } free(visited_versions); return count; } #ifndef NT_GNU_BUILD_ID #define NT_GNU_BUILD_ID 3 #endif /* * Read the .notes.gnu.build-id section that is added by the linker via --build-id. The * section format follows the one defined in shared/elf-note.h, with name == "GNU\0", * type == NT_GNU_BUILD_ID and desc being the hash. */ int kmod_elf_get_build_id(const struct kmod_elf *elf, const void **hash, size_t *hash_len) { uint32_t namesz, descsz, type; uint64_t off, size; const char *name; off = elf->sections[KMOD_ELF_SECTION_BUILD_ID].offset; size = elf->sections[KMOD_ELF_SECTION_BUILD_ID].size; if (off == 0 || size == 0) /* * Conditionally available since 4.10 if toolchain supports it. * Unconditionally available since 5.10: * kernel- commit 89ff7131f78a ("kbuild: add --hash-style= and * --build-id unconditionally"). Just return an error and don't * log anything. */ return -ENODATA; if (size < 3 * sizeof(uint32_t)) { ELFDBG(elf, "build-id section is too small: %" PRIu64 "\n", size); return -EINVAL; } namesz = elf_get_u32(elf, off); off += sizeof(uint32_t); descsz = elf_get_u32(elf, off); off += sizeof(uint32_t); type = elf_get_u32(elf, off); off += sizeof(uint32_t); if (size < 3 * sizeof(uint32_t) + namesz + descsz) { ELFDBG(elf, "build-id section is too small: %" PRIu64 "\n", size); return -EINVAL; } name = elf_get_mem(elf, off); if (type != NT_GNU_BUILD_ID || !streq(name, "GNU")) { ELFDBG(elf, "Invalid build-id section: type %" PRIu32 ", name %.3s\n", type, name); return -EINVAL; } off += namesz; *hash = elf_get_mem(elf, off); *hash_len = descsz; return 0; }