diff options
Diffstat (limited to 'src/elflint.c')
-rw-r--r-- | src/elflint.c | 348 |
1 files changed, 308 insertions, 40 deletions
diff --git a/src/elflint.c b/src/elflint.c index cd335fe6..a679acc4 100644 --- a/src/elflint.c +++ b/src/elflint.c @@ -951,14 +951,14 @@ section [%2d] '%s': _DYNAMIC symbol size %" PRIu64 " does not match dynamic segm static bool is_rel_dyn (Ebl *ebl, const GElf_Ehdr *ehdr, int idx, const GElf_Shdr *shdr, - bool rela) + 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), rela ? ".rela.dyn" : ".rel.dyn")) + 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 @@ -984,14 +984,106 @@ is_rel_dyn (Ebl *ebl, const GElf_Ehdr *ehdr, int idx, const GElf_Shdr *shdr, if (dyn->d_tag == DT_RELCOUNT) { - /* Found it. One last check: does the number - specified number of relative relocations exceed - the total number of relocations? */ - if (dyn->d_un.d_val > shdr->sh_size / shdr->sh_entsize) + /* 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 (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); + 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) + 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 (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) + 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); + } + } } } @@ -1718,7 +1810,194 @@ extended section index is %" PRIu32 " but symbol index is not XINDEX\n"), static void -check_hash (Ebl *ebl, GElf_Ehdr *ehdr, GElf_Shdr *shdr, int idx) +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 < (2 + nbucket + nchain) * shdr->sh_entsize) + 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) * shdr->sh_entsize)); + + size_t maxidx = nchain; + + if (symshdr != NULL) + { + 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; + } + + size_t cnt; + for (cnt = 2; cnt < 2 + nbucket; ++cnt) + if (((Elf32_Word *) data->d_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 (((Elf32_Word *) data->d_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]; + + if (shdr->sh_size < (2 + nbucket + nchain) * shdr->sh_entsize) + 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) * shdr->sh_entsize)); + + size_t maxidx = nchain; + + if (symshdr != NULL) + { + 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; + } + + size_t cnt; + for (cnt = 2; cnt < 2 + nbucket; ++cnt) + if (((Elf64_Xword *) data->d_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 (((Elf64_Xword *) data->d_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_gnu_hash (Ebl *ebl, GElf_Shdr *shdr, Elf_Data *data, int idx, + GElf_Shdr *symshdr) +{ + Elf32_Word nbuckets = ((Elf32_Word *) data->d_buf)[0]; + Elf32_Word symbias = ((Elf32_Word *) data->d_buf)[1]; + + if (shdr->sh_size < (2 + 2 * nbuckets) * sizeof (Elf32_Word)) + { + 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) ((2 + 2 * nbuckets) * sizeof (Elf32_Word))); + return; + } + + size_t maxidx = shdr->sh_size / sizeof (Elf32_Word) - (2 + 2 * nbuckets); + + if (symshdr != NULL) + 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); + + size_t cnt; + for (cnt = 2; cnt < 2 + 2 * nbuckets; cnt += 2) + { + Elf32_Word bitset = ((Elf32_Word *) data->d_buf)[cnt]; + Elf32_Word symidx = ((Elf32_Word *) data->d_buf)[cnt + 1]; + + if (symidx == 0) + { + /* Nothing in here. No bit in the bitset should be set either. */ + if (bitset != 0) + ERROR (gettext ("\ +section [%2d] '%s': hash chain for bucket %zu empty but bitset is not\n"), + idx, section_name (ebl, idx), cnt / 2 - 1); + + 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 / 2 - 1); + continue; + } + + Elf32_Word collected_bitset = 0; + while (symidx - symbias < maxidx) + { + Elf32_Word chainhash = ((Elf32_Word *) data->d_buf)[2 + 2 * 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) + ERROR (gettext ("\ +section [%2d] '%s': symbol %u referenced in chain for bucket %zu is undefined\n"), + idx, section_name (ebl, idx), symidx, cnt / 2 - 1); + + const char *symname = elf_strptr (ebl->elf, symshdr->sh_link, + sym->st_name); + 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 / 2 - 1); + } + } + + collected_bitset |= 1 << ((chainhash >> 5) & 31); + + 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 / 2 - 1); + else if (symshdr != NULL + && 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 / 2 - 1); + + if (bitset != collected_bitset) + ERROR (gettext ("\ +section [%2d] '%s': bitset for bucket %zu does not match chain entries: computed %#x, reported %#x\n"), + idx, section_name (ebl, idx), cnt / 2 - 1, + collected_bitset, bitset); + } +} + + +static void +check_hash (int tag, Ebl *ebl, GElf_Ehdr *ehdr, GElf_Shdr *shdr, int idx) { if (ehdr->e_type == ET_REL) { @@ -1744,9 +2023,11 @@ section [%2d] '%s': relocatable files cannot have hash tables\n"), section [%2d] '%s': hash table not for dynamic symbol table\n"), idx, section_name (ebl, idx)); - if (shdr->sh_entsize != sizeof (Elf32_Word)) + if (shdr->sh_entsize != (tag == SHT_GNU_HASH + ? sizeof (Elf32_Word) + : (size_t) ebl_sysvhash_entrysize (ebl))) ERROR (gettext ("\ -section [%2d] '%s': entry size does not match Elf32_Word\n"), +section [%2d] '%s': hash table entry size incorrect\n"), idx, section_name (ebl, idx)); if ((shdr->sh_flags & SHF_ALLOC) == 0) @@ -1756,40 +2037,26 @@ section [%2d] '%s': entry size does not match Elf32_Word\n"), if (shdr->sh_size < 2 * shdr->sh_entsize) { ERROR (gettext ("\ -section [%2d] '%s': hash table has not even room for nbucket and nchain\n"), +section [%2d] '%s': hash table has not even room for initial two administrative entries\n"), idx, section_name (ebl, idx)); return; } - Elf32_Word nbucket = ((Elf32_Word *) data->d_buf)[0]; - Elf32_Word nchain = ((Elf32_Word *) data->d_buf)[1]; - - if (shdr->sh_size < (2 + nbucket + nchain) * shdr->sh_entsize) - 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) * shdr->sh_entsize)); - - if (symshdr != NULL) + switch (tag) { - size_t symsize = symshdr->sh_size / symshdr->sh_entsize; - size_t cnt; - - if (nchain < symshdr->sh_size / symshdr->sh_entsize) - ERROR (gettext ("section [%2d] '%s': chain array not large enough\n"), - idx, section_name (ebl, idx)); + 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; - for (cnt = 2; cnt < 2 + nbucket; ++cnt) - if (((Elf32_Word *) data->d_buf)[cnt] >= symsize) - ERROR (gettext ("\ -section [%2d] '%s': hash bucket reference %zu out of bounds\n"), - idx, section_name (ebl, idx), cnt - 2); + case SHT_GNU_HASH: + check_gnu_hash (ebl, shdr, data, idx, symshdr); + break; - for (; cnt < 2 + nbucket + nchain; ++cnt) - if (((Elf32_Word *) data->d_buf)[cnt] >= symsize) - ERROR (gettext ("\ -section [%2d] '%s': hash chain reference %zu out of bounds\n"), - idx, section_name (ebl, idx), cnt - 2 - nbucket); + default: + assert (! "should not happen"); } } @@ -2178,7 +2445,7 @@ section [%2d] '%s': symbol %d: local symbol with version\n"), index we need for this symbol. */ struct version_namelist *runp = version_namelist; while (runp != NULL) - if (runp->ndx == *versym) + if (runp->ndx == (*versym & 0x7fff)) break; else runp = runp->next; @@ -2945,7 +3212,8 @@ section [%2zu] '%s': relocatable files cannot have dynamic symbol tables\n"), break; case SHT_HASH: - check_hash (ebl, ehdr, shdr, cnt); + case SHT_GNU_HASH: + check_hash (shdr->sh_type, ebl, ehdr, shdr, cnt); break; case SHT_NULL: |