blob: 52b04339c36ea604a1f88ec1ae33392e3fad1622 [file] [log] [blame]
// 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_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",
};
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 inline 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 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;
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 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 inline 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 inline 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);
}
const void *kmod_elf_get_memory(const struct kmod_elf *elf)
{
return elf->memory;
}
/*
* 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_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_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;
}