/* Copyright(C) 2016, Red Hat, Inc., Stanislav Kozina This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ /* * This file contains the code which reads the __ksymtab section of the kernel * binaries to ensure that the symbol we parse is actually exported using the * EXPORT_SYMBOL() macro. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "main.h" #include "utils.h" #include "hash.h" #include "ksymtab.h" #define KSYMTAB_STRINGS "__ksymtab_strings" #define SYMTAB ".symtab" #define STRTAB ".strtab" #define KSYMTAB_SIZE 8192 struct ksymtab { struct hash *hash; size_t mark_count; }; struct ksym; static int elf_get_section(Elf *elf, size_t shstrndx, const char *section, const char **d_data, size_t *size) { Elf_Scn *scn; GElf_Shdr shdr; char *name; Elf_Data *data; scn = elf_nextscn(elf, NULL); for (; scn != NULL; scn = elf_nextscn(elf, scn)) { if (gelf_getshdr(scn, &shdr) != &shdr) fail("getshdr() failed: %s\n", elf_errmsg(-1)); name = elf_strptr(elf, shstrndx, shdr.sh_name); if (name == NULL) fail("elf_strptr() failed: %s\n", elf_errmsg(-1)); if (strcmp(name, section) == 0) break; } if (scn == NULL) /* no suitable section */ return -1; /* * This is unlucky. Fedora/EL builds -debuginfo packages by running * eu-strip --reloc-debug-sections which places only standard .debug* * sections into the -debuginfo modules. The sections which cannot * be stripped completely (because they are allocated) are changed * to SHT_NOBITS type to indicate you need to look in the original * (non-debug) module for them. But those are xzipped. * So we reject such stuff. We only support fresh output from the * kernel build. */ if (shdr.sh_type == SHT_NOBITS) { printf("The %s section has type SHT_NOBITS. Most likely you're " "running this tool on modules coming from kernel-debuginfo " "packages. They don't contain the %s section, you need to " "use the raw modules before they are stripped\n", section, section); exit(1); } if (gelf_getshdr(scn, &shdr) != &shdr) fail("getshdr() failed: %s\n", elf_errmsg(-1)); data = elf_getdata(scn, NULL); if (data == NULL || data->d_size == 0) fail("%s section empty!\n", section); *d_data = data->d_buf; *size = data->d_size; return 0; } struct elf_data *elf_open(const char *filename) { Elf *elf; int fd; int class; GElf_Ehdr *ehdr; size_t shstrndx; struct elf_data *data = NULL; if (elf_version(EV_CURRENT) == EV_NONE) fail("elf_version() failed: %s\n", elf_errmsg(-1)); fd = open(filename, O_RDONLY, 0); if (fd < 0) fail("Failed to open file %s: %s\n", filename, strerror(errno)); elf = elf_begin(fd, ELF_C_READ, NULL); if (elf == NULL) fail("elf_begin() failed: %s\n", elf_errmsg(-1)); if (elf_kind(elf) != ELF_K_ELF) { printf("Doesn't look like an ELF file, ignoring: %s\n", filename); (void) elf_end(elf); (void) close(fd); goto out; } ehdr = safe_zmalloc(sizeof(*ehdr)); if (gelf_getehdr(elf, ehdr) == NULL) fail("getehdr() failed: %s\n", elf_errmsg(-1)); class = gelf_getclass(elf); if (class != ELFCLASS64) { printf("Unsupported elf class of %s: %d\n", filename, class); free(ehdr); (void) elf_end(elf); (void) close(fd); goto out; } /* * Get section index of the string table associated with the section * headers in the ELF file. * Required by elf_get_section calls. */ if (elf_getshdrstrndx(elf, &shstrndx) != 0) fail("elf_getshdrstrndx() failed: %s\n", elf_errmsg(-1)) data = safe_zmalloc(sizeof(*data)); data->fd = fd; data->elf = elf; data->ehdr = ehdr; data->shstrndx = shstrndx; out: return data; } void elf_close(struct elf_data *ed) { if (ed == NULL) return; (void) elf_end(ed->elf); (void) close(ed->fd); } static void elf_for_each_global_sym(struct elf_data *ed, void (*fn)(const char *name, uint64_t value, int binding, void *ctx), void *ctx) { const Elf64_Sym *end; Elf64_Sym *sym; int binding; const char *name; const char *data; size_t size; if (elf_get_section(ed->elf, ed->shstrndx, SYMTAB, &data, &size) < 0) return; sym = (Elf64_Sym *)data; end = (Elf64_Sym *)(data + size); sym++; /* skip first zero record */ for (; sym < end; sym++) { binding = ELF64_ST_BIND(sym->st_info); if (!(binding == STB_GLOBAL || binding == STB_WEAK)) continue; if (sym->st_name == 0) continue; if (sym->st_name > ed->strtab_size) fail("Symbol name index %d out of range %ld\n", sym->st_name, ed->strtab_size); name = ed->strtab + sym->st_name; if (name == NULL) fail("Could not find symbol name\n"); fn(name, sym->st_value, binding, ctx); } } void ksymtab_ksym_mark(struct ksym *ksym) { if (!ksym->mark) ksym->ksymtab->mark_count++; ksym->mark = true; } static void ksymtab_ksym_free(void *arg) { struct ksym *ksym = arg; free(ksym->link); free(ksym); } void ksymtab_free(struct ksymtab *ksymtab) { struct hash *h; if (ksymtab == NULL) return; h = ksymtab->hash; hash_free(h); free(ksymtab); } struct ksymtab *ksymtab_new(size_t size) { struct hash *h; struct ksymtab *ksymtab; h = hash_new(size, ksymtab_ksym_free); assert(h != NULL); ksymtab = safe_zmalloc(sizeof(*ksymtab)); ksymtab->hash = h; /* ksymtab->mark_count is zeroed by the allocator */ return ksymtab; } struct ksym *ksymtab_add_sym(struct ksymtab *ksymtab, const char *str, size_t len, uint64_t value) { struct hash *h = ksymtab->hash; struct ksym *ksym; ksym = safe_zmalloc(sizeof(*ksym) + len + 1); memcpy(ksym->key, str, len); ksym->key[len] = '\0'; ksym->value = value; ksym->ksymtab = ksymtab; /* ksym->link is zeroed by the allocator */ hash_add(h, ksym->key, ksym); return ksym; } struct ksym *ksymtab_copy_sym(struct ksymtab *ksymtab, struct ksym *ksym) { const char *name = ksymtab_ksym_get_name(ksym); uint64_t value = ksymtab_ksym_get_value(ksym); char *link = ksymtab_ksym_get_link(ksym); struct ksym *new; new = ksymtab_add_sym(ksymtab, name, strlen(name), value); ksymtab_ksym_set_link(new, link); return new; } struct ksym *ksymtab_find(struct ksymtab *ksymtab, const char *name) { struct ksym *v; struct hash *h = ksymtab->hash; if (name == NULL) return NULL; v = hash_find(h, name); if (v == NULL) return NULL; return v; } size_t ksymtab_len(struct ksymtab *ksymtab) { struct hash *h; if (ksymtab == NULL) return 0; h = ksymtab->hash; return hash_get_count(h); } size_t ksymtab_mark_count(struct ksymtab *ksymtab) { return ksymtab->mark_count; } void ksymtab_for_each(struct ksymtab *ksymtab, void (*f)(struct ksym *, void *), void *ctx) { struct hash *h; struct hash_iter iter; const void *v; struct ksym *vv; if (ksymtab == NULL) return; h = ksymtab->hash; hash_iter_init(h, &iter); while (hash_iter_next(&iter, NULL, &v)) { vv = (struct ksym *)v; f(vv, ctx); } } /* Parses raw content of __ksymtab_strings section to a ksymtab */ static struct ksymtab *parse_ksymtab_strings(const char *d_buf, size_t d_size) { char *p, *oldp; size_t size = 0; size_t i = 0; struct ksymtab *res; res = ksymtab_new(KSYMTAB_SIZE); p = oldp = (char *)d_buf; /* Make sure we have the final '\0' */ if (p[d_size - 1] != '\0') fail("Mallformed " KSYMTAB_STRINGS " section: %s\n", p); for (size = 0; size < d_size; size++, p++) { /* End of symbol? */ if (*p == '\0') { size_t len = p - oldp; /* Skip empty strings */ if (len == 0) { oldp = p + 1; continue; } ksymtab_add_sym(res, oldp, len, i); i++; oldp = p + 1; } } return res; } /* * An entry for address -> symbol mapping. * The key will be "value". * We can use name pointer directly from the elf, * it will be freed later. */ struct map_entry { uint64_t value; const char *name; }; static struct map_entry *map_entry_new(uint64_t value, const char *name) { struct map_entry *res; res = safe_zmalloc(sizeof(*res)); res->value = value; res->name = name; return res; } struct weak_filter_ctx { struct ksymtab *ksymtab; struct ksymtab *weaks; struct hash *map; }; /* * Does two things in one pass on the symbol table: * 1) makes address -> symbol map for GLOBAL symbols; * 2) collecs subset of EXPORTed symbol, which have WEAK binding. */ static void weak_filter(const char *name, uint64_t value, int bind, void *_ctx) { struct weak_filter_ctx *ctx = _ctx; struct map_entry *m; struct ksym *ksym; if (bind == STB_GLOBAL) { m = map_entry_new(value, name); hash_add_bin(ctx->map, (const char *)&m->value, sizeof(m->value), m); return; } /* WEAK handling */ ksym = ksymtab_find(ctx->ksymtab, name); if (ksym == NULL) /* skip non-exported aliases */ return; ksymtab_add_sym(ctx->weaks, name, strlen(name), value); } struct weak_to_alias_ctx { struct ksymtab *aliases; struct hash *map; }; static void weak_to_alias(struct ksym *ksym, void *_ctx) { struct weak_to_alias_ctx *ctx = _ctx; struct map_entry *m; uint64_t value = ksymtab_ksym_get_value(ksym); const char *name = ksymtab_ksym_get_name(ksym); struct ksym *alias; m = hash_find_bin(ctx->map, (const char *)&value, sizeof(value)); if (m == NULL) /* there is no GLOBAL alias for the WEAK exported symbol */ return; alias = ksymtab_add_sym(ctx->aliases, m->name, strlen(m->name), 0); ksymtab_ksym_set_link(alias, name); } static struct ksymtab *ksymtab_weaks_to_aliases(struct ksymtab *weaks, struct hash *map) { struct ksymtab *aliases; struct weak_to_alias_ctx ctx; aliases = ksymtab_new(KSYMTAB_SIZE); if (aliases == NULL) fail("Cannot create ksymtab\n"); ctx.aliases = aliases; ctx.map = map; ksymtab_for_each(weaks, weak_to_alias, &ctx); return aliases; } /* * Generate weak aliases for the symbols, found in the list of exported. * It will work correctly for one alias only. */ static struct ksymtab *ksymtab_find_aliases(struct ksymtab *ksymtab, struct elf_data *elf) { struct ksymtab *aliases; struct ksymtab *weaks; struct hash *map; /* address to name mapping */ struct weak_filter_ctx ctx; weaks = ksymtab_new(KSYMTAB_SIZE); if (weaks == NULL) fail("Cannot create weaks symtab\n"); map = hash_new(KSYMTAB_SIZE, free); if (map == NULL) fail("Cannot create address->symbol mapping hash\n"); ctx.ksymtab = ksymtab; ctx.weaks = weaks; ctx.map = map; /* * If there's a weak symbol on the whitelist, * we need to find the proper global * symbol to generate the type for it. * * It is done in two steps below: * 1) create address -> global symbol mapping and * suitable weak symbol list; * 2) for all weak symbols find its alias with the mapping. */ elf_for_each_global_sym(elf, weak_filter, &ctx); aliases = ksymtab_weaks_to_aliases(weaks, map); hash_free(map); ksymtab_free(weaks); return aliases; } int elf_get_endianness(struct elf_data *data, unsigned int *endianness) { if (data->ehdr->e_ident[EI_DATA] != ELFDATA2LSB && data->ehdr->e_ident[EI_DATA] != ELFDATA2MSB) { printf("Unsupported ELF endianness (EI_DATA) found: %d.\n", data->ehdr->e_ident[EI_DATA]); return 1; } *endianness = data->ehdr->e_ident[EI_DATA]; return 0; } static inline int elf_get_strtab(struct elf_data *data) { const char *strtab; size_t strtab_size; if (elf_get_section(data->elf, data->shstrndx, STRTAB, &strtab, &strtab_size) < 0) { return 1; } data->strtab = strtab; data->strtab_size = strtab_size; return 0; } /* * Build list of exported symbols, ie. read seciton __ksymtab_strings, * analyze symbol table and create table of aliases -- list of global symbols, * which have the same addresses, as weak symbols, * mentioned by __ksymtab_strings */ int elf_get_exported(struct elf_data *data, struct ksymtab **ksymtab, struct ksymtab **aliases) { const char *ksymtab_raw; size_t ksymtab_sz; if (elf_get_strtab(data) > 0) return 1; if (elf_get_section(data->elf, data->shstrndx, KSYMTAB_STRINGS, &ksymtab_raw, &ksymtab_sz) < 0) return 1; *ksymtab = parse_ksymtab_strings(ksymtab_raw, ksymtab_sz); *aliases = ksymtab_find_aliases(*ksymtab, data); return 0; }