blob: edb466dd608451ed3b1aed2450d605171faf75fe [file] [log] [blame]
/* Pedantic checking of ELF files compliance with gABI/psABI spec.
Copyright (C) 2001-2015, 2017, 2018 Red Hat, Inc.
This file is part of elfutils.
Written by Ulrich Drepper <drepper@redhat.com>, 2001.
This file 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.
elfutils 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 <http://www.gnu.org/licenses/>. */
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <argp.h>
#include <assert.h>
#include <byteswap.h>
#include <endian.h>
#include <fcntl.h>
#include <gelf.h>
#include <inttypes.h>
#include <libintl.h>
#include <locale.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <elf-knowledge.h>
#include <libeu.h>
#include <system.h>
#include <printversion.h>
#include "../libelf/libelfP.h"
#include "../libelf/common.h"
#include "../libebl/libeblP.h"
#include "../libdw/libdwP.h"
#include "../libdwfl/libdwflP.h"
#include "../libdw/memory-access.h"
/* Name and version of program. */
ARGP_PROGRAM_VERSION_HOOK_DEF = print_version;
/* Bug report address. */
ARGP_PROGRAM_BUG_ADDRESS_DEF = PACKAGE_BUGREPORT;
#define ARGP_strict 300
#define ARGP_gnuld 301
/* Definitions of arguments for argp functions. */
static const struct argp_option options[] =
{
{ "strict", ARGP_strict, NULL, 0,
N_("Be extremely strict, flag level 2 features."), 0 },
{ "quiet", 'q', NULL, 0, N_("Do not print anything if successful"), 0 },
{ "debuginfo", 'd', NULL, 0, N_("Binary is a separate debuginfo file"), 0 },
{ "gnu-ld", ARGP_gnuld, NULL, 0,
N_("Binary has been created with GNU ld and is therefore known to be \
broken in certain ways"), 0 },
{ NULL, 0, NULL, 0, NULL, 0 }
};
/* Short description of program. */
static const char doc[] = N_("\
Pedantic checking of ELF files compliance with gABI/psABI spec.");
/* Strings for arguments in help texts. */
static const char args_doc[] = N_("FILE...");
/* Prototype for option handler. */
static error_t parse_opt (int key, char *arg, struct argp_state *state);
/* Data structure to communicate with argp functions. */
static struct argp argp =
{
options, parse_opt, args_doc, doc, NULL, NULL, NULL
};
/* Declarations of local functions. */
static void process_file (int fd, Elf *elf, const char *prefix,
const char *suffix, const char *fname, size_t size,
bool only_one);
static void process_elf_file (Elf *elf, const char *prefix, const char *suffix,
const char *fname, size_t size, bool only_one);
static void check_note_section (Ebl *ebl, GElf_Ehdr *ehdr,
GElf_Shdr *shdr, int idx);
/* Report an error. */
#define ERROR(str, args...) \
do { \
printf (str, ##args); \
++error_count; \
} while (0)
static unsigned int error_count;
/* True if we should perform very strict testing. */
static bool be_strict;
/* True if no message is to be printed if the run is succesful. */
static bool be_quiet;
/* True if binary is from strip -f, not a normal ELF file. */
static bool is_debuginfo;
/* True if binary is assumed to be generated with GNU ld. */
static bool gnuld;
/* Index of section header string table. */
static uint32_t shstrndx;
/* Array to count references in section groups. */
static int *scnref;
/* Numbers of sections and program headers. */
static unsigned int shnum;
static unsigned int phnum;
int
main (int argc, char *argv[])
{
/* Set locale. */
setlocale (LC_ALL, "");
/* Initialize the message catalog. */
textdomain (PACKAGE_TARNAME);
/* Parse and process arguments. */
int remaining;
argp_parse (&argp, argc, argv, 0, &remaining, NULL);
/* Before we start tell the ELF library which version we are using. */
elf_version (EV_CURRENT);
/* Now process all the files given at the command line. */
bool only_one = remaining + 1 == argc;
do
{
/* Open the file. */
int fd = open (argv[remaining], O_RDONLY);
if (fd == -1)
{
error (0, errno, gettext ("cannot open input file"));
continue;
}
/* Create an `Elf' descriptor. */
Elf *elf = elf_begin (fd, ELF_C_READ_MMAP, NULL);
if (elf == NULL)
ERROR (gettext ("cannot generate Elf descriptor: %s\n"),
elf_errmsg (-1));
else
{
unsigned int prev_error_count = error_count;
struct stat st;
if (fstat (fd, &st) != 0)
{
printf ("cannot stat '%s': %m\n", argv[remaining]);
close (fd);
continue;
}
process_file (fd, elf, NULL, NULL, argv[remaining], st.st_size,
only_one);
/* Now we can close the descriptor. */
if (elf_end (elf) != 0)
ERROR (gettext ("error while closing Elf descriptor: %s\n"),
elf_errmsg (-1));
if (prev_error_count == error_count && !be_quiet)
puts (gettext ("No errors"));
}
close (fd);
}
while (++remaining < argc);
return error_count != 0;
}
/* Handle program arguments. */
static error_t
parse_opt (int key, char *arg __attribute__ ((unused)),
struct argp_state *state __attribute__ ((unused)))
{
switch (key)
{
case ARGP_strict:
be_strict = true;
break;
case 'q':
be_quiet = true;
break;
case 'd':
is_debuginfo = true;
break;
case ARGP_gnuld:
gnuld = true;
break;
case ARGP_KEY_NO_ARGS:
fputs (gettext ("Missing file name.\n"), stderr);
argp_help (&argp, stderr, ARGP_HELP_SEE, program_invocation_short_name);
exit (EXIT_FAILURE);
default:
return ARGP_ERR_UNKNOWN;
}
return 0;
}
/* Process one file. */
static void
process_file (int fd, Elf *elf, const char *prefix, const char *suffix,
const char *fname, size_t size, bool only_one)
{
/* We can handle two types of files: ELF files and archives. */
Elf_Kind kind = elf_kind (elf);
switch (kind)
{
case ELF_K_ELF:
/* Yes! It's an ELF file. */
process_elf_file (elf, prefix, suffix, fname, size, only_one);
break;
case ELF_K_AR:
{
Elf *subelf;
Elf_Cmd cmd = ELF_C_READ_MMAP;
size_t prefix_len = prefix == NULL ? 0 : strlen (prefix);
size_t fname_len = strlen (fname) + 1;
char new_prefix[prefix_len + 1 + fname_len];
char new_suffix[(suffix == NULL ? 0 : strlen (suffix)) + 2];
char *cp = new_prefix;
/* Create the full name of the file. */
if (prefix != NULL)
{
cp = mempcpy (cp, prefix, prefix_len);
*cp++ = '(';
strcpy (stpcpy (new_suffix, suffix), ")");
}
else
new_suffix[0] = '\0';
memcpy (cp, fname, fname_len);
/* It's an archive. We process each file in it. */
while ((subelf = elf_begin (fd, cmd, elf)) != NULL)
{
kind = elf_kind (subelf);
/* Call this function recursively. */
if (kind == ELF_K_ELF || kind == ELF_K_AR)
{
Elf_Arhdr *arhdr = elf_getarhdr (subelf);
assert (arhdr != NULL);
process_file (fd, subelf, new_prefix, new_suffix,
arhdr->ar_name, arhdr->ar_size, false);
}
/* Get next archive element. */
cmd = elf_next (subelf);
if (elf_end (subelf) != 0)
ERROR (gettext (" error while freeing sub-ELF descriptor: %s\n"),
elf_errmsg (-1));
}
}
break;
default:
/* We cannot do anything. */
ERROR (gettext ("\
Not an ELF file - it has the wrong magic bytes at the start\n"));
break;
}
}
static const char *
section_name (Ebl *ebl, int idx)
{
GElf_Shdr shdr_mem;
GElf_Shdr *shdr;
const char *ret;
if ((unsigned int) idx > shnum)
return "<invalid>";
shdr = gelf_getshdr (elf_getscn (ebl->elf, idx), &shdr_mem);
if (shdr == NULL)
return "<invalid>";
ret = elf_strptr (ebl->elf, shstrndx, shdr->sh_name);
if (ret == NULL)
return "<invalid>";
return ret;
}
static const int valid_e_machine[] =
{
EM_M32, EM_SPARC, EM_386, EM_68K, EM_88K, EM_860, EM_MIPS, EM_S370,
EM_MIPS_RS3_LE, EM_PARISC, EM_VPP500, EM_SPARC32PLUS, EM_960, EM_PPC,
EM_PPC64, EM_S390, EM_V800, EM_FR20, EM_RH32, EM_RCE, EM_ARM,
EM_FAKE_ALPHA, EM_SH, EM_SPARCV9, EM_TRICORE, EM_ARC, EM_H8_300,
EM_H8_300H, EM_H8S, EM_H8_500, EM_IA_64, EM_MIPS_X, EM_COLDFIRE,
EM_68HC12, EM_MMA, EM_PCP, EM_NCPU, EM_NDR1, EM_STARCORE, EM_ME16,
EM_ST100, EM_TINYJ, EM_X86_64, EM_PDSP, EM_FX66, EM_ST9PLUS, EM_ST7,
EM_68HC16, EM_68HC11, EM_68HC08, EM_68HC05, EM_SVX, EM_ST19, EM_VAX,
EM_CRIS, EM_JAVELIN, EM_FIREPATH, EM_ZSP, EM_MMIX, EM_HUANY, EM_PRISM,
EM_AVR, EM_FR30, EM_D10V, EM_D30V, EM_V850, EM_M32R, EM_MN10300,
EM_MN10200, EM_PJ, EM_OPENRISC, EM_ARC_A5, EM_XTENSA, EM_ALPHA,
EM_TILEGX, EM_TILEPRO, EM_AARCH64, EM_BPF, EM_RISCV, EM_CSKY
};
#define nvalid_e_machine \
(sizeof (valid_e_machine) / sizeof (valid_e_machine[0]))
static void
check_elf_header (Ebl *ebl, GElf_Ehdr *ehdr, size_t size)
{
char buf[512];
size_t cnt;
/* Check e_ident field. */
if (ehdr->e_ident[EI_MAG0] != ELFMAG0)
ERROR ("e_ident[%d] != '%c'\n", EI_MAG0, ELFMAG0);
if (ehdr->e_ident[EI_MAG1] != ELFMAG1)
ERROR ("e_ident[%d] != '%c'\n", EI_MAG1, ELFMAG1);
if (ehdr->e_ident[EI_MAG2] != ELFMAG2)
ERROR ("e_ident[%d] != '%c'\n", EI_MAG2, ELFMAG2);
if (ehdr->e_ident[EI_MAG3] != ELFMAG3)
ERROR ("e_ident[%d] != '%c'\n", EI_MAG3, ELFMAG3);
if (ehdr->e_ident[EI_CLASS] != ELFCLASS32
&& ehdr->e_ident[EI_CLASS] != ELFCLASS64)
ERROR (gettext ("e_ident[%d] == %d is no known class\n"),
EI_CLASS, ehdr->e_ident[EI_CLASS]);
if (ehdr->e_ident[EI_DATA] != ELFDATA2LSB
&& ehdr->e_ident[EI_DATA] != ELFDATA2MSB)
ERROR (gettext ("e_ident[%d] == %d is no known data encoding\n"),
EI_DATA, ehdr->e_ident[EI_DATA]);
if (ehdr->e_ident[EI_VERSION] != EV_CURRENT)
ERROR (gettext ("unknown ELF header version number e_ident[%d] == %d\n"),
EI_VERSION, ehdr->e_ident[EI_VERSION]);
/* We currently don't handle any OS ABIs other than Linux and the
kFreeBSD variant of Debian. */
if (ehdr->e_ident[EI_OSABI] != ELFOSABI_NONE
&& ehdr->e_ident[EI_OSABI] != ELFOSABI_LINUX
&& ehdr->e_ident[EI_OSABI] != ELFOSABI_FREEBSD)
ERROR (gettext ("unsupported OS ABI e_ident[%d] == '%s'\n"),
EI_OSABI,
ebl_osabi_name (ebl, ehdr->e_ident[EI_OSABI], buf, sizeof (buf)));
/* No ABI versions other than zero are supported either. */
if (ehdr->e_ident[EI_ABIVERSION] != 0)
ERROR (gettext ("unsupported ABI version e_ident[%d] == %d\n"),
EI_ABIVERSION, ehdr->e_ident[EI_ABIVERSION]);
for (cnt = EI_PAD; cnt < EI_NIDENT; ++cnt)
if (ehdr->e_ident[cnt] != 0)
ERROR (gettext ("e_ident[%zu] is not zero\n"), cnt);
/* Check the e_type field. */
if (ehdr->e_type != ET_REL && ehdr->e_type != ET_EXEC
&& ehdr->e_type != ET_DYN && ehdr->e_type != ET_CORE)
ERROR (gettext ("unknown object file type %d\n"), ehdr->e_type);
/* Check the e_machine field. */
for (cnt = 0; cnt < nvalid_e_machine; ++cnt)
if (valid_e_machine[cnt] == ehdr->e_machine)
break;
if (cnt == nvalid_e_machine)
ERROR (gettext ("unknown machine type %d\n"), ehdr->e_machine);
/* Check the e_version field. */
if (ehdr->e_version != EV_CURRENT)
ERROR (gettext ("unknown object file version\n"));
/* Check the e_phoff and e_phnum fields. */
if (ehdr->e_phoff == 0)
{
if (ehdr->e_phnum != 0)
ERROR (gettext ("invalid program header offset\n"));
else if (ehdr->e_type == ET_EXEC || ehdr->e_type == ET_DYN)
ERROR (gettext ("\
executables and DSOs cannot have zero program header offset\n"));
}
else if (ehdr->e_phnum == 0)
ERROR (gettext ("invalid number of program header entries\n"));
/* Check the e_shoff field. */
shnum = ehdr->e_shnum;
shstrndx = ehdr->e_shstrndx;
if (ehdr->e_shoff == 0)
{
if (ehdr->e_shnum != 0)
ERROR (gettext ("invalid section header table offset\n"));
else if (ehdr->e_type != ET_EXEC && ehdr->e_type != ET_DYN
&& ehdr->e_type != ET_CORE)
ERROR (gettext ("section header table must be present\n"));
}
else
{
if (ehdr->e_shnum == 0)
{
/* Get the header of the zeroth section. The sh_size field
might contain the section number. */
GElf_Shdr shdr_mem;
GElf_Shdr *shdr = gelf_getshdr (elf_getscn (ebl->elf, 0), &shdr_mem);
if (shdr != NULL)
{
/* The error will be reported later. */
if (shdr->sh_size == 0)
ERROR (gettext ("\
invalid number of section header table entries\n"));
else
shnum = shdr->sh_size;
}
}
if (ehdr->e_shstrndx == SHN_XINDEX)
{
/* Get the header of the zeroth section. The sh_size field
might contain the section number. */
GElf_Shdr shdr_mem;
GElf_Shdr *shdr = gelf_getshdr (elf_getscn (ebl->elf, 0), &shdr_mem);
if (shdr != NULL && shdr->sh_link < shnum)
shstrndx = shdr->sh_link;
}
else if (shstrndx >= shnum)
ERROR (gettext ("invalid section header index\n"));
}
/* Check the shdrs actually exist. And uncompress them before
further checking. Indexes between sections reference the
uncompressed data. */
unsigned int scnt;
Elf_Scn *scn = NULL;
for (scnt = 1; scnt < shnum; ++scnt)
{
scn = elf_nextscn (ebl->elf, scn);
if (scn == NULL)
break;
/* If the section wasn't compressed this does nothing, but
returns an error. We don't care. */
elf_compress (scn, 0, 0);
}
if (scnt < shnum)
ERROR (gettext ("Can only check %u headers, shnum was %u\n"), scnt, shnum);
shnum = scnt;
phnum = ehdr->e_phnum;
if (ehdr->e_phnum == PN_XNUM)
{
/* Get the header of the zeroth section. The sh_info field
might contain the phnum count. */
GElf_Shdr shdr_mem;
GElf_Shdr *shdr = gelf_getshdr (elf_getscn (ebl->elf, 0), &shdr_mem);
if (shdr != NULL)
{
/* The error will be reported later. */
if (shdr->sh_info < PN_XNUM)
ERROR (gettext ("\
invalid number of program header table entries\n"));
else
phnum = shdr->sh_info;
}
}
/* Check the phdrs actually exist. */
unsigned int pcnt;
for (pcnt = 0; pcnt < phnum; ++pcnt)
{
GElf_Phdr phdr_mem;
GElf_Phdr *phdr = gelf_getphdr (ebl->elf, pcnt, &phdr_mem);
if (phdr == NULL)
break;
}
if (pcnt < phnum)
ERROR (gettext ("Can only check %u headers, phnum was %u\n"), pcnt, phnum);
phnum = pcnt;
/* Check the e_flags field. */
if (!ebl_machine_flag_check (ebl, ehdr->e_flags))
ERROR (gettext ("invalid machine flags: %s\n"),
ebl_machine_flag_name (ebl, ehdr->e_flags, buf, sizeof (buf)));
/* Check e_ehsize, e_phentsize, and e_shentsize fields. */
if (gelf_getclass (ebl->elf) == ELFCLASS32)
{
if (ehdr->e_ehsize != 0 && ehdr->e_ehsize != sizeof (Elf32_Ehdr))
ERROR (gettext ("invalid ELF header size: %hd\n"), ehdr->e_ehsize);
if (ehdr->e_phentsize != 0 && ehdr->e_phentsize != sizeof (Elf32_Phdr))
ERROR (gettext ("invalid program header size: %hd\n"),
ehdr->e_phentsize);
else if (ehdr->e_phoff + phnum * ehdr->e_phentsize > size)
ERROR (gettext ("invalid program header position or size\n"));
if (ehdr->e_shentsize != 0 && ehdr->e_shentsize != sizeof (Elf32_Shdr))
ERROR (gettext ("invalid section header size: %hd\n"),
ehdr->e_shentsize);
else if (ehdr->e_shoff + shnum * ehdr->e_shentsize > size)
ERROR (gettext ("invalid section header position or size\n"));
}
else if (gelf_getclass (ebl->elf) == ELFCLASS64)
{
if (ehdr->e_ehsize != 0 && ehdr->e_ehsize != sizeof (Elf64_Ehdr))
ERROR (gettext ("invalid ELF header size: %hd\n"), ehdr->e_ehsize);
if (ehdr->e_phentsize != 0 && ehdr->e_phentsize != sizeof (Elf64_Phdr))
ERROR (gettext ("invalid program header size: %hd\n"),
ehdr->e_phentsize);
else if (ehdr->e_phoff + phnum * ehdr->e_phentsize > size)
ERROR (gettext ("invalid program header position or size\n"));
if (ehdr->e_shentsize != 0 && ehdr->e_shentsize != sizeof (Elf64_Shdr))
ERROR (gettext ("invalid section header size: %hd\n"),
ehdr->e_shentsize);
else if (ehdr->e_shoff + shnum * ehdr->e_shentsize > size)
ERROR (gettext ("invalid section header position or size\n"));
}
}
/* Check that there is a section group section with index < IDX which
contains section IDX and that there is exactly one. */
static void
check_scn_group (Ebl *ebl, int idx)
{
if (scnref[idx] == 0)
{
/* No reference so far. Search following sections, maybe the
order is wrong. */
size_t cnt;
for (cnt = idx + 1; cnt < shnum; ++cnt)
{
Elf_Scn *scn = elf_getscn (ebl->elf, cnt);
GElf_Shdr shdr_mem;
GElf_Shdr *shdr = gelf_getshdr (scn, &shdr_mem);
if (shdr == NULL)
/* We cannot get the section header so we cannot check it.
The error to get the section header will be shown
somewhere else. */
continue;
if (shdr->sh_type != SHT_GROUP)
continue;
Elf_Data *data = elf_getdata (scn, NULL);
if (data == NULL || data->d_buf == NULL
|| data->d_size < sizeof (Elf32_Word))
/* Cannot check the section. */
continue;
Elf32_Word *grpdata = (Elf32_Word *) data->d_buf;
for (size_t inner = 1; inner < data->d_size / sizeof (Elf32_Word);
++inner)
if (grpdata[inner] == (Elf32_Word) idx)
goto out;
}
out:
if (cnt == shnum)
ERROR (gettext ("\
section [%2d] '%s': section with SHF_GROUP flag set not part of a section group\n"),
idx, section_name (ebl, idx));
else
ERROR (gettext ("\
section [%2d] '%s': section group [%2zu] '%s' does not precede group member\n"),
idx, section_name (ebl, idx),
cnt, section_name (ebl, cnt));
}
}
static void
check_symtab (Ebl *ebl, GElf_Ehdr *ehdr, GElf_Shdr *shdr, int idx)
{
bool no_xndx_warned = false;
int no_pt_tls = 0;
Elf_Data *data = elf_getdata (elf_getscn (ebl->elf, idx), NULL);
if (data == NULL)
{
ERROR (gettext ("section [%2d] '%s': cannot get section data\n"),
idx, section_name (ebl, idx));
return;
}
GElf_Shdr strshdr_mem;
GElf_Shdr *strshdr = gelf_getshdr (elf_getscn (ebl->elf, shdr->sh_link),
&strshdr_mem);
if (strshdr == NULL)
return;
if (strshdr->sh_type != SHT_STRTAB)
{
ERROR (gettext ("section [%2d] '%s': referenced as string table for section [%2d] '%s' but type is not SHT_STRTAB\n"),
shdr->sh_link, section_name (ebl, shdr->sh_link),
idx, section_name (ebl, idx));
strshdr = NULL;
}
/* Search for an extended section index table section. */
Elf_Data *xndxdata = NULL;
Elf32_Word xndxscnidx = 0;
bool found_xndx = false;
for (size_t cnt = 1; cnt < shnum; ++cnt)
if (cnt != (size_t) idx)
{
Elf_Scn *xndxscn = elf_getscn (ebl->elf, cnt);
GElf_Shdr xndxshdr_mem;
GElf_Shdr *xndxshdr = gelf_getshdr (xndxscn, &xndxshdr_mem);
if (xndxshdr == NULL)
continue;
if (xndxshdr->sh_type == SHT_SYMTAB_SHNDX
&& xndxshdr->sh_link == (GElf_Word) idx)
{
if (found_xndx)
ERROR (gettext ("\
section [%2d] '%s': symbol table cannot have more than one extended index section\n"),
idx, section_name (ebl, idx));
xndxdata = elf_getdata (xndxscn, NULL);
xndxscnidx = elf_ndxscn (xndxscn);
found_xndx = true;
}
}
size_t sh_entsize = gelf_fsize (ebl->elf, ELF_T_SYM, 1, EV_CURRENT);
if (shdr->sh_entsize != sh_entsize)
ERROR (gettext ("\
section [%2u] '%s': entry size is does not match ElfXX_Sym\n"),
idx, section_name (ebl, idx));
/* Test the zeroth entry. */
GElf_Sym sym_mem;
Elf32_Word xndx;
GElf_Sym *sym = gelf_getsymshndx (data, xndxdata, 0, &sym_mem, &xndx);
if (sym == NULL)
ERROR (gettext ("section [%2d] '%s': cannot get symbol %d: %s\n"),
idx, section_name (ebl, idx), 0, elf_errmsg (-1));
else
{
if (sym->st_name != 0)
ERROR (gettext ("section [%2d] '%s': '%s' in zeroth entry not zero\n"),
idx, section_name (ebl, idx), "st_name");
if (sym->st_value != 0)
ERROR (gettext ("section [%2d] '%s': '%s' in zeroth entry not zero\n"),
idx, section_name (ebl, idx), "st_value");
if (sym->st_size != 0)
ERROR (gettext ("section [%2d] '%s': '%s' in zeroth entry not zero\n"),
idx, section_name (ebl, idx), "st_size");
if (sym->st_info != 0)
ERROR (gettext ("section [%2d] '%s': '%s' in zeroth entry not zero\n"),
idx, section_name (ebl, idx), "st_info");
if (sym->st_other != 0)
ERROR (gettext ("section [%2d] '%s': '%s' in zeroth entry not zero\n"),
idx, section_name (ebl, idx), "st_other");
if (sym->st_shndx != 0)
ERROR (gettext ("section [%2d] '%s': '%s' in zeroth entry not zero\n"),
idx, section_name (ebl, idx), "st_shndx");
if (xndxdata != NULL && xndx != 0)
ERROR (gettext ("\
section [%2d] '%s': XINDEX for zeroth entry not zero\n"),
xndxscnidx, section_name (ebl, xndxscnidx));
}
for (size_t cnt = 1; cnt < shdr->sh_size / sh_entsize; ++cnt)
{
sym = gelf_getsymshndx (data, xndxdata, cnt, &sym_mem, &xndx);
if (sym == NULL)
{
ERROR (gettext ("section [%2d] '%s': cannot get symbol %zu: %s\n"),
idx, section_name (ebl, idx), cnt, elf_errmsg (-1));
continue;
}
const char *name = NULL;
if (strshdr == NULL)
name = "";
else if (sym->st_name >= strshdr->sh_size)
ERROR (gettext ("\
section [%2d] '%s': symbol %zu: invalid name value\n"),
idx, section_name (ebl, idx), cnt);
else
{
name = elf_strptr (ebl->elf, shdr->sh_link, sym->st_name);
if (name == NULL)
name = "";
}
if (sym->st_shndx == SHN_XINDEX)
{
if (xndxdata == NULL)
{
if (!no_xndx_warned)
ERROR (gettext ("\
section [%2d] '%s': symbol %zu: too large section index but no extended section index section\n"),
idx, section_name (ebl, idx), cnt);
no_xndx_warned = true;
}
else if (xndx < SHN_LORESERVE)
ERROR (gettext ("\
section [%2d] '%s': symbol %zu: XINDEX used for index which would fit in st_shndx (%" PRIu32 ")\n"),
xndxscnidx, section_name (ebl, xndxscnidx), cnt,
xndx);
}
else if ((sym->st_shndx >= SHN_LORESERVE
// && sym->st_shndx <= SHN_HIRESERVE always true
&& sym->st_shndx != SHN_ABS
&& sym->st_shndx != SHN_COMMON)
|| (sym->st_shndx >= shnum
&& (sym->st_shndx < SHN_LORESERVE
/* || sym->st_shndx > SHN_HIRESERVE always false */)))
ERROR (gettext ("\
section [%2d] '%s': symbol %zu: invalid section index\n"),
idx, section_name (ebl, idx), cnt);
else
xndx = sym->st_shndx;
if (GELF_ST_TYPE (sym->st_info) >= STT_NUM
&& !ebl_symbol_type_name (ebl, GELF_ST_TYPE (sym->st_info), NULL, 0))
ERROR (gettext ("section [%2d] '%s': symbol %zu: unknown type\n"),
idx, section_name (ebl, idx), cnt);
if (GELF_ST_BIND (sym->st_info) >= STB_NUM
&& !ebl_symbol_binding_name (ebl, GELF_ST_BIND (sym->st_info), NULL,
0))
ERROR (gettext ("\
section [%2d] '%s': symbol %zu: unknown symbol binding\n"),
idx, section_name (ebl, idx), cnt);
if (GELF_ST_BIND (sym->st_info) == STB_GNU_UNIQUE
&& GELF_ST_TYPE (sym->st_info) != STT_OBJECT)
ERROR (gettext ("\
section [%2d] '%s': symbol %zu: unique symbol not of object type\n"),
idx, section_name (ebl, idx), cnt);
if (xndx == SHN_COMMON)
{
/* Common symbols can only appear in relocatable files. */
if (ehdr->e_type != ET_REL)
ERROR (gettext ("\
section [%2d] '%s': symbol %zu: COMMON only allowed in relocatable files\n"),
idx, section_name (ebl, idx), cnt);
if (cnt < shdr->sh_info)
ERROR (gettext ("\
section [%2d] '%s': symbol %zu: local COMMON symbols are nonsense\n"),
idx, section_name (ebl, idx), cnt);
if (GELF_R_TYPE (sym->st_info) == STT_FUNC)
ERROR (gettext ("\
section [%2d] '%s': symbol %zu: function in COMMON section is nonsense\n"),
idx, section_name (ebl, idx), cnt);
}
else if (xndx > 0 && xndx < shnum)
{
GElf_Shdr destshdr_mem;
GElf_Shdr *destshdr;
destshdr = gelf_getshdr (elf_getscn (ebl->elf, xndx), &destshdr_mem);
if (destshdr != NULL)
{
GElf_Addr sh_addr = (ehdr->e_type == ET_REL ? 0
: destshdr->sh_addr);
GElf_Addr st_value;
if (GELF_ST_TYPE (sym->st_info) == STT_FUNC
|| (GELF_ST_TYPE (sym->st_info) == STT_GNU_IFUNC))
st_value = sym->st_value & ebl_func_addr_mask (ebl);
else
st_value = sym->st_value;
if (GELF_ST_TYPE (sym->st_info) != STT_TLS)
{
if (! ebl_check_special_symbol (ebl, sym, name,
destshdr))
{
if (st_value - sh_addr > destshdr->sh_size)
{
/* GNU ld has severe bugs. When it decides to remove
empty sections it leaves symbols referencing them
behind. These are symbols in .symtab or .dynsym
and for the named symbols have zero size. See
sourceware PR13621. */
if (!gnuld
|| (strcmp (section_name (ebl, idx), ".symtab")
&& strcmp (section_name (ebl, idx),
".dynsym"))
|| sym->st_size != 0
|| (strcmp (name, "__preinit_array_start") != 0
&& strcmp (name, "__preinit_array_end") != 0
&& strcmp (name, "__init_array_start") != 0
&& strcmp (name, "__init_array_end") != 0
&& strcmp (name, "__fini_array_start") != 0
&& strcmp (name, "__fini_array_end") != 0
&& strcmp (name, "__bss_start") != 0
&& strcmp (name, "__bss_start__") != 0
&& strcmp (name, "__TMC_END__") != 0
&& strcmp (name, ".TOC.") != 0
&& strcmp (name, "_edata") != 0
&& strcmp (name, "__edata") != 0
&& strcmp (name, "_end") != 0
&& strcmp (name, "__end") != 0))
ERROR (gettext ("\
section [%2d] '%s': symbol %zu: st_value out of bounds\n"),
idx, section_name (ebl, idx), cnt);
}
else if ((st_value - sh_addr
+ sym->st_size) > destshdr->sh_size)
ERROR (gettext ("\
section [%2d] '%s': symbol %zu does not fit completely in referenced section [%2d] '%s'\n"),
idx, section_name (ebl, idx), cnt,
(int) xndx, section_name (ebl, xndx));
}
}
else
{
if ((destshdr->sh_flags & SHF_TLS) == 0)
ERROR (gettext ("\
section [%2d] '%s': symbol %zu: referenced section [%2d] '%s' does not have SHF_TLS flag set\n"),
idx, section_name (ebl, idx), cnt,
(int) xndx, section_name (ebl, xndx));
if (ehdr->e_type == ET_REL)
{
/* For object files the symbol value must fall
into the section. */
if (st_value > destshdr->sh_size)
ERROR (gettext ("\
section [%2d] '%s': symbol %zu: st_value out of bounds of referenced section [%2d] '%s'\n"),
idx, section_name (ebl, idx), cnt,
(int) xndx, section_name (ebl, xndx));
else if (st_value + sym->st_size
> destshdr->sh_size)
ERROR (gettext ("\
section [%2d] '%s': symbol %zu does not fit completely in referenced section [%2d] '%s'\n"),
idx, section_name (ebl, idx), cnt,
(int) xndx, section_name (ebl, xndx));
}
else
{
GElf_Phdr phdr_mem;
GElf_Phdr *phdr = NULL;
unsigned int pcnt;
for (pcnt = 0; pcnt < phnum; ++pcnt)
{
phdr = gelf_getphdr (ebl->elf, pcnt, &phdr_mem);
if (phdr != NULL && phdr->p_type == PT_TLS)
break;
}
if (pcnt == phnum)
{
if (no_pt_tls++ == 0)
ERROR (gettext ("\
section [%2d] '%s': symbol %zu: TLS symbol but no TLS program header entry\n"),
idx, section_name (ebl, idx), cnt);
}
else if (phdr == NULL)
{
ERROR (gettext ("\
section [%2d] '%s': symbol %zu: TLS symbol but couldn't get TLS program header entry\n"),
idx, section_name (ebl, idx), cnt);
}
else if (!is_debuginfo)
{
if (st_value
< destshdr->sh_offset - phdr->p_offset)
ERROR (gettext ("\
section [%2d] '%s': symbol %zu: st_value short of referenced section [%2d] '%s'\n"),
idx, section_name (ebl, idx), cnt,
(int) xndx, section_name (ebl, xndx));
else if (st_value
> (destshdr->sh_offset - phdr->p_offset
+ destshdr->sh_size))
ERROR (gettext ("\
section [%2d] '%s': symbol %zu: st_value out of bounds of referenced section [%2d] '%s'\n"),
idx, section_name (ebl, idx), cnt,
(int) xndx, section_name (ebl, xndx));
else if (st_value + sym->st_size
> (destshdr->sh_offset - phdr->p_offset
+ destshdr->sh_size))
ERROR (gettext ("\
section [%2d] '%s': symbol %zu does not fit completely in referenced section [%2d] '%s'\n"),
idx, section_name (ebl, idx), cnt,
(int) xndx, section_name (ebl, xndx));
}
}
}
}
}
if (GELF_ST_BIND (sym->st_info) == STB_LOCAL)
{
if (cnt >= shdr->sh_info)
ERROR (gettext ("\
section [%2d] '%s': symbol %zu: local symbol outside range described in sh_info\n"),
idx, section_name (ebl, idx), cnt);
}
else
{
if (cnt < shdr->sh_info)
ERROR (gettext ("\
section [%2d] '%s': symbol %zu: non-local symbol outside range described in sh_info\n"),
idx, section_name (ebl, idx), cnt);
}
if (GELF_ST_TYPE (sym->st_info) == STT_SECTION
&& GELF_ST_BIND (sym->st_info) != STB_LOCAL)
ERROR (gettext ("\
section [%2d] '%s': symbol %zu: non-local section symbol\n"),
idx, section_name (ebl, idx), cnt);
if (name != NULL)
{
if (strcmp (name, "_GLOBAL_OFFSET_TABLE_") == 0)
{
/* Check that address and size match the global offset table. */
GElf_Shdr destshdr_mem;
GElf_Shdr *destshdr = gelf_getshdr (elf_getscn (ebl->elf, xndx),
&destshdr_mem);
if (destshdr == NULL && xndx == SHN_ABS)
{
/* In a DSO, we have to find the GOT section by name. */
Elf_Scn *gotscn = NULL;
Elf_Scn *gscn = NULL;
while ((gscn = elf_nextscn (ebl->elf, gscn)) != NULL)
{
destshdr = gelf_getshdr (gscn, &destshdr_mem);
assert (destshdr != NULL);
const char *sname = elf_strptr (ebl->elf,
shstrndx,
destshdr->sh_name);
if (sname != NULL)
{
if (strcmp (sname, ".got.plt") == 0)
break;
if (strcmp (sname, ".got") == 0)
/* Do not stop looking.
There might be a .got.plt section. */
gotscn = gscn;
}
destshdr = NULL;
}
if (destshdr == NULL && gotscn != NULL)
destshdr = gelf_getshdr (gotscn, &destshdr_mem);
}
const char *sname = ((destshdr == NULL || xndx == SHN_UNDEF)
? NULL
: elf_strptr (ebl->elf, shstrndx,
destshdr->sh_name));
if (sname == NULL)
{
if (xndx != SHN_UNDEF || ehdr->e_type != ET_REL)
ERROR (gettext ("\
section [%2d] '%s': _GLOBAL_OFFSET_TABLE_ symbol refers to \
bad section [%2d]\n"),
idx, section_name (ebl, idx), xndx);
}
else if (strcmp (sname, ".got.plt") != 0
&& strcmp (sname, ".got") != 0)
ERROR (gettext ("\
section [%2d] '%s': _GLOBAL_OFFSET_TABLE_ symbol refers to \
section [%2d] '%s'\n"),
idx, section_name (ebl, idx), xndx, sname);
if (destshdr != NULL)
{
/* Found it. */
if (!ebl_check_special_symbol (ebl, sym, name,
destshdr))
{
if (ehdr->e_type != ET_REL
&& sym->st_value != destshdr->sh_addr)
/* This test is more strict than the psABIs which
usually allow the symbol to be in the middle of
the .got section, allowing negative offsets. */
ERROR (gettext ("\
section [%2d] '%s': _GLOBAL_OFFSET_TABLE_ symbol value %#" PRIx64 " does not match %s section address %#" PRIx64 "\n"),
idx, section_name (ebl, idx),
(uint64_t) sym->st_value,
sname, (uint64_t) destshdr->sh_addr);
if (!gnuld && sym->st_size != destshdr->sh_size)
ERROR (gettext ("\
section [%2d] '%s': _GLOBAL_OFFSET_TABLE_ symbol size %" PRIu64 " does not match %s section size %" PRIu64 "\n"),
idx, section_name (ebl, idx),
(uint64_t) sym->st_size,
sname, (uint64_t) destshdr->sh_size);
}
}
else
ERROR (gettext ("\
section [%2d] '%s': _GLOBAL_OFFSET_TABLE_ symbol present, but no .got section\n"),
idx, section_name (ebl, idx));
}
else if (strcmp (name, "_DYNAMIC") == 0)
/* Check that address and size match the dynamic section.
We locate the dynamic section via the program header
entry. */
for (unsigned int pcnt = 0; pcnt < phnum; ++pcnt)
{
GElf_Phdr phdr_mem;
GElf_Phdr *phdr = gelf_getphdr (ebl->elf, pcnt, &phdr_mem);
if (phdr != NULL && phdr->p_type == PT_DYNAMIC)
{
if (sym->st_value != phdr->p_vaddr)
ERROR (gettext ("\
section [%2d] '%s': _DYNAMIC_ symbol value %#" PRIx64 " does not match dynamic segment address %#" PRIx64 "\n"),
idx, section_name (ebl, idx),
(uint64_t) sym->st_value,
(uint64_t) phdr->p_vaddr);
if (!gnuld && sym->st_size != phdr->p_memsz)
ERROR (gettext ("\
section [%2d] '%s': _DYNAMIC symbol size %" PRIu64 " does not match dynamic segment size %" PRIu64 "\n"),
idx, section_name (ebl, idx),
(uint64_t) sym->st_size,
(uint64_t) phdr->p_memsz);
break;
}
}
}
if (GELF_ST_VISIBILITY (sym->st_other) != STV_DEFAULT
&& shdr->sh_type == SHT_DYNSYM)
ERROR (gettext ("\
section [%2d] '%s': symbol %zu: symbol in dynamic symbol table with non-default visibility\n"),
idx, section_name (ebl, idx), cnt);
if (! ebl_check_st_other_bits (ebl, sym->st_other))
ERROR (gettext ("\
section [%2d] '%s': symbol %zu: unknown bit set in st_other\n"),
idx, section_name (ebl, idx), cnt);
}
}
static bool
is_rel_dyn (Ebl *ebl, const GElf_Ehdr *ehdr, int idx, const GElf_Shdr *shdr,
bool is_rela)
{
/* If this is no executable or DSO it cannot be a .rel.dyn section. */
if (ehdr->e_type != ET_EXEC && ehdr->e_type != ET_DYN)
return false;
/* Check the section name. Unfortunately necessary. */
if (strcmp (section_name (ebl, idx), is_rela ? ".rela.dyn" : ".rel.dyn"))
return false;
/* When a .rel.dyn section is used a DT_RELCOUNT dynamic section
entry can be present as well. */
Elf_Scn *scn = NULL;
while ((scn = elf_nextscn (ebl->elf, scn)) != NULL)
{
GElf_Shdr rcshdr_mem;
const GElf_Shdr *rcshdr = gelf_getshdr (scn, &rcshdr_mem);
if (rcshdr == NULL)
break;
if (rcshdr->sh_type == SHT_DYNAMIC && rcshdr->sh_entsize != 0)
{
/* Found the dynamic section. Look through it. */
Elf_Data *d = elf_getdata (scn, NULL);
size_t cnt;
if (d == NULL)
ERROR (gettext ("\
section [%2d] '%s': cannot get section data.\n"),
idx, section_name (ebl, idx));
for (cnt = 1; cnt < rcshdr->sh_size / rcshdr->sh_entsize; ++cnt)
{
GElf_Dyn dyn_mem;
GElf_Dyn *dyn = gelf_getdyn (d, cnt, &dyn_mem);
if (dyn == NULL)
break;
if (dyn->d_tag == DT_RELCOUNT)
{
/* Found it. Does the type match. */
if (is_rela)
ERROR (gettext ("\
section [%2d] '%s': DT_RELCOUNT used for this RELA section\n"),
idx, section_name (ebl, idx));
else
{
/* Does the number specified number of relative
relocations exceed the total number of
relocations? */
if (shdr->sh_entsize != 0
&& dyn->d_un.d_val > (shdr->sh_size
/ shdr->sh_entsize))
ERROR (gettext ("\
section [%2d] '%s': DT_RELCOUNT value %d too high for this section\n"),
idx, section_name (ebl, idx),
(int) dyn->d_un.d_val);
/* Make sure the specified number of relocations are
relative. */
Elf_Data *reldata = elf_getdata (elf_getscn (ebl->elf,
idx), NULL);
if (reldata != NULL && shdr->sh_entsize != 0)
for (size_t inner = 0;
inner < shdr->sh_size / shdr->sh_entsize;
++inner)
{
GElf_Rel rel_mem;
GElf_Rel *rel = gelf_getrel (reldata, inner,
&rel_mem);
if (rel == NULL)
/* The problem will be reported elsewhere. */
break;
if (ebl_relative_reloc_p (ebl,
GELF_R_TYPE (rel->r_info)))
{
if (inner >= dyn->d_un.d_val)
ERROR (gettext ("\
section [%2d] '%s': relative relocations after index %d as specified by DT_RELCOUNT\n"),
idx, section_name (ebl, idx),
(int) dyn->d_un.d_val);
}
else if (inner < dyn->d_un.d_val)
ERROR (gettext ("\
section [%2d] '%s': non-relative relocation at index %zu; DT_RELCOUNT specified %d relative relocations\n"),
idx, section_name (ebl, idx),
inner, (int) dyn->d_un.d_val);
}
}
}
if (dyn->d_tag == DT_RELACOUNT)
{
/* Found it. Does the type match. */
if (!is_rela)
ERROR (gettext ("\
section [%2d] '%s': DT_RELACOUNT used for this REL section\n"),
idx, section_name (ebl, idx));
else
{
/* Does the number specified number of relative
relocations exceed the total number of
relocations? */
if (shdr->sh_entsize != 0
&& dyn->d_un.d_val > shdr->sh_size / shdr->sh_entsize)
ERROR (gettext ("\
section [%2d] '%s': DT_RELCOUNT value %d too high for this section\n"),
idx, section_name (ebl, idx),
(int) dyn->d_un.d_val);
/* Make sure the specified number of relocations are
relative. */
Elf_Data *reldata = elf_getdata (elf_getscn (ebl->elf,
idx), NULL);
if (reldata != NULL && shdr->sh_entsize != 0)
for (size_t inner = 0;
inner < shdr->sh_size / shdr->sh_entsize;
++inner)
{
GElf_Rela rela_mem;
GElf_Rela *rela = gelf_getrela (reldata, inner,
&rela_mem);
if (rela == NULL)
/* The problem will be reported elsewhere. */
break;
if (ebl_relative_reloc_p (ebl,
GELF_R_TYPE (rela->r_info)))
{
if (inner >= dyn->d_un.d_val)
ERROR (gettext ("\
section [%2d] '%s': relative relocations after index %d as specified by DT_RELCOUNT\n"),
idx, section_name (ebl, idx),
(int) dyn->d_un.d_val);
}
else if (inner < dyn->d_un.d_val)
ERROR (gettext ("\
section [%2d] '%s': non-relative relocation at index %zu; DT_RELCOUNT specified %d relative relocations\n"),
idx, section_name (ebl, idx),
inner, (int) dyn->d_un.d_val);
}
}
}
}
break;
}
}
return true;
}
struct loaded_segment
{
GElf_Addr from;
GElf_Addr to;
bool read_only;
struct loaded_segment *next;
};
/* Check whether binary has text relocation flag set. */
static bool textrel;
/* Keep track of whether text relocation flag is needed. */
static bool needed_textrel;
static bool
check_reloc_shdr (Ebl *ebl, const GElf_Ehdr *ehdr, const GElf_Shdr *shdr,
int idx, int reltype, GElf_Shdr **destshdrp,
GElf_Shdr *destshdr_memp, struct loaded_segment **loadedp)
{
bool reldyn = false;
/* Check whether the link to the section we relocate is reasonable. */
if (shdr->sh_info >= shnum)
ERROR (gettext ("section [%2d] '%s': invalid destination section index\n"),
idx, section_name (ebl, idx));
else if (shdr->sh_info != 0)
{
*destshdrp = gelf_getshdr (elf_getscn (ebl->elf, shdr->sh_info),
destshdr_memp);
if (*destshdrp != NULL)
{
if(! ebl_check_reloc_target_type (ebl, (*destshdrp)->sh_type))
{
reldyn = is_rel_dyn (ebl, ehdr, idx, shdr, true);
if (!reldyn)
ERROR (gettext ("\
section [%2d] '%s': invalid destination section type\n"),
idx, section_name (ebl, idx));
else
{
/* There is no standard, but we require that .rel{,a}.dyn
sections have a sh_info value of zero. */
if (shdr->sh_info != 0)
ERROR (gettext ("\
section [%2d] '%s': sh_info should be zero\n"),
idx, section_name (ebl, idx));
}
}
if ((((*destshdrp)->sh_flags & SHF_MERGE) != 0)
&& ((*destshdrp)->sh_flags & SHF_STRINGS) != 0)
ERROR (gettext ("\
section [%2d] '%s': no relocations for merge-able string sections possible\n"),
idx, section_name (ebl, idx));
}
}
size_t sh_entsize = gelf_fsize (ebl->elf, reltype, 1, EV_CURRENT);
if (shdr->sh_entsize != sh_entsize)
ERROR (gettext (reltype == ELF_T_RELA ? "\
section [%2d] '%s': section entry size does not match ElfXX_Rela\n" : "\
section [%2d] '%s': section entry size does not match ElfXX_Rel\n"),
idx, section_name (ebl, idx));
/* In preparation of checking whether relocations are text
relocations or not we need to determine whether the file is
flagged to have text relocation and we need to determine a) what
the loaded segments are and b) which are read-only. This will
also allow us to determine whether the same reloc section is
modifying loaded and not loaded segments. */
for (unsigned int i = 0; i < phnum; ++i)
{
GElf_Phdr phdr_mem;
GElf_Phdr *phdr = gelf_getphdr (ebl->elf, i, &phdr_mem);
if (phdr == NULL)
continue;
if (phdr->p_type == PT_LOAD)
{
struct loaded_segment *newp = xmalloc (sizeof (*newp));
newp->from = phdr->p_vaddr;
newp->to = phdr->p_vaddr + phdr->p_memsz;
newp->read_only = (phdr->p_flags & PF_W) == 0;
newp->next = *loadedp;
*loadedp = newp;
}
else if (phdr->p_type == PT_DYNAMIC)
{
Elf_Scn *dynscn = gelf_offscn (ebl->elf, phdr->p_offset);
GElf_Shdr dynshdr_mem;
GElf_Shdr *dynshdr = gelf_getshdr (dynscn, &dynshdr_mem);
Elf_Data *dyndata = elf_getdata (dynscn, NULL);
if (dynshdr != NULL && dynshdr->sh_type == SHT_DYNAMIC
&& dyndata != NULL && dynshdr->sh_entsize != 0)
for (size_t j = 0; j < dynshdr->sh_size / dynshdr->sh_entsize; ++j)
{
GElf_Dyn dyn_mem;
GElf_Dyn *dyn = gelf_getdyn (dyndata, j, &dyn_mem);
if (dyn != NULL
&& (dyn->d_tag == DT_TEXTREL
|| (dyn->d_tag == DT_FLAGS
&& (dyn->d_un.d_val & DF_TEXTREL) != 0)))
{
textrel = true;
break;
}
}
}
}
/* A quick test which can be easily done here (although it is a bit
out of place): the text relocation flag makes only sense if there
is a segment which is not writable. */
if (textrel)
{
struct loaded_segment *seg = *loadedp;
while (seg != NULL && !seg->read_only)
seg = seg->next;
if (seg == NULL)
ERROR (gettext ("\
text relocation flag set but there is no read-only segment\n"));
}
return reldyn;
}
enum load_state
{
state_undecided,
state_loaded,
state_unloaded,
state_error
};
static void
check_one_reloc (Ebl *ebl, GElf_Ehdr *ehdr, GElf_Shdr *relshdr, int idx,
size_t cnt, const GElf_Shdr *symshdr, Elf_Data *symdata,
GElf_Addr r_offset, GElf_Xword r_info,
const GElf_Shdr *destshdr, bool reldyn,
struct loaded_segment *loaded, enum load_state *statep)
{
bool known_broken = gnuld;
if (!ebl_reloc_type_check (ebl, GELF_R_TYPE (r_info)))
ERROR (gettext ("section [%2d] '%s': relocation %zu: invalid type\n"),
idx, section_name (ebl, idx), cnt);
else if (((ehdr->e_type != ET_EXEC && ehdr->e_type != ET_DYN)
/* The executable/DSO can contain relocation sections with
all the relocations the linker has applied. Those sections
are marked non-loaded, though. */
|| (relshdr->sh_flags & SHF_ALLOC) != 0)
&& !ebl_reloc_valid_use (ebl, GELF_R_TYPE (r_info)))
ERROR (gettext ("\
section [%2d] '%s': relocation %zu: relocation type invalid for the file type\n"),
idx, section_name (ebl, idx), cnt);
if (symshdr != NULL
&& ((GELF_R_SYM (r_info) + 1)
* gelf_fsize (ebl->elf, ELF_T_SYM, 1, EV_CURRENT)
> symshdr->sh_size))
ERROR (gettext ("\
section [%2d] '%s': relocation %zu: invalid symbol index\n"),
idx, section_name (ebl, idx), cnt);
/* No more tests if this is a no-op relocation. */
if (ebl_none_reloc_p (ebl, GELF_R_TYPE (r_info)))
return;
if (ebl_gotpc_reloc_check (ebl, GELF_R_TYPE (r_info)))
{
const char *name;
char buf[64];
GElf_Sym sym_mem;
GElf_Sym *sym = gelf_getsym (symdata, GELF_R_SYM (r_info), &sym_mem);
if (sym != NULL
/* Get the name for the symbol. */
&& (name = elf_strptr (ebl->elf, symshdr->sh_link, sym->st_name))
&& strcmp (name, "_GLOBAL_OFFSET_TABLE_") !=0 )
ERROR (gettext ("\
section [%2d] '%s': relocation %zu: only symbol '_GLOBAL_OFFSET_TABLE_' can be used with %s\n"),
idx, section_name (ebl, idx), cnt,
ebl_reloc_type_name (ebl, GELF_R_SYM (r_info),
buf, sizeof (buf)));
}
if (reldyn)
{
// XXX TODO Check .rel.dyn section addresses.
}
else if (!known_broken)
{
if (destshdr != NULL
&& GELF_R_TYPE (r_info) != 0
&& (r_offset - (ehdr->e_type == ET_REL ? 0
: destshdr->sh_addr)) >= destshdr->sh_size)
ERROR (gettext ("\
section [%2d] '%s': relocation %zu: offset out of bounds\n"),
idx, section_name (ebl, idx), cnt);
}
GElf_Sym sym_mem;
GElf_Sym *sym = gelf_getsym (symdata, GELF_R_SYM (r_info), &sym_mem);
if (ebl_copy_reloc_p (ebl, GELF_R_TYPE (r_info))
/* Make sure the referenced symbol is an object or unspecified. */
&& sym != NULL
&& GELF_ST_TYPE (sym->st_info) != STT_NOTYPE
&& GELF_ST_TYPE (sym->st_info) != STT_OBJECT)
{
char buf[64];
ERROR (gettext ("section [%2d] '%s': relocation %zu: copy relocation against symbol of type %s\n"),
idx, section_name (ebl, idx), cnt,
ebl_symbol_type_name (ebl, GELF_ST_TYPE (sym->st_info),
buf, sizeof (buf)));
}
if ((ehdr->e_type != ET_EXEC && ehdr->e_type != ET_DYN)
|| (relshdr->sh_flags & SHF_ALLOC) != 0)
{
bool in_loaded_seg = false;
while (loaded != NULL)
{
if (r_offset < loaded->to
&& r_offset + (sym == NULL ? 0 : sym->st_size) >= loaded->from)
{
/* The symbol is in this segment. */
if (loaded->read_only)
{
if (textrel)
needed_textrel = true;
else
ERROR (gettext ("section [%2d] '%s': relocation %zu: read-only section modified but text relocation flag not set\n"),
idx, section_name (ebl, idx), cnt);
}
in_loaded_seg = true;
}
loaded = loaded->next;
}
if (*statep == state_undecided)
*statep = in_loaded_seg ? state_loaded : state_unloaded;
else if ((*statep == state_unloaded && in_loaded_seg)
|| (*statep == state_loaded && !in_loaded_seg))
{
ERROR (gettext ("\
section [%2d] '%s': relocations are against loaded and unloaded data\n"),
idx, section_name (ebl, idx));
*statep = state_error;
}
}
}
static void
check_rela (Ebl *ebl, GElf_Ehdr *ehdr, GElf_Shdr *shdr, int idx)
{
Elf_Data *data = elf_getdata (elf_getscn (ebl->elf, idx), NULL);
if (data == NULL)
{
ERROR (gettext ("section [%2d] '%s': cannot get section data\n"),
idx, section_name (ebl, idx));
return;
}
/* Check the fields of the section header. */
GElf_Shdr destshdr_mem;
GElf_Shdr *destshdr = NULL;
struct loaded_segment *loaded = NULL;
bool reldyn = check_reloc_shdr (ebl, ehdr, shdr, idx, ELF_T_RELA, &destshdr,
&destshdr_mem, &loaded);
Elf_Scn *symscn = elf_getscn (ebl->elf, shdr->sh_link);
GElf_Shdr symshdr_mem;
GElf_Shdr *symshdr = gelf_getshdr (symscn, &symshdr_mem);
Elf_Data *symdata = elf_getdata (symscn, NULL);
enum load_state state = state_undecided;
size_t sh_entsize = gelf_fsize (ebl->elf, ELF_T_RELA, 1, EV_CURRENT);
for (size_t cnt = 0; cnt < shdr->sh_size / sh_entsize; ++cnt)
{
GElf_Rela rela_mem;
GElf_Rela *rela = gelf_getrela (data, cnt, &rela_mem);
if (rela == NULL)
{
ERROR (gettext ("\
section [%2d] '%s': cannot get relocation %zu: %s\n"),
idx, section_name (ebl, idx), cnt, elf_errmsg (-1));
continue;
}
check_one_reloc (ebl, ehdr, shdr, idx, cnt, symshdr, symdata,
rela->r_offset, rela->r_info, destshdr, reldyn, loaded,
&state);
}
while (loaded != NULL)
{
struct loaded_segment *old = loaded;
loaded = loaded->next;
free (old);
}
}
static void
check_rel (Ebl *ebl, GElf_Ehdr *ehdr, GElf_Shdr *shdr, int idx)
{
Elf_Data *data = elf_getdata (elf_getscn (ebl->elf, idx), NULL);
if (data == NULL)
{
ERROR (gettext ("section [%2d] '%s': cannot get section data\n"),
idx, section_name (ebl, idx));
return;
}
/* Check the fields of the section header. */
GElf_Shdr destshdr_mem;
GElf_Shdr *destshdr = NULL;
struct loaded_segment *loaded = NULL;
bool reldyn = check_reloc_shdr (ebl, ehdr, shdr, idx, ELF_T_REL, &destshdr,
&destshdr_mem, &loaded);
Elf_Scn *symscn = elf_getscn (ebl->elf, shdr->sh_link);
GElf_Shdr symshdr_mem;
GElf_Shdr *symshdr = gelf_getshdr (symscn, &symshdr_mem);
Elf_Data *symdata = elf_getdata (symscn, NULL);
enum load_state state = state_undecided;
size_t sh_entsize = gelf_fsize (ebl->elf, ELF_T_REL, 1, EV_CURRENT);
for (size_t cnt = 0; cnt < shdr->sh_size / sh_entsize; ++cnt)
{
GElf_Rel rel_mem;
GElf_Rel *rel = gelf_getrel (data, cnt, &rel_mem);
if (rel == NULL)
{
ERROR (gettext ("\
section [%2d] '%s': cannot get relocation %zu: %s\n"),
idx, section_name (ebl, idx), cnt, elf_errmsg (-1));
continue;
}
check_one_reloc (ebl, ehdr, shdr, idx, cnt, symshdr, symdata,
rel->r_offset, rel->r_info, destshdr, reldyn, loaded,
&state);
}
while (loaded != NULL)
{
struct loaded_segment *old = loaded;
loaded = loaded->next;
free (old);
}
}
/* Number of dynamic sections. */
static int ndynamic;
static void
check_dynamic (Ebl *ebl, GElf_Ehdr *ehdr, GElf_Shdr *shdr, int idx)
{
Elf_Data *data;
GElf_Shdr strshdr_mem;
GElf_Shdr *strshdr;
size_t cnt;
static const bool dependencies[DT_NUM][DT_NUM] =
{
[DT_NEEDED] = { [DT_STRTAB] = true },
[DT_PLTRELSZ] = { [DT_JMPREL] = true },
[DT_HASH] = { [DT_SYMTAB] = true },
[DT_STRTAB] = { [DT_STRSZ] = true },
[DT_SYMTAB] = { [DT_STRTAB] = true, [DT_SYMENT] = true },
[DT_RELA] = { [DT_RELASZ] = true, [DT_RELAENT] = true },
[DT_RELASZ] = { [DT_RELA] = true },
[DT_RELAENT] = { [DT_RELA] = true },
[DT_STRSZ] = { [DT_STRTAB] = true },
[DT_SYMENT] = { [DT_SYMTAB] = true },
[DT_SONAME] = { [DT_STRTAB] = true },
[DT_RPATH] = { [DT_STRTAB] = true },
[DT_REL] = { [DT_RELSZ] = true, [DT_RELENT] = true },
[DT_RELSZ] = { [DT_REL] = true },
[DT_RELENT] = { [DT_REL] = true },
[DT_JMPREL] = { [DT_PLTRELSZ] = true, [DT_PLTREL] = true },
[DT_RUNPATH] = { [DT_STRTAB] = true },
[DT_PLTREL] = { [DT_JMPREL] = true },
};
bool has_dt[DT_NUM];
bool has_val_dt[DT_VALNUM];
bool has_addr_dt[DT_ADDRNUM];
static const bool level2[DT_NUM] =
{
[DT_RPATH] = true,
[DT_SYMBOLIC] = true,
[DT_TEXTREL] = true,
[DT_BIND_NOW] = true
};
static const bool mandatory[DT_NUM] =
{
[DT_NULL] = true,
[DT_STRTAB] = true,
[DT_SYMTAB] = true,
[DT_STRSZ] = true,
[DT_SYMENT] = true
};
memset (has_dt, '\0', sizeof (has_dt));
memset (has_val_dt, '\0', sizeof (has_val_dt));
memset (has_addr_dt, '\0', sizeof (has_addr_dt));
if (++ndynamic == 2)
ERROR (gettext ("more than one dynamic section present\n"));
data = elf_getdata (elf_getscn (ebl->elf, idx), NULL);
if (data == NULL)
{
ERROR (gettext ("section [%2d] '%s': cannot get section data\n"),
idx, section_name (ebl, idx));
return;
}
strshdr = gelf_getshdr (elf_getscn (ebl->elf, shdr->sh_link), &strshdr_mem);
if (strshdr != NULL && strshdr->sh_type != SHT_STRTAB)
ERROR (gettext ("\
section [%2d] '%s': referenced as string table for section [%2d] '%s' but type is not SHT_STRTAB\n"),
shdr->sh_link, section_name (ebl, shdr->sh_link),
idx, section_name (ebl, idx));
else if (strshdr == NULL)
{
ERROR (gettext ("\
section [%2d]: referenced as string table for section [%2d] '%s' but section link value is invalid\n"),
shdr->sh_link, idx, section_name (ebl, idx));
return;
}
size_t sh_entsize = gelf_fsize (ebl->elf, ELF_T_DYN, 1, EV_CURRENT);
if (shdr->sh_entsize != sh_entsize)
ERROR (gettext ("\
section [%2d] '%s': section entry size does not match ElfXX_Dyn\n"),
idx, section_name (ebl, idx));
if (shdr->sh_info != 0)
ERROR (gettext ("section [%2d] '%s': sh_info not zero\n"),
idx, section_name (ebl, idx));
bool non_null_warned = false;
for (cnt = 0; cnt < shdr->sh_size / sh_entsize; ++cnt)
{
GElf_Dyn dyn_mem;
GElf_Dyn *dyn = gelf_getdyn (data, cnt, &dyn_mem);
if (dyn == NULL)
{
ERROR (gettext ("\
section [%2d] '%s': cannot get dynamic section entry %zu: %s\n"),
idx, section_name (ebl, idx), cnt, elf_errmsg (-1));
continue;
}
if (has_dt[DT_NULL] && dyn->d_tag != DT_NULL && ! non_null_warned)
{
ERROR (gettext ("\
section [%2d] '%s': non-DT_NULL entries follow DT_NULL entry\n"),
idx, section_name (ebl, idx));
non_null_warned = true;
}
if (!ebl_dynamic_tag_check (ebl, dyn->d_tag))
ERROR (gettext ("section [%2d] '%s': entry %zu: unknown tag\n"),
idx, section_name (ebl, idx), cnt);
if (dyn->d_tag >= 0 && dyn->d_tag < DT_NUM)
{
if (has_dt[dyn->d_tag]
&& dyn->d_tag != DT_NEEDED
&& dyn->d_tag != DT_NULL
&& dyn->d_tag != DT_POSFLAG_1)
{
char buf[50];
ERROR (gettext ("\
section [%2d] '%s': entry %zu: more than one entry with tag %s\n"),
idx, section_name (ebl, idx), cnt,
ebl_dynamic_tag_name (ebl, dyn->d_tag,
buf, sizeof (buf)));
}
if (be_strict && level2[dyn->d_tag])
{
char buf[50];
ERROR (gettext ("\
section [%2d] '%s': entry %zu: level 2 tag %s used\n"),
idx, section_name (ebl, idx), cnt,
ebl_dynamic_tag_name (ebl, dyn->d_tag,
buf, sizeof (buf)));
}
has_dt[dyn->d_tag] = true;
}
else if (dyn->d_tag >= 0 && dyn->d_tag <= DT_VALRNGHI
&& DT_VALTAGIDX (dyn->d_tag) < DT_VALNUM)
has_val_dt[DT_VALTAGIDX (dyn->d_tag)] = true;
else if (dyn->d_tag >= 0 && dyn->d_tag <= DT_ADDRRNGHI
&& DT_ADDRTAGIDX (dyn->d_tag) < DT_ADDRNUM)
has_addr_dt[DT_ADDRTAGIDX (dyn->d_tag)] = true;
if (dyn->d_tag == DT_PLTREL && dyn->d_un.d_val != DT_REL
&& dyn->d_un.d_val != DT_RELA)
ERROR (gettext ("\
section [%2d] '%s': entry %zu: DT_PLTREL value must be DT_REL or DT_RELA\n"),
idx, section_name (ebl, idx), cnt);
/* Check that addresses for entries are in loaded segments. */
switch (dyn->d_tag)
{
size_t n;
case DT_STRTAB:
/* We require the referenced section is the same as the one
specified in sh_link. */
if (strshdr->sh_addr != dyn->d_un.d_val)
{
ERROR (gettext ("\
section [%2d] '%s': entry %zu: pointer does not match address of section [%2d] '%s' referenced by sh_link\n"),
idx, section_name (ebl, idx), cnt,
shdr->sh_link, section_name (ebl, shdr->sh_link));
break;
}
goto check_addr;
default:
if (dyn->d_tag < DT_ADDRRNGLO || dyn->d_tag > DT_ADDRRNGHI)
/* Value is no pointer. */
break;
FALLTHROUGH;
case DT_AUXILIARY:
case DT_FILTER:
case DT_FINI:
case DT_FINI_ARRAY:
case DT_HASH:
case DT_INIT:
case DT_INIT_ARRAY:
case DT_JMPREL:
case DT_PLTGOT:
case DT_REL:
case DT_RELA:
case DT_SYMBOLIC:
case DT_SYMTAB:
case DT_VERDEF:
case DT_VERNEED:
case DT_VERSYM:
check_addr:
for (n = 0; n < phnum; ++n)
{
GElf_Phdr phdr_mem;
GElf_Phdr *phdr = gelf_getphdr (ebl->elf, n, &phdr_mem);
if (phdr != NULL && phdr->p_type == PT_LOAD
&& phdr->p_vaddr <= dyn->d_un.d_ptr
&& phdr->p_vaddr + phdr->p_memsz > dyn->d_un.d_ptr)
break;
}
if (unlikely (n >= phnum))
{
char buf[50];
ERROR (gettext ("\
section [%2d] '%s': entry %zu: %s value must point into loaded segment\n"),
idx, section_name (ebl, idx), cnt,
ebl_dynamic_tag_name (ebl, dyn->d_tag, buf,
sizeof (buf)));
}
break;
case DT_NEEDED:
case DT_RPATH:
case DT_RUNPATH:
case DT_SONAME:
if (dyn->d_un.d_ptr >= strshdr->sh_size)
{
char buf[50];
ERROR (gettext ("\
section [%2d] '%s': entry %zu: %s value must be valid offset in section [%2d] '%s'\n"),
idx, section_name (ebl, idx), cnt,
ebl_dynamic_tag_name (ebl, dyn->d_tag, buf,
sizeof (buf)),
shdr->sh_link, section_name (ebl, shdr->sh_link));
}
break;
}
}
for (cnt = 1; cnt < DT_NUM; ++cnt)
if (has_dt[cnt])
{
for (int inner = 0; inner < DT_NUM; ++inner)
if (dependencies[cnt][inner] && ! has_dt[inner])
{
char buf1[50];
char buf2[50];
ERROR (gettext ("\
section [%2d] '%s': contains %s entry but not %s\n"),
idx, section_name (ebl, idx),
ebl_dynamic_tag_name (ebl, cnt, buf1, sizeof (buf1)),
ebl_dynamic_tag_name (ebl, inner, buf2, sizeof (buf2)));
}
}
else
{
if (mandatory[cnt])
{
char buf[50];
ERROR (gettext ("\
section [%2d] '%s': mandatory tag %s not present\n"),
idx, section_name (ebl, idx),
ebl_dynamic_tag_name (ebl, cnt, buf, sizeof (buf)));
}
}
/* Make sure we have an hash table. */
if (!has_dt[DT_HASH] && !has_addr_dt[DT_ADDRTAGIDX (DT_GNU_HASH)])
ERROR (gettext ("\
section [%2d] '%s': no hash section present\n"),
idx, section_name (ebl, idx));
/* The GNU-style hash table also needs a symbol table. */
if (!has_dt[DT_HASH] && has_addr_dt[DT_ADDRTAGIDX (DT_GNU_HASH)]
&& !has_dt[DT_SYMTAB])
ERROR (gettext ("\
section [%2d] '%s': contains %s entry but not %s\n"),
idx, section_name (ebl, idx),
"DT_GNU_HASH", "DT_SYMTAB");
/* Check the rel/rela tags. At least one group must be available. */
if ((has_dt[DT_RELA] || has_dt[DT_RELASZ] || has_dt[DT_RELAENT])
&& (!has_dt[DT_RELA] || !has_dt[DT_RELASZ] || !has_dt[DT_RELAENT]))
ERROR (gettext ("\
section [%2d] '%s': not all of %s, %s, and %s are present\n"),
idx, section_name (ebl, idx),
"DT_RELA", "DT_RELASZ", "DT_RELAENT");
if ((has_dt[DT_REL] || has_dt[DT_RELSZ] || has_dt[DT_RELENT])
&& (!has_dt[DT_REL] || !has_dt[DT_RELSZ] || !has_dt[DT_RELENT]))
ERROR (gettext ("\
section [%2d] '%s': not all of %s, %s, and %s are present\n"),
idx, section_name (ebl, idx),
"DT_REL", "DT_RELSZ", "DT_RELENT");
/* Check that all prelink sections are present if any of them is. */
if (has_val_dt[DT_VALTAGIDX (DT_GNU_PRELINKED)]
|| has_val_dt[DT_VALTAGIDX (DT_CHECKSUM)])
{
if (!has_val_dt[DT_VALTAGIDX (DT_GNU_PRELINKED)])
ERROR (gettext ("\
section [%2d] '%s': %s tag missing in DSO marked during prelinking\n"),
idx, section_name (ebl, idx), "DT_GNU_PRELINKED");
if (!has_val_dt[DT_VALTAGIDX (DT_CHECKSUM)])
ERROR (gettext ("\
section [%2d] '%s': %s tag missing in DSO marked during prelinking\n"),
idx, section_name (ebl, idx), "DT_CHECKSUM");
/* Only DSOs can be marked like this. */
if (ehdr->e_type != ET_DYN)
ERROR (gettext ("\
section [%2d] '%s': non-DSO file marked as dependency during prelink\n"),
idx, section_name (ebl, idx));
}
if (has_val_dt[DT_VALTAGIDX (DT_GNU_CONFLICTSZ)]
|| has_val_dt[DT_VALTAGIDX (DT_GNU_LIBLISTSZ)]
|| has_addr_dt[DT_ADDRTAGIDX (DT_GNU_CONFLICT)]
|| has_addr_dt[DT_ADDRTAGIDX (DT_GNU_LIBLIST)])
{
if (!has_val_dt[DT_VALTAGIDX (DT_GNU_CONFLICTSZ)])
ERROR (gettext ("\
section [%2d] '%s': %s tag missing in prelinked executable\n"),
idx, section_name (ebl, idx), "DT_GNU_CONFLICTSZ");
if (!has_val_dt[DT_VALTAGIDX (DT_GNU_LIBLISTSZ)])
ERROR (gettext ("\
section [%2d] '%s': %s tag missing in prelinked executable\n"),
idx, section_name (ebl, idx), "DT_GNU_LIBLISTSZ");
if (!has_addr_dt[DT_ADDRTAGIDX (DT_GNU_CONFLICT)])
ERROR (gettext ("\
section [%2d] '%s': %s tag missing in prelinked executable\n"),
idx, section_name (ebl, idx), "DT_GNU_CONFLICT");
if (!has_addr_dt[DT_ADDRTAGIDX (DT_GNU_LIBLIST)])
ERROR (gettext ("\
section [%2d] '%s': %s tag missing in prelinked executable\n"),
idx, section_name (ebl, idx), "DT_GNU_LIBLIST");
}
}
static void
check_symtab_shndx (Ebl *ebl, GElf_Ehdr *ehdr, GElf_Shdr *shdr, int idx)
{
if (ehdr->e_type != ET_REL)
{
ERROR (gettext ("\
section [%2d] '%s': only relocatable files can have extended section index\n"),
idx, section_name (ebl, idx));
return;
}
Elf_Scn *symscn = elf_getscn (ebl->elf, shdr->sh_link);
GElf_Shdr symshdr_mem;
GElf_Shdr *symshdr = gelf_getshdr (symscn, &symshdr_mem);
if (symshdr != NULL && symshdr->sh_type != SHT_SYMTAB)
ERROR (gettext ("\
section [%2d] '%s': extended section index section not for symbol table\n"),
idx, section_name (ebl, idx));
else if (symshdr == NULL)
ERROR (gettext ("\
section [%2d] '%s': sh_link extended section index [%2d] is invalid\n"),
idx, section_name (ebl, idx), shdr->sh_link);
Elf_Data *symdata = elf_getdata (symscn, NULL);
if (symdata == NULL)
ERROR (gettext ("cannot get data for symbol section\n"));
if (shdr->sh_entsize != sizeof (Elf32_Word))
ERROR (gettext ("\
section [%2d] '%s': entry size does not match Elf32_Word\n"),
idx, section_name (ebl, idx));
if (symshdr != NULL
&& shdr->sh_entsize != 0
&& symshdr->sh_entsize != 0
&& (shdr->sh_size / shdr->sh_entsize
< symshdr->sh_size / symshdr->sh_entsize))
ERROR (gettext ("\
section [%2d] '%s': extended index table too small for symbol table\n"),
idx, section_name (ebl, idx));
if (shdr->sh_info != 0)
ERROR (gettext ("section [%2d] '%s': sh_info not zero\n"),
idx, section_name (ebl, idx));
for (size_t cnt = idx + 1; cnt < shnum; ++cnt)
{
GElf_Shdr rshdr_mem;
GElf_Shdr *rshdr = gelf_getshdr (elf_getscn (ebl->elf, cnt), &rshdr_mem);
if (rshdr != NULL && rshdr->sh_type == SHT_SYMTAB_SHNDX
&& rshdr->sh_link == shdr->sh_link)
{
ERROR (gettext ("\
section [%2d] '%s': extended section index in section [%2zu] '%s' refers to same symbol table\n"),
idx, section_name (ebl, idx),
cnt, section_name (ebl, cnt));
break;
}
}
Elf_Data *data = elf_getdata (elf_getscn (ebl->elf, idx), NULL);
if (data == NULL || data->d_buf == NULL)
{
ERROR (gettext ("section [%2d] '%s': cannot get section data\n"),
idx, section_name (ebl, idx));
return;
}
if (data->d_size < sizeof (Elf32_Word)
|| *((Elf32_Word *) data->d_buf) != 0)
ERROR (gettext ("symbol 0 should have zero extended section index\n"));
for (size_t cnt = 1; cnt < data->d_size / sizeof (Elf32_Word); ++cnt)
{
Elf32_Word xndx = ((Elf32_Word *) data->d_buf)[cnt];
if (xndx != 0)
{
GElf_Sym sym_data;
GElf_Sym *sym = gelf_getsym (symdata, cnt, &sym_data);
if (sym == NULL)
{
ERROR (gettext ("cannot get data for symbol %zu\n"), cnt);
continue;
}
if (sym->st_shndx != SHN_XINDEX)
ERROR (gettext ("\
extended section index is %" PRIu32 " but symbol index is not XINDEX\n"),
(uint32_t) xndx);
}
}
}
static void
check_sysv_hash (Ebl *ebl, GElf_Shdr *shdr, Elf_Data *data, int idx,
GElf_Shdr *symshdr)
{
Elf32_Word nbucket = ((Elf32_Word *) data->d_buf)[0];
Elf32_Word nchain = ((Elf32_Word *) data->d_buf)[1];
if (shdr->sh_size < (2ULL + nbucket + nchain) * sizeof (Elf32_Word))
{
ERROR (gettext ("\
section [%2d] '%s': hash table section is too small (is %ld, expected %ld)\n"),
idx, section_name (ebl, idx), (long int) shdr->sh_size,
(long int) ((2 + nbucket + nchain) * sizeof (Elf32_Word)));
return;
}
size_t maxidx = nchain;
if (symshdr != NULL && symshdr->sh_entsize != 0)
{
size_t symsize = symshdr->sh_size / symshdr->sh_entsize;
if (nchain > symshdr->sh_size / symshdr->sh_entsize)
ERROR (gettext ("section [%2d] '%s': chain array too large\n"),
idx, section_name (ebl, idx));
maxidx = symsize;
}
Elf32_Word *buf = (Elf32_Word *) data->d_buf;
Elf32_Word *end = (Elf32_Word *) ((char *) data->d_buf + shdr->sh_size);
size_t cnt;
for (cnt = 2; cnt < 2 + nbucket; ++cnt)
{
if (buf + cnt >= end)
break;
else if (buf[cnt] >= maxidx)
ERROR (gettext ("\
section [%2d] '%s': hash bucket reference %zu out of bounds\n"),
idx, section_name (ebl, idx), cnt - 2);
}
for (; cnt < 2 + nbucket + nchain; ++cnt)
{
if (buf + cnt >= end)
break;
else if (buf[cnt] >= maxidx)
ERROR (gettext ("\
section [%2d] '%s': hash chain reference %zu out of bounds\n"),
idx, section_name (ebl, idx), cnt - 2 - nbucket);
}
}
static void
check_sysv_hash64 (Ebl *ebl, GElf_Shdr *shdr, Elf_Data *data, int idx,
GElf_Shdr *symshdr)
{
Elf64_Xword nbucket = ((Elf64_Xword *) data->d_buf)[0];
Elf64_Xword nchain = ((Elf64_Xword *) data->d_buf)[1];
uint64_t maxwords = shdr->sh_size / sizeof (Elf64_Xword);
if (maxwords < 2
|| maxwords - 2 < nbucket
|| maxwords - 2 - nbucket < nchain)
{
ERROR (gettext ("\
section [%2d] '%s': hash table section is too small (is %ld, expected %ld)\n"),
idx, section_name (ebl, idx), (long int) shdr->sh_size,
(long int) ((2 + nbucket + nchain) * sizeof (Elf64_Xword)));
return;
}
size_t maxidx = nchain;
if (symshdr != NULL && symshdr->sh_entsize != 0)
{
size_t symsize = symshdr->sh_size / symshdr->sh_entsize;
if (nchain > symshdr->sh_size / symshdr->sh_entsize)
ERROR (gettext ("section [%2d] '%s': chain array too large\n"),
idx, section_name (ebl, idx));
maxidx = symsize;
}
Elf64_Xword *buf = (Elf64_Xword *) data->d_buf;
Elf64_Xword *end = (Elf64_Xword *) ((char *) data->d_buf + shdr->sh_size);
size_t cnt;
for (cnt = 2; cnt < 2 + nbucket; ++cnt)
{
if (buf + cnt >= end)
break;
else if (buf[cnt] >= maxidx)
ERROR (gettext ("\
section [%2d] '%s': hash bucket reference %zu out of bounds\n"),
idx, section_name (ebl, idx), cnt - 2);
}
for (; cnt < 2 + nbucket + nchain; ++cnt)
{
if (buf + cnt >= end)
break;
else if (buf[cnt] >= maxidx)
ERROR (gettext ("\
section [%2d] '%s': hash chain reference %" PRIu64 " out of bounds\n"),
idx, section_name (ebl, idx), (uint64_t) cnt - 2 - nbucket);
}
}
static void
check_gnu_hash (Ebl *ebl, GElf_Shdr *shdr, Elf_Data *data, int idx,
GElf_Shdr *symshdr)
{
if (data->d_size < 4 * sizeof (Elf32_Word))
{
ERROR (gettext ("\
section [%2d] '%s': not enough data\n"),
idx, section_name (ebl, idx));
return;
}
Elf32_Word nbuckets = ((Elf32_Word *) data->d_buf)[0];
Elf32_Word symbias = ((Elf32_Word *) data->d_buf)[1];
Elf32_Word bitmask_words = ((Elf32_Word *) data->d_buf)[2];
if (bitmask_words == 0 || !powerof2 (bitmask_words))
{
ERROR (gettext ("\
section [%2d] '%s': bitmask size zero or not power of 2: %u\n"),
idx, section_name (ebl, idx), bitmask_words);
return;
}
size_t bitmask_idxmask = bitmask_words - 1;
if (gelf_getclass (ebl->elf) == ELFCLASS64)
bitmask_words *= 2;
Elf32_Word shift = ((Elf32_Word *) data->d_buf)[3];
/* Is there still room for the sym chain?
Use uint64_t calculation to prevent 32bit overlow. */
uint64_t used_buf = (4ULL + bitmask_words + nbuckets) * sizeof (Elf32_Word);
if (used_buf > data->d_size)
{
ERROR (gettext ("\
section [%2d] '%s': hash table section is too small (is %ld, expected at least %ld)\n"),
idx, section_name (ebl, idx), (long int) shdr->sh_size,
(long int) used_buf);
return;
}
if (shift > 31)
{
ERROR (gettext ("\
section [%2d] '%s': 2nd hash function shift too big: %u\n"),
idx, section_name (ebl, idx), shift);
return;
}
size_t maxidx = shdr->sh_size / sizeof (Elf32_Word) - (4 + bitmask_words
+ nbuckets);
if (symshdr != NULL && symshdr->sh_entsize != 0)
maxidx = MIN (maxidx, symshdr->sh_size / symshdr->sh_entsize);
/* We need the symbol section data. */
Elf_Data *symdata = elf_getdata (elf_getscn (ebl->elf, shdr->sh_link), NULL);
union
{
Elf32_Word *p32;
Elf64_Xword *p64;
} bitmask = { .p32 = &((Elf32_Word *) data->d_buf)[4] },
collected = { .p32 = xcalloc (bitmask_words, sizeof (Elf32_Word)) };
size_t classbits = gelf_getclass (ebl->elf) == ELFCLASS32 ? 32 : 64;
size_t cnt;
for (cnt = 4 + bitmask_words; cnt < 4 + bitmask_words + nbuckets; ++cnt)
{
Elf32_Word symidx = ((Elf32_Word *) data->d_buf)[cnt];
if (symidx == 0)
continue;
if (symidx < symbias)
{
ERROR (gettext ("\
section [%2d] '%s': hash chain for bucket %zu lower than symbol index bias\n"),
idx, section_name (ebl, idx), cnt - (4 + bitmask_words));
continue;
}
while (symidx - symbias < maxidx)
{
Elf32_Word chainhash = ((Elf32_Word *) data->d_buf)[4
+ bitmask_words
+ nbuckets
+ symidx
- symbias];
if (symdata != NULL)
{
/* Check that the referenced symbol is not undefined. */
GElf_Sym sym_mem;
GElf_Sym *sym = gelf_getsym (symdata, symidx, &sym_mem);
if (sym != NULL && sym->st_shndx == SHN_UNDEF
&& GELF_ST_TYPE (sym->st_info) != STT_FUNC)
ERROR (gettext ("\
section [%2d] '%s': symbol %u referenced in chain for bucket %zu is undefined\n"),
idx, section_name (ebl, idx), symidx,
cnt - (4 + bitmask_words));
const char *symname = (sym != NULL
? elf_strptr (ebl->elf, symshdr->sh_link,
sym->st_name)
: NULL);
if (symname != NULL)
{
Elf32_Word hval = elf_gnu_hash (symname);
if ((hval & ~1u) != (chainhash & ~1u))
ERROR (gettext ("\
section [%2d] '%s': hash value for symbol %u in chain for bucket %zu wrong\n"),
idx, section_name (ebl, idx), symidx,
cnt - (4 + bitmask_words));
/* Set the bits in the bitmask. */
size_t maskidx = (hval / classbits) & bitmask_idxmask;
if (maskidx >= bitmask_words)
{
ERROR (gettext ("\
section [%2d] '%s': mask index for symbol %u in chain for bucket %zu wrong\n"),
idx, section_name (ebl, idx), symidx,
cnt - (4 + bitmask_words));
return;
}
if (classbits == 32)
{
collected.p32[maskidx]
|= UINT32_C (1) << (hval & (classbits - 1));
collected.p32[maskidx]
|= UINT32_C (1) << ((hval >> shift) & (classbits - 1));
}
else
{
collected.p64[maskidx]
|= UINT64_C (1) << (hval & (classbits - 1));
collected.p64[maskidx]
|= UINT64_C (1) << ((hval >> shift) & (classbits - 1));
}
}
}
if ((chainhash & 1) != 0)
break;
++symidx;
}
if (symidx - symbias >= maxidx)
ERROR (gettext ("\
section [%2d] '%s': hash chain for bucket %zu out of bounds\n"),
idx, section_name (ebl, idx), cnt - (4 + bitmask_words));
else if (symshdr != NULL && symshdr->sh_entsize != 0
&& symidx > symshdr->sh_size / symshdr->sh_entsize)
ERROR (gettext ("\
section [%2d] '%s': symbol reference in chain for bucket %zu out of bounds\n"),
idx, section_name (ebl, idx), cnt - (4 + bitmask_words));
}
if (memcmp (collected.p32, bitmask.p32, bitmask_words * sizeof (Elf32_Word)))
ERROR (gettext ("\
section [%2d] '%s': bitmask does not match names in the hash table\n"),
idx, section_name (ebl, idx));
free (collected.p32);
}
static void
check_hash (int tag, Ebl *ebl, GElf_Ehdr *ehdr, GElf_Shdr *shdr, int idx)
{
if (ehdr->e_type == ET_REL)
{
ERROR (gettext ("\
section [%2d] '%s': relocatable files cannot have hash tables\n"),
idx, section_name (ebl, idx));
return;
}
Elf_Data *data = elf_getdata (elf_getscn (ebl->elf, idx), NULL);
if (data == NULL || data->d_buf == NULL)
{
ERROR (gettext ("section [%2d] '%s': cannot get section data\n"),
idx, section_name (ebl, idx));
return;
}
GElf_Shdr symshdr_mem;
GElf_Shdr *symshdr = gelf_getshdr (elf_getscn (ebl->elf, shdr->sh_link),
&symshdr_mem);
if (symshdr != NULL && symshdr->sh_type != SHT_DYNSYM)
ERROR (gettext ("\
section [%2d] '%s': hash table not for dynamic symbol table\n"),
idx, section_name (ebl, idx));
else if (symshdr == NULL)
ERROR (gettext ("\
section [%2d] '%s': invalid sh_link symbol table section index [%2d]\n"),
idx, section_name (ebl, idx), shdr->sh_link);
size_t expect_entsize = (tag == SHT_GNU_HASH
? (gelf_getclass (ebl->elf) == ELFCLASS32
? sizeof (Elf32_Word) : 0)
: (size_t) ebl_sysvhash_entrysize (ebl));
if (shdr->sh_entsize != expect_entsize)
ERROR (gettext ("\
section [%2d] '%s': hash table entry size incorrect\n"),
idx, section_name (ebl, idx));
if ((shdr->sh_flags & SHF_ALLOC) == 0)
ERROR (gettext ("section [%2d] '%s': not marked to be allocated\n"),
idx, section_name (ebl, idx));
if (shdr->sh_size < (tag == SHT_GNU_HASH ? 4 : 2) * (expect_entsize ?: 4))
{
ERROR (gettext ("\
section [%2d] '%s': hash table has not even room for initial administrative entries\n"),
idx, section_name (ebl, idx));
return;
}
switch (tag)
{
case SHT_HASH:
if (ebl_sysvhash_entrysize (ebl) == sizeof (Elf64_Xword))
check_sysv_hash64 (ebl, shdr, data, idx, symshdr);
else
check_sysv_hash (ebl, shdr, data, idx, symshdr);
break;
case SHT_GNU_HASH:
check_gnu_hash (ebl, shdr, data, idx, symshdr);
break;
default:
assert (! "should not happen");
}
}
/* Compare content of both hash tables, it must be identical. */
static void
compare_hash_gnu_hash (Ebl *ebl, GElf_Ehdr *ehdr, size_t hash_idx,
size_t gnu_hash_idx)
{
Elf_Scn *hash_scn = elf_getscn (ebl->elf, hash_idx);
Elf_Data *hash_data = elf_getdata (hash_scn, NULL);
GElf_Shdr hash_shdr_mem;
GElf_Shdr *hash_shdr = gelf_getshdr (hash_scn, &hash_shdr_mem);
Elf_Scn *gnu_hash_scn = elf_getscn (ebl->elf, gnu_hash_idx);
Elf_Data *gnu_hash_data = elf_getdata (gnu_hash_scn, NULL);
GElf_Shdr gnu_hash_shdr_mem;
GElf_Shdr *gnu_hash_shdr = gelf_getshdr (gnu_hash_scn, &gnu_hash_shdr_mem);
if (hash_shdr == NULL || gnu_hash_shdr == NULL
|| hash_data == NULL || hash_data->d_buf == NULL
|| gnu_hash_data == NULL || gnu_hash_data->d_buf == NULL)
/* None of these pointers should be NULL since we used the
sections already. We are careful nonetheless. */
return;
/* The link must point to the same symbol table. */
if (hash_shdr->sh_link != gnu_hash_shdr->sh_link)
{
ERROR (gettext ("\
sh_link in hash sections [%2zu] '%s' and [%2zu] '%s' not identical\n"),
hash_idx, elf_strptr (ebl->elf, shstrndx, hash_shdr->sh_name),
gnu_hash_idx,
elf_strptr (ebl->elf, shstrndx, gnu_hash_shdr->sh_name));
return;
}
Elf_Scn *sym_scn = elf_getscn (ebl->elf, hash_shdr->sh_link);
Elf_Data *sym_data = elf_getdata (sym_scn, NULL);
GElf_Shdr sym_shdr_mem;
GElf_Shdr *sym_shdr = gelf_getshdr (sym_scn, &sym_shdr_mem);
if (sym_data == NULL || sym_data->d_buf == NULL
|| sym_shdr == NULL || sym_shdr->sh_entsize == 0)
return;
const char *hash_name;
const char *gnu_hash_name;
hash_name = elf_strptr (ebl->elf, shstrndx, hash_shdr->sh_name);
gnu_hash_name = elf_strptr (ebl->elf, shstrndx, gnu_hash_shdr->sh_name);
if (gnu_hash_data->d_size < 4 * sizeof (Elf32_Word))
{
ERROR (gettext ("\
hash section [%2zu] '%s' does not contain enough data\n"),
gnu_hash_idx, gnu_hash_name);
return;
}
uint32_t nentries = sym_shdr->sh_size / sym_shdr->sh_entsize;
char *used = alloca (nentries);
memset (used, '\0', nentries);
/* First go over the GNU_HASH table and mark the entries as used. */
const Elf32_Word *gnu_hasharr = (Elf32_Word *) gnu_hash_data->d_buf;