_ _
| |_ ___| |_ _
| | . | | | |
|_|_|___|_|_ |
|___|
git mirror - github.com/owenewans/holy - branch master
file src/elf.c
#define _POSIX_C_SOURCE 200809L
#include "elf.h"
#include <errno.h>
#include <fcntl.h>
#include <gelf.h>
#include <libelf.h>
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
/* older libc headers omit these x86 psabi note constants. */
#ifndef GNU_PROPERTY_X86_ISA_1_NEEDED
#define GNU_PROPERTY_X86_ISA_1_NEEDED 0xc0008002
#endif
#ifndef GNU_PROPERTY_X86_ISA_1_BASELINE
#define GNU_PROPERTY_X86_ISA_1_BASELINE (1U << 0)
#endif
#ifndef GNU_PROPERTY_X86_ISA_1_V2
#define GNU_PROPERTY_X86_ISA_1_V2 (1U << 1)
#endif
#ifndef GNU_PROPERTY_X86_ISA_1_V3
#define GNU_PROPERTY_X86_ISA_1_V3 (1U << 2)
#endif
#ifndef GNU_PROPERTY_X86_ISA_1_V4
#define GNU_PROPERTY_X86_ISA_1_V4 (1U << 3)
#endif
static int read_exact(int fd, char *out, size_t length, off_t offset)
{
size_t done = 0;
while (done < length) {
ssize_t got = pread(fd, out + done, length - done, offset + (off_t)done);
if (got < 0 && errno == EINTR) continue;
if (got <= 0) return 0;
done += (size_t)got;
}
return 1;
}
static uint32_t word32(const unsigned char *p, int little_endian)
{
if (little_endian)
return (uint32_t)p[0] | ((uint32_t)p[1] << 8) |
((uint32_t)p[2] << 16) | ((uint32_t)p[3] << 24);
return ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) |
((uint32_t)p[2] << 8) | (uint32_t)p[3];
}
static int read_property_note(const unsigned char *desc, size_t size,
int elf_class, int little_endian,
struct holy_elf_info *info)
{
size_t cursor = 0, align = elf_class == ELFCLASS64 ? 8 : 4;
while (cursor < size) {
uint32_t type, length;
size_t consumed, padded;
if (size - cursor < 8) return 0;
type = word32(desc + cursor, little_endian);
length = word32(desc + cursor + 4, little_endian);
if ((size_t)length > size - cursor - 8) return 0;
if (type == GNU_PROPERTY_X86_ISA_1_NEEDED) {
if (length != 4 || info->isa_present) return 0;
info->isa_needed = word32(desc + cursor + 8, little_endian);
info->isa_present = 1;
}
consumed = 8 + (size_t)length;
if (consumed > (size_t)-1 - (align - 1)) return 0;
padded = (consumed + align - 1) & ~(align - 1);
if (padded > size - cursor) return 0;
cursor += padded;
}
return 1;
}
static int read_notes(Elf *elf, const GElf_Phdr *phdr, uint64_t file_size,
int elf_class, int little_endian,
struct holy_elf_info *info)
{
Elf_Data *data;
size_t cursor = 0;
if (phdr->p_offset > file_size || phdr->p_filesz > file_size - phdr->p_offset ||
phdr->p_filesz > 16 * 1024 * 1024) return 0;
if (!phdr->p_filesz) return 1;
data = elf_getdata_rawchunk(elf, (off_t)phdr->p_offset,
(size_t)phdr->p_filesz,
elf_class == ELFCLASS64 && phdr->p_align >= 8 ?
ELF_T_NHDR8 : ELF_T_NHDR);
if (!data) return 0;
while (cursor < data->d_size) {
GElf_Nhdr note;
size_t name_offset, desc_offset;
size_t next = gelf_getnote(data, cursor, ¬e, &name_offset, &desc_offset);
const unsigned char *bytes = data->d_buf;
if (!next || next <= cursor || next > data->d_size ||
name_offset > data->d_size || note.n_namesz > data->d_size - name_offset ||
desc_offset > data->d_size || note.n_descsz > data->d_size - desc_offset)
return 0;
if (note.n_type == NT_GNU_PROPERTY_TYPE_0 && note.n_namesz == 4 &&
!memcmp(bytes + name_offset, "GNU\0", 4) &&
!read_property_note(bytes + desc_offset, note.n_descsz,
elf_class, little_endian, info)) return 0;
/* a build-id note states the identity a debug file keeps after the runtime
file is stripped, so the first one wins and a second is refused */
if (note.n_type == NT_GNU_BUILD_ID && note.n_namesz == 4 &&
!memcmp(bytes + name_offset, "GNU\0", 4)) {
size_t length = note.n_descsz, at;
const unsigned char *desc = bytes + desc_offset;
char *hex;
/* an empty or oversized note states no identity, and a second one is a
file this reader cannot describe */
if (info->build_id) return 0;
if (!length || length > 32) { cursor = next; continue; }
hex = malloc(length * 2 + 1);
if (!hex) return 0;
for (at = 0; at < length; ++at) snprintf(hex + at * 2, 3, "%02x", desc[at]);
hex[length * 2] = 0;
info->build_id = hex;
}
cursor = next;
}
return 1;
}
int holy_elf_build_id(const char *path, char *hex)
{
struct stat st;
Elf *elf = NULL;
Elf_Scn *section = NULL;
GElf_Ehdr ehdr;
off_t original;
int fd = open(path, O_RDONLY | O_CLOEXEC), found = 0;
if (fd < 0) return 0;
original = lseek(fd, 0, SEEK_CUR);
if (original < 0 || fstat(fd, &st) || !S_ISREG(st.st_mode)) goto done;
if (elf_version(EV_CURRENT) == EV_NONE) goto done;
elf = elf_begin(fd, ELF_C_READ, NULL);
if (!elf) goto done;
if (elf_kind(elf) != ELF_K_ELF || !gelf_getehdr(elf, &ehdr)) goto done;
/* a separate debug file keeps the note section and loses the segments a loadable
artifact needs, so the section is read where the loader would not look */
while (!found && (section = elf_nextscn(elf, section)) != NULL) {
GElf_Shdr header;
Elf_Data *data;
size_t cursor = 0;
if (!gelf_getshdr(section, &header) || header.sh_type != SHT_NOTE ||
!header.sh_size || header.sh_size > 64 * 1024 ||
header.sh_offset > (uint64_t)st.st_size ||
header.sh_size > (uint64_t)st.st_size - header.sh_offset) continue;
data = elf_getdata_rawchunk(elf, (off_t)header.sh_offset, (size_t)header.sh_size,
gelf_getclass(elf) == ELFCLASS64 && header.sh_addralign >= 8 ?
ELF_T_NHDR8 : ELF_T_NHDR);
if (!data) continue;
while (cursor < data->d_size) {
GElf_Nhdr note;
size_t name_offset, desc_offset, at;
size_t next = gelf_getnote(data, cursor, ¬e, &name_offset, &desc_offset);
const unsigned char *bytes = data->d_buf;
if (!next || next <= cursor || next > data->d_size ||
name_offset > data->d_size || note.n_namesz > data->d_size - name_offset ||
desc_offset > data->d_size || note.n_descsz > data->d_size - desc_offset)
break;
if (note.n_type == NT_GNU_BUILD_ID && note.n_namesz == 4 &&
!memcmp(bytes + name_offset, "GNU\0", 4) && note.n_descsz &&
note.n_descsz <= 32) {
for (at = 0; at < note.n_descsz; ++at)
snprintf(hex + at * 2, 3, "%02x", bytes[desc_offset + at]);
hex[note.n_descsz * 2] = 0;
found = 1;
break;
}
cursor = next;
}
}
done:
if (elf) elf_end(elf);
close(fd);
return found;
}
static char *dynamic_string(const char *table, size_t length, uint64_t offset,
int allow_empty)
{
const char *end;
char *copy;
size_t n, i;
if (offset >= length) return NULL;
end = memchr(table + offset, '\0', length - (size_t)offset);
if (!end) return NULL;
n = (size_t)(end - (table + offset));
if (!n && !allow_empty) return NULL;
for (i = 0; i < n; ++i)
if ((unsigned char)table[offset + i] < 32 || table[offset + i] == 127)
return NULL;
copy = malloc(n + 1);
if (copy) memcpy(copy, table + offset, n + 1);
return copy;
}
static int map_virtual(Elf *elf, size_t count, uint64_t address,
uint64_t minimum, uint64_t file_size,
uint64_t *offset, uint64_t *available)
{
size_t i;
for (i = 0; i < count; ++i) {
GElf_Phdr load;
uint64_t delta, start, length;
if (!gelf_getphdr(elf, (int)i, &load)) return 0;
if (load.p_type != PT_LOAD || address < load.p_vaddr) continue;
delta = address - load.p_vaddr;
if (delta > load.p_filesz || minimum > load.p_filesz - delta ||
load.p_offset > file_size || delta > file_size - load.p_offset)
continue;
start = load.p_offset + delta;
length = load.p_filesz - delta;
if (length > file_size - start) length = file_size - start;
if (minimum > length) continue;
*offset = start;
*available = length;
return 1;
}
return 0;
}
static int version_needs(Elf *elf, uint64_t offset, uint64_t available,
uint64_t count, const char *strings,
size_t string_length, struct holy_elf_info *info)
{
Elf_Data *data;
size_t min_need = gelf_fsize(elf, ELF_T_VNEED, 1, EV_CURRENT);
size_t min_aux = gelf_fsize(elf, ELF_T_VNAUX, 1, EV_CURRENT);
uint64_t cursor = 0, i;
if (available > 16 * 1024 * 1024) available = 16 * 1024 * 1024;
if (!min_need || !min_aux || !count || available < min_aux ||
count > available / min_need) return 0;
data = elf_getdata_rawchunk(elf, (off_t)offset, (size_t)available, ELF_T_VNEED);
if (!data) return 0;
for (i = 0; i < count; ++i) {
GElf_Verneed need;
uint64_t aux_cursor, j;
char *provider;
if (cursor > available - min_need || cursor > INT_MAX ||
!gelf_getverneed(data, (int)cursor, &need) || need.vn_version != 1 ||
!need.vn_cnt || need.vn_cnt > available / min_aux ||
!need.vn_aux || need.vn_aux > available - cursor) return 0;
provider = dynamic_string(strings, string_length, need.vn_file, 0);
if (!provider) return 0;
aux_cursor = cursor + need.vn_aux;
for (j = 0; j < need.vn_cnt; ++j) {
GElf_Vernaux aux;
struct holy_elf_version *next;
char *name, *source;
if (aux_cursor > available - min_aux || aux_cursor > INT_MAX ||
!gelf_getvernaux(data, (int)aux_cursor, &aux)) {
free(provider);
return 0;
}
name = dynamic_string(strings, string_length, aux.vna_name, 0);
source = strdup(provider);
if (!name || !source ||
info->version_count == (size_t)-1 / sizeof *info->versions) {
free(name);
free(source);
free(provider);
return 0;
}
next = realloc(info->versions,
(info->version_count + 1) * sizeof *info->versions);
if (!next) { free(name); free(source); free(provider); return 0; }
info->versions = next;
info->versions[info->version_count].provider = source;
info->versions[info->version_count].name = name;
info->versions[info->version_count].weak = !!(aux.vna_flags & VER_FLG_WEAK);
info->versions[info->version_count].index = aux.vna_other & 0x7fff;
++info->version_count;
if (j + 1 < need.vn_cnt) {
if (!aux.vna_next || aux.vna_next > available - aux_cursor) {
free(provider);
return 0;
}
aux_cursor += aux.vna_next;
}
}
free(provider);
if (i + 1 < count) {
if (!need.vn_next || need.vn_next > available - cursor) return 0;
cursor += need.vn_next;
}
}
return 1;
}
static int version_definitions(Elf *elf, uint64_t offset, uint64_t available,
uint64_t count, const char *strings,
size_t string_length, struct holy_elf_info *info)
{
Elf_Data *data;
size_t min_def = gelf_fsize(elf, ELF_T_VDEF, 1, EV_CURRENT);
size_t min_aux = gelf_fsize(elf, ELF_T_VDAUX, 1, EV_CURRENT);
uint64_t cursor = 0, i;
if (available > 16 * 1024 * 1024) available = 16 * 1024 * 1024;
if (!min_def || !min_aux || !count || available < min_aux ||
count > available / min_def) return 0;
data = elf_getdata_rawchunk(elf, (off_t)offset, (size_t)available, ELF_T_VDEF);
if (!data) return 0;
for (i = 0; i < count; ++i) {
GElf_Verdef def;
uint64_t aux_cursor, j;
if (cursor > available - min_def || cursor > INT_MAX ||
!gelf_getverdef(data, (int)cursor, &def) || def.vd_version != 1 ||
!def.vd_cnt || def.vd_cnt > available / min_aux ||
!def.vd_aux || def.vd_aux > available - cursor) return 0;
aux_cursor = cursor + def.vd_aux;
for (j = 0; j < def.vd_cnt; ++j) {
GElf_Verdaux aux;
char *name;
struct holy_elf_definition *next;
if (aux_cursor > available - min_aux || aux_cursor > INT_MAX ||
!gelf_getverdaux(data, (int)aux_cursor, &aux)) return 0;
name = dynamic_string(strings, string_length, aux.vda_name, 0);
if (!name || info->defined_version_count ==
(size_t)-1 / sizeof *info->defined_versions) {
free(name);
return 0;
}
if (j) free(name);
else {
next = realloc(info->defined_versions,
(info->defined_version_count + 1) *
sizeof *info->defined_versions);
if (!next) { free(name); return 0; }
info->defined_versions = next;
info->defined_versions[info->defined_version_count].name = name;
info->defined_versions[info->defined_version_count++].index = def.vd_ndx & 0x7fff;
}
if (j + 1 < def.vd_cnt) {
if (!aux.vda_next || aux.vda_next > available - aux_cursor) return 0;
aux_cursor += aux.vda_next;
}
}
if (i + 1 < count) {
if (!def.vd_next || def.vd_next > available - cursor) return 0;
cursor += def.vd_next;
}
}
return 1;
}
static int hash_symbol_count(Elf *elf, uint64_t file_size, size_t phdr_count,
uint64_t address, int gnu, size_t *count)
{
uint64_t offset, available, skip;
uint32_t *words, buckets, first, bloom, last = 0;
Elf_Data *data;
size_t i, chain;
if (!map_virtual(elf, phdr_count, address, gnu ? 16 : 8,
file_size, &offset, &available)) return 0;
data = elf_getdata_rawchunk(elf, (off_t)offset, gnu ? 16 : 8, ELF_T_WORD);
if (!data) return 0;
words = data->d_buf;
buckets = words[0];
first = words[1];
if (!gnu) {
if (!buckets || !first || (uint64_t)buckets + first > (available - 8) / 4)
return 0;
*count = first;
return 1;
}
bloom = words[2];
if (!buckets || !bloom || (bloom & (bloom - 1))) return 0;
skip = 16 + (uint64_t)bloom * (gelf_getclass(elf) == ELFCLASS64 ? 8 : 4);
if (skip > available || buckets > (available - skip) / 4 ||
buckets > 4 * 1024 * 1024) return 0;
data = elf_getdata_rawchunk(elf, (off_t)(offset + skip), (size_t)buckets * 4, ELF_T_WORD);
if (!data) return 0;
words = data->d_buf;
for (i = 0; i < buckets; ++i) {
if (words[i] && words[i] < first) return 0;
if (words[i] > last) last = words[i];
}
/* an empty gnu hash does not bound the unexported import symbols. */
if (!last) { *count = 0; return first != 0; }
skip += (uint64_t)buckets * 4;
chain = (size_t)last - first;
if (chain >= (available - skip) / 4) return 0;
available = (available - skip) / 4;
if (available > 4 * 1024 * 1024) available = 4 * 1024 * 1024;
data = elf_getdata_rawchunk(elf, (off_t)(offset + skip), (size_t)available * 4, ELF_T_WORD);
if (!data) return 0;
words = data->d_buf;
for (; chain < available; ++chain) {
if (words[chain] & 1) {
if ((uint64_t)first + chain + 1 > SIZE_MAX) return 0;
*count = (size_t)first + chain + 1;
return 1;
}
}
return 0;
}
static int dynamic_symbols(Elf *elf, uint64_t file_size, size_t phdr_count,
uint64_t address, uint64_t syment,
uint64_t hash, int has_hash, uint64_t gnu_hash,
int has_gnu_hash, uint64_t versym, int has_versym,
const char *strings, size_t string_length,
struct holy_elf_info *info)
{
struct version_ref { const char *name, *provider; } *versions = NULL;
Elf_Data *symbols, *indices = NULL;
uint64_t offset, available;
size_t count = 0, gnu_count = 0, names_size = 0, i;
size_t entry = gelf_fsize(elf, ELF_T_SYM, 1, EV_CURRENT);
int ok = 0;
if (!entry || syment != entry || (!has_hash && !has_gnu_hash)) return 0;
if (has_hash && !hash_symbol_count(elf, file_size, phdr_count, hash, 0, &count)) return 0;
if (has_gnu_hash && !hash_symbol_count(elf, file_size, phdr_count, gnu_hash, 1, &gnu_count)) return 0;
if (has_hash && has_gnu_hash && gnu_count && count != gnu_count) return 0;
if (!has_hash) count = gnu_count;
if (!count) {
Elf_Scn *section = NULL;
while ((section = elf_nextscn(elf, section)) != NULL) {
GElf_Shdr header;
if (!gelf_getshdr(section, &header)) return 0;
if (header.sh_type == SHT_DYNSYM && header.sh_addr == address) {
if (header.sh_entsize != entry || header.sh_size % entry ||
header.sh_size > 16 * 1024 * 1024) return 0;
count = (size_t)(header.sh_size / entry);
break;
}
}
}
if (!count || count > 16 * 1024 * 1024 / entry || count > INT_MAX ||
count > SIZE_MAX / sizeof *info->symbols ||
!map_virtual(elf, phdr_count, address, count * entry, file_size, &offset, &available)) return 0;
symbols = elf_getdata_rawchunk(elf, (off_t)offset, count * entry, ELF_T_SYM);
if (!symbols) return 0;
if (has_versym) {
if (!map_virtual(elf, phdr_count, versym, count * 2, file_size, &offset, &available)) return 0;
indices = elf_getdata_rawchunk(elf, (off_t)offset, count * 2, ELF_T_HALF);
if (!indices) return 0;
}
versions = calloc(32768, sizeof *versions);
if (!versions) return 0;
for (i = 0; i < info->version_count; ++i) {
struct holy_elf_version *v = &info->versions[i];
if (v->index < 2 || versions[v->index].name) goto done;
versions[v->index].name = v->name;
versions[v->index].provider = v->provider;
}
for (i = 0; i < info->defined_version_count; ++i) {
struct holy_elf_definition *v = &info->defined_versions[i];
if (!v->index || versions[v->index].name) goto done;
versions[v->index].name = v->name;
}
info->symbols = calloc(count, sizeof *info->symbols);
if (!info->symbols) goto done;
info->symbol_count = count;
for (i = 0; i < count; ++i) {
GElf_Sym symbol;
GElf_Versym index = 1;
struct holy_elf_symbol *out = &info->symbols[i];
if (!gelf_getsym(symbols, (int)i, &symbol) || symbol.st_shndx == SHN_XINDEX ||
(indices && !gelf_getversym(indices, (int)i, &index))) goto done;
if (!i && (symbol.st_name || symbol.st_info || symbol.st_other ||
symbol.st_shndx || symbol.st_value || symbol.st_size)) goto done;
out->name = dynamic_string(strings, string_length, symbol.st_name, 1);
if (!out->name) goto done;
if (strlen(out->name) + 1 > 64 * 1024 * 1024 - names_size) goto done;
names_size += strlen(out->name) + 1;
out->binding = GELF_ST_BIND(symbol.st_info);
out->type = GELF_ST_TYPE(symbol.st_info);
out->visibility = symbol.st_other & 7;
out->section = symbol.st_shndx;
out->version_index = index & 0x7fff;
out->version_hidden = !!(index & 0x8000);
if (out->version_index > 1) {
struct version_ref *v = &versions[out->version_index];
if (!v->name) goto done;
out->version = v->name;
out->provider = v->provider;
}
}
ok = 1;
done:
free(versions);
return ok;
}
static int dynamic_table(Elf *elf, int fd, uint64_t file_size,
size_t phdr_count, const GElf_Phdr *dynamic,
struct holy_elf_info *info)
{
Elf_Data *data;
uint64_t table_addr = 0, table_size = 0, soname = 0, rpath = 0, runpath = 0;
uint64_t verneed_addr = 0, verneed_num = 0;
uint64_t verdef_addr = 0, verdef_num = 0;
uint64_t symtab = 0, syment = 0, hash = 0, gnu_hash = 0, versym = 0;
uint64_t *needed = NULL;
size_t needed_count = 0, entry_size, entries, i, j;
int has_addr = 0, has_size = 0, has_soname = 0, has_rpath = 0, has_runpath = 0;
int has_verneed = 0, has_verneednum = 0;
int has_verdef = 0, has_verdefnum = 0;
int has_symtab = 0, has_syment = 0, has_hash = 0, has_gnu_hash = 0, has_versym = 0;
int has_flags1 = 0;
int ended = 0, ok = 0;
char *strings = NULL;
if (!dynamic->p_filesz || dynamic->p_filesz > 16 * 1024 * 1024 ||
dynamic->p_offset > file_size || dynamic->p_filesz > file_size - dynamic->p_offset)
return 0;
entry_size = gelf_fsize(elf, ELF_T_DYN, 1, EV_CURRENT);
if (!entry_size || dynamic->p_filesz % entry_size) return 0;
entries = (size_t)dynamic->p_filesz / entry_size;
data = elf_getdata_rawchunk(elf, (off_t)dynamic->p_offset,
(size_t)dynamic->p_filesz, ELF_T_DYN);
if (!data) return 0;
for (i = 0; i < entries; ++i) {
GElf_Dyn item;
uint64_t *next;
if (!gelf_getdyn(data, (int)i, &item)) goto done;
if (item.d_tag == DT_NULL) { ended = 1; break; }
if (item.d_tag == DT_FLAGS_1) {
if (has_flags1++) goto done;
info->flags1 = item.d_un.d_val;
} else if (item.d_tag == DT_SYMTAB) {
if (has_symtab++) goto done;
symtab = item.d_un.d_ptr;
} else if (item.d_tag == DT_SYMENT) {
if (has_syment++) goto done;
syment = item.d_un.d_val;
} else if (item.d_tag == DT_HASH) {
if (has_hash++) goto done;
hash = item.d_un.d_ptr;
} else if (item.d_tag == DT_GNU_HASH) {
if (has_gnu_hash++) goto done;
gnu_hash = item.d_un.d_ptr;
} else if (item.d_tag == DT_VERSYM) {
if (has_versym++) goto done;
versym = item.d_un.d_ptr;
} else if (item.d_tag == DT_STRTAB) {
if (has_addr++) goto done;
table_addr = item.d_un.d_ptr;
} else if (item.d_tag == DT_STRSZ) {
if (has_size++) goto done;
table_size = item.d_un.d_val;
} else if (item.d_tag == DT_NEEDED) {
if (needed_count == (size_t)-1 / sizeof *needed) goto done;
next = realloc(needed, (needed_count + 1) * sizeof *needed);
if (!next) goto done;
needed = next;
needed[needed_count++] = item.d_un.d_val;
} else if (item.d_tag == DT_SONAME) {
if (has_soname++) goto done;
soname = item.d_un.d_val;
} else if (item.d_tag == DT_RPATH) {
if (has_rpath++) goto done;
rpath = item.d_un.d_val;
} else if (item.d_tag == DT_RUNPATH) {
if (has_runpath++) goto done;
runpath = item.d_un.d_val;
} else if (item.d_tag == DT_VERNEED) {
if (has_verneed++) goto done;
verneed_addr = item.d_un.d_ptr;
} else if (item.d_tag == DT_VERNEEDNUM) {
if (has_verneednum++) goto done;
verneed_num = item.d_un.d_val;
} else if (item.d_tag == DT_VERDEF) {
if (has_verdef++) goto done;
verdef_addr = item.d_un.d_ptr;
} else if (item.d_tag == DT_VERDEFNUM) {
if (has_verdefnum++) goto done;
verdef_num = item.d_un.d_val;
}
}
if (!ended || has_symtab != has_syment ||
(!has_symtab && (has_hash || has_gnu_hash || has_versym))) goto done;
if (has_verneed != has_verneednum || has_verdef != has_verdefnum) goto done;
if (needed_count || has_soname || has_rpath || has_runpath || has_verneed ||
has_verdef || has_symtab) {
uint64_t string_offset = 0, available = 0;
if (!has_addr || !has_size || !table_size || table_size > 16 * 1024 * 1024)
goto done;
if (!map_virtual(elf, phdr_count, table_addr, table_size, file_size,
&string_offset, &available)) goto done;
strings = malloc((size_t)table_size);
if (!strings || !read_exact(fd, strings, (size_t)table_size, (off_t)string_offset))
goto done;
for (j = 0; j < needed_count; ++j) {
char **next_names;
char *name = dynamic_string(strings, (size_t)table_size, needed[j], 0);
if (!name || info->needed_count == (size_t)-1 / sizeof *info->needed) {
free(name);
goto done;
}
next_names = realloc(info->needed,
(info->needed_count + 1) * sizeof *info->needed);
if (!next_names) { free(name); goto done; }
info->needed = next_names;
info->needed[info->needed_count++] = name;
}
if (has_soname && !(info->soname = dynamic_string(strings, (size_t)table_size, soname, 0)))
goto done;
if (has_rpath && !(info->rpath = dynamic_string(strings, (size_t)table_size, rpath, 1)))
goto done;
if (has_runpath && !(info->runpath = dynamic_string(strings, (size_t)table_size, runpath, 1)))
goto done;
if (has_verneed) {
uint64_t need_offset, need_available;
size_t minimum = gelf_fsize(elf, ELF_T_VNEED, 1, EV_CURRENT);
if (!minimum || !map_virtual(elf, phdr_count, verneed_addr,
minimum, file_size, &need_offset, &need_available) ||
!version_needs(elf, need_offset, need_available, verneed_num,
strings, (size_t)table_size, info)) goto done;
}
if (has_verdef) {
uint64_t def_offset, def_available;
size_t minimum = gelf_fsize(elf, ELF_T_VDEF, 1, EV_CURRENT);
if (!minimum || !map_virtual(elf, phdr_count, verdef_addr,
minimum, file_size, &def_offset, &def_available) ||
!version_definitions(elf, def_offset, def_available, verdef_num,
strings, (size_t)table_size, info)) goto done;
}
}
if (has_symtab && (!strings || !dynamic_symbols(elf, file_size, phdr_count,
symtab, syment, hash, has_hash, gnu_hash, has_gnu_hash, versym,
has_versym, strings, (size_t)table_size, info))) goto done;
ok = 1;
done:
free(strings);
free(needed);
return ok;
}
int holy_elf_read_fd(int fd, struct holy_elf_info *info)
{
int result = 2, seen = 0, seen_dynamic = 0;
Elf *elf = NULL;
GElf_Ehdr ehdr;
struct stat st;
off_t original_offset;
size_t count, i;
GElf_Phdr dynamic = {0};
memset(info, 0, sizeof *info);
original_offset = lseek(fd, 0, SEEK_CUR);
if (original_offset < 0) return 2;
if (fstat(fd, &st) || !S_ISREG(st.st_mode)) goto done;
if (elf_version(EV_CURRENT) == EV_NONE) goto done;
elf = elf_begin(fd, ELF_C_READ, NULL);
if (!elf) goto done;
if (elf_kind(elf) != ELF_K_ELF) { result = 1; goto done; }
if (!gelf_getehdr(elf, &ehdr) || elf_getphdrnum(elf, &count) || count > INT_MAX)
goto done;
info->elf_class = gelf_getclass(elf);
info->machine = ehdr.e_machine;
info->type = ehdr.e_type;
if (info->elf_class != ELFCLASS32 && info->elf_class != ELFCLASS64) goto done;
for (i = 0; i < count; ++i) {
GElf_Phdr phdr;
char *end;
if (!gelf_getphdr(elf, i, &phdr)) goto done;
if (phdr.p_type == PT_DYNAMIC) {
if (seen_dynamic++) goto done;
dynamic = phdr;
info->has_dynamic = 1;
}
if (phdr.p_type == PT_NOTE &&
!read_notes(elf, &phdr, (uint64_t)st.st_size,
info->elf_class, ehdr.e_ident[EI_DATA] == ELFDATA2LSB,
info)) goto done;
if (phdr.p_type != PT_INTERP) continue;
if (seen++ || !phdr.p_filesz || phdr.p_filesz > 1024 * 1024 ||
phdr.p_offset > (uint64_t)st.st_size ||
phdr.p_filesz > (uint64_t)st.st_size - phdr.p_offset) goto done;
info->interpreter = malloc((size_t)phdr.p_filesz + 1);
if (!info->interpreter ||
!read_exact(fd, info->interpreter, (size_t)phdr.p_filesz, (off_t)phdr.p_offset))
goto done;
info->interpreter[phdr.p_filesz] = '\0';
end = memchr(info->interpreter, '\0', (size_t)phdr.p_filesz);
if (!end || end == info->interpreter) goto done;
{
char *p;
for (p = info->interpreter; p < end; ++p)
if ((unsigned char)*p < 32 || *p == 127) goto done;
for (p = end + 1; p < info->interpreter + phdr.p_filesz; ++p)
if (*p) goto done;
}
}
if (seen_dynamic && !dynamic_table(elf, fd, (uint64_t)st.st_size,
count, &dynamic, info)) goto done;
result = 0;
done:
if (elf) elf_end(elf);
if (lseek(fd, original_offset, SEEK_SET) < 0) result = 2;
return result;
}
int holy_elf_read(const char *path, struct holy_elf_info *info)
{
int fd = open(path, O_RDONLY | O_NOFOLLOW | O_CLOEXEC | O_NONBLOCK);
int result;
if (fd < 0) {
perror(path);
memset(info, 0, sizeof *info);
return 2;
}
result = holy_elf_read_fd(fd, info);
close(fd);
return result;
}
int holy_elf_kernel_module_fd(int fd, const struct holy_elf_info *info,
const char *release, size_t release_length)
{
Elf *elf;
Elf_Scn *section = NULL;
size_t strings;
int found = 0;
if (info->type != ET_REL || info->has_dynamic || info->interpreter ||
info->needed_count || info->soname || !release_length ||
elf_version(EV_CURRENT) == EV_NONE) return 0;
elf = elf_begin(fd, ELF_C_READ, NULL);
if (!elf) return 0;
if (elf_getshdrstrndx(elf, &strings)) goto done;
while ((section = elf_nextscn(elf, section))) {
GElf_Shdr header;
const char *name;
Elf_Data *data = NULL;
if (!gelf_getshdr(section, &header)) { found = 0; goto done; }
name = elf_strptr(elf, strings, header.sh_name);
if (!name) { found = 0; goto done; }
if (strcmp(name, ".modinfo")) continue;
if (header.sh_type != SHT_PROGBITS || header.sh_size > 1024 * 1024) {
found = 0;
goto done;
}
while ((data = elf_getdata(section, data))) {
const char *p = data->d_buf;
size_t left = data->d_size;
if (!p && left) { found = 0; goto done; }
while (left) {
const char *end = memchr(p, '\0', left);
size_t length;
if (!end) { found = 0; goto done; }
length = (size_t)(end - p);
if (length >= 9 + release_length &&
!memcmp(p, "vermagic=", 9) &&
!memcmp(p + 9, release, release_length) &&
(length == 9 + release_length ||
p[9 + release_length] == ' ')) found = 1;
left -= length + 1;
p = end + 1;
}
}
break;
}
done:
elf_end(elf);
return found;
}
void holy_elf_free(struct holy_elf_info *info)
{
free(info->interpreter);
info->interpreter = NULL;
{
size_t i;
for (i = 0; i < info->needed_count; ++i) free(info->needed[i]);
}
free(info->needed);
info->needed = NULL;
info->needed_count = 0;
free(info->soname);
free(info->rpath);
free(info->runpath);
free(info->build_id);
info->soname = info->rpath = info->runpath = info->build_id = NULL;
{
size_t i;
for (i = 0; i < info->version_count; ++i) {
free(info->versions[i].provider);
free(info->versions[i].name);
}
}
free(info->versions);
info->versions = NULL;
info->version_count = 0;
{
size_t i;
for (i = 0; i < info->defined_version_count; ++i)
free(info->defined_versions[i].name);
}
free(info->defined_versions);
info->defined_versions = NULL;
info->defined_version_count = 0;
{
size_t i;
for (i = 0; i < info->symbol_count; ++i) free(info->symbols[i].name);
}
free(info->symbols);
info->symbols = NULL;
info->symbol_count = 0;
}
const char *holy_elf_machine(const struct holy_elf_info *info)
{
if (info->machine == EM_386 && info->elf_class == ELFCLASS32) return "x86";
if (info->machine == EM_X86_64 && info->elf_class == ELFCLASS64) return "x86_64";
if (info->machine == EM_X86_64 && info->elf_class == ELFCLASS32) return "x32";
return "other";
}
const char *holy_elf_runtime(const struct holy_elf_info *info)
{
const char *base;
if (!info->interpreter) {
size_t i;
int glibc = 0, musl = 0;
if (info->type == ET_DYN && info->soname &&
((!strcmp(info->soname, "libc.musl-x86_64.so.1") &&
info->machine == EM_X86_64 && info->elf_class == ELFCLASS64) ||
(!strcmp(info->soname, "libc.musl-i386.so.1") &&
info->machine == EM_386 && info->elf_class == ELFCLASS32))) return "musl";
if (info->type == ET_DYN && info->soname &&
(!strcmp(info->soname, "libc.so.6") ||
(!strcmp(info->soname, "ld-linux-x86-64.so.2") &&
info->machine == EM_X86_64 && info->elf_class == ELFCLASS64) ||
(!strcmp(info->soname, "ld-linux.so.2") &&
info->machine == EM_386 && info->elf_class == ELFCLASS32))) return "glibc";
if (info->type == ET_DYN && info->has_dynamic) {
for (i = 0; i < info->needed_count; ++i) {
if (!strcmp(info->needed[i], "libc.so.6")) glibc = 1;
if ((!strcmp(info->needed[i], "libc.musl-x86_64.so.1") &&
info->machine == EM_X86_64 && info->elf_class == ELFCLASS64) ||
(!strcmp(info->needed[i], "libc.musl-i386.so.1") &&
info->machine == EM_386 && info->elf_class == ELFCLASS32)) musl = 1;
}
for (i = 0; i < info->version_count; ++i)
if (info->versions[i].provider && info->versions[i].name &&
!strcmp(info->versions[i].provider, "libc.so.6") &&
!strncmp(info->versions[i].name, "GLIBC_", 6)) glibc = 1;
if (glibc != musl) return glibc ? "glibc" : "musl";
}
if (info->type == ET_EXEC && !info->has_dynamic && !info->needed_count)
return "nolibc";
return "unknown";
}
base = strrchr(info->interpreter, '/');
base = base ? base + 1 : info->interpreter;
if (!strcmp(base, "ld-linux-x86-64.so.2") || !strcmp(base, "ld-linux.so.2"))
return "glibc";
if (!strcmp(base, "ld-musl-x86_64.so.1") || !strcmp(base, "ld-musl-i386.so.1"))
return "musl";
return "unknown";
}
const char *holy_elf_isa(const struct holy_elf_info *info)
{
uint32_t mask = info->isa_needed;
if (!info->isa_present || info->machine != EM_X86_64 ||
info->elf_class != ELFCLASS64 ||
(mask & ~(GNU_PROPERTY_X86_ISA_1_BASELINE | GNU_PROPERTY_X86_ISA_1_V2 |
GNU_PROPERTY_X86_ISA_1_V3 | GNU_PROPERTY_X86_ISA_1_V4)))
return "unknown";
if (mask & GNU_PROPERTY_X86_ISA_1_V4) return "x86-64-v4";
if (mask & GNU_PROPERTY_X86_ISA_1_V3) return "x86-64-v3";
if (mask & GNU_PROPERTY_X86_ISA_1_V2) return "x86-64-v2";
if (mask & GNU_PROPERTY_X86_ISA_1_BASELINE) return "x86-64-baseline";
return "unknown";
}
int holy_elf_exports_symbol(const struct holy_elf_info *provider,
const struct holy_elf_symbol *wanted)
{
size_t i;
for (i = 0; i < provider->symbol_count; ++i) {
const struct holy_elf_symbol *s = &provider->symbols[i];
if (!s->section ||
(s->binding != STB_GLOBAL && s->binding != STB_WEAK && s->binding != 10) ||
(s->visibility != STV_DEFAULT && s->visibility != STV_PROTECTED) ||
strcmp(s->name, wanted->name)) continue;
if ((wanted->type == STT_TLS) != (s->type == STT_TLS)) continue;
if (wanted->type == STT_FUNC && s->type != STT_FUNC && s->type != 10) continue;
if (wanted->type == STT_OBJECT && s->type != STT_OBJECT) continue;
if (wanted->version) {
if (!s->version || strcmp(s->version, wanted->version)) continue;
} else if (s->version_hidden) continue;
return 1;
}
return 0;
}