#define _POSIX_C_SOURCE 200809L #include "elf.h" #include #include #include #include #include #include #include #include #include #include #include /* 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; }