diff --git a/llvm/include/llvm/Object/ELF.h b/llvm/include/llvm/Object/ELF.h --- a/llvm/include/llvm/Object/ELF.h +++ b/llvm/include/llvm/Object/ELF.h @@ -512,8 +512,9 @@ Expected ELFFile::getSymbolFromHashTable(const Elf_Shdr &Sec, StringRef Name) const { - if (Sec.sh_type != ELF::SHT_HASH) - return createError("invalid sh_type for hash table, expected SHT_HASH"); + if (Sec.sh_type != ELF::SHT_HASH && Sec.sh_type != ELF::SHT_GNU_HASH) + return createError( + "invalid sh_type for hash table, expected SHT_HASH or SHT_GNU_HASH"); Expected SectionsOrError = sections(); if (!SectionsOrError) return SectionsOrError.takeError(); @@ -532,34 +533,82 @@ return SymsOrErr.takeError(); ArrayRef SymTab = *SymsOrErr; - const Elf_Hash *HashTab = - reinterpret_cast(base() + Sec.sh_offset); + if (Sec.sh_type == ELF::SHT_HASH) { + const Elf_Hash *HashTab = + reinterpret_cast(base() + Sec.sh_offset); + if (Sec.sh_offset + Sec.sh_size >= Buf.size()) + return createError("section " + getSecIndexForError(*this, Sec) + + " has invalid sh_offset: " + Twine(Sec.sh_offset)); + if (Sec.sh_size < sizeof(Elf_Hash) || + Sec.sh_size < sizeof(Elf_Hash) + sizeof(Elf_Word) * HashTab->nbucket + + sizeof(Elf_Word) * HashTab->nchain) + return createError("section " + getSecIndexForError(*this, Sec) + + " has invalid sh_size: " + Twine(Sec.sh_size)); + + const uint32_t Hash = hashSysV(Name); + const Elf_Word NBucket = HashTab->nbucket; + ArrayRef Bucket = HashTab->buckets(); + ArrayRef Chain = HashTab->chains(); + for (Elf_Word I = Bucket[Hash % NBucket]; I != ELF::STN_UNDEF; + I = Chain[I]) { + if (I >= SymTab.size()) + return createError( + "symbol [index " + Twine(I) + + "] is greater than the number of symbols: " + Twine(SymTab.size())); + if (SymTab[I].st_name >= StrTab.size()) + return createError( + "symbol [index " + Twine(I) + + "] has invalid st_name: " + Twine(SymTab[I].st_name)); - if (Sec.sh_offset + Sec.sh_size >= Buf.size()) - return createError("section " + getSecIndexForError(*this, Sec) + - " has invalid sh_offset: " + Twine(Sec.sh_offset)); - if (Sec.sh_size < sizeof(Elf_Hash) || - Sec.sh_size < sizeof(Elf_Hash) + sizeof(Elf_Word) * HashTab->nbucket + - sizeof(Elf_Word) * HashTab->nchain) - return createError("section " + getSecIndexForError(*this, Sec) + - " has invalid sh_size: " + Twine(Sec.sh_size)); - - const uint32_t Hash = hashSysV(Name); - const Elf_Word NBucket = HashTab->nbucket; - ArrayRef Bucket = HashTab->buckets(); - ArrayRef Chain = HashTab->chains(); - - for (Elf_Word I = Bucket[Hash % NBucket]; I != ELF::STN_UNDEF; I = Chain[I]) { - if (I >= SymTab.size()) - return createError("symbol [index " + Twine(I) + - "] is greater than the number of symbols: " + - Twine(SymTab.size())); - if (SymTab[I].st_name >= StrTab.size()) - return createError("symbol [index " + Twine(I) + - "] has invalid st_name: " + Twine(SymTab[I].st_name)); - - if (StrTab.drop_front(SymTab[I].st_name).data() == Name) - return &SymTab[I]; + if (StrTab.drop_front(SymTab[I].st_name).data() == Name) + return &SymTab[I]; + } + } else if (Sec.sh_type == ELF::SHT_GNU_HASH) { + const Elf_GnuHash *HashTab = + reinterpret_cast(base() + Sec.sh_offset); + if (Sec.sh_offset + Sec.sh_size >= Buf.size()) + return createError("section " + getSecIndexForError(*this, Sec) + + " has invalid sh_offset: " + Twine(Sec.sh_offset)); + if (Sec.sh_size < sizeof(Elf_GnuHash) || + Sec.sh_size < sizeof(Elf_GnuHash) + + sizeof(Elf_Word) * HashTab->maskwords + + sizeof(Elf_Word) * HashTab->nbuckets + + sizeof(Elf_Word) * (SymTab.size() - HashTab->symndx)) + return createError("section " + getSecIndexForError(*this, Sec) + + " has invalid sh_size: " + Twine(Sec.sh_size)); + + const uint32_t NameHash = hashGnu(Name); + const Elf_Word NBucket = HashTab->nbuckets; + const Elf_Word SymOffset = HashTab->symndx; + ArrayRef Filter = HashTab->filter(); + ArrayRef Bucket = HashTab->buckets(); + ArrayRef Chain = HashTab->values(SymTab.size()); + + // Check the bloom filter and exit early if the symbol is not present. + uint64_t ElfClassBits = ELFT::Is64Bits ? 64 : 32; + Elf_Off Word = Filter[(NameHash / ElfClassBits) % HashTab->maskwords]; + uint64_t Mask = (0x1ull << (NameHash % ElfClassBits)) | + (0x1ull << ((NameHash >> HashTab->shift2) % ElfClassBits)); + if ((Word & Mask) != Mask) + return nullptr; + + for (Elf_Word I = Bucket[NameHash % NBucket]; + I >= SymOffset && I < SymTab.size(); I = I + 1) { + const uint32_t ChainHash = Chain[I - SymOffset]; + + if ((NameHash | 0x1) != (ChainHash | 0x1)) + continue; + + if (SymTab[I].st_name >= StrTab.size()) + return createError( + "symbol [index " + Twine(I) + + "] has invalid st_name: " + Twine(SymTab[I].st_name)); + if (StrTab.drop_front(SymTab[I].st_name).data() == Name) + return &SymTab[I]; + + if (ChainHash & 0x1) + break; + } } return nullptr; diff --git a/llvm/unittests/Object/ELFObjectFileTest.cpp b/llvm/unittests/Object/ELFObjectFileTest.cpp --- a/llvm/unittests/Object/ELFObjectFileTest.cpp +++ b/llvm/unittests/Object/ELFObjectFileTest.cpp @@ -871,7 +871,8 @@ auto SymOrErr = Elf.getSymbolFromHashTable(**StrTabOrErr, "x"); EXPECT_THAT_EXPECTED( SymOrErr, - FailedWithMessage("invalid sh_type for hash table, expected SHT_HASH")); + FailedWithMessage( + "invalid sh_type for hash table, expected SHT_HASH or SHT_GNU_HASH")); } // Test for getSymbolFromHashTable: check that it fails with an invalid offset. @@ -941,7 +942,8 @@ auto SymOrErr = Elf.getSymbolFromHashTable(**HashOrErr, "x"); EXPECT_THAT_EXPECTED( SymOrErr, - FailedWithMessage("section [index 2] has a sh_offset (0x270f) + sh_size (0x18) that is greater than the file size (0x218)")); + FailedWithMessage("section [index 2] has a sh_offset (0x270f) + sh_size " + "(0x18) that is greater than the file size (0x218)")); } // Test for getSymbolFromHashTable: check that it fails with an invalid size. @@ -1177,3 +1179,130 @@ FailedWithMessage("section header table goes past the " "end of the file: e_shoff = 0x270f")); } + +// Test for getSymbolFromHashTable: check that it returns the expected symbol +// using its associated name using the GNU_HASH table. +TEST(ELFObjectFileTest, SymbolFromGnuHashTableTest) { + SmallString<0> Storage; + Expected> ElfOrErr = toBinary(Storage, R"( +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_DYN +Sections: + - Name: .gnu.hash + Type: SHT_GNU_HASH + Link: .dynsym + Header: + SymNdx: 0x1 + Shift2: 0x1A + BloomFilter: [ 0xE0000001 ] + HashBuckets: [ 0x1 ] + HashValues: [ 0x2B61C, 0x2B61E, 0x2B61F ] + - Name: .dynsym + Type: SHT_DYNSYM + Link: .dynstr + - Name: .dynstr + Type: SHT_STRTAB +DynamicSymbols: + - Name: x + Index: SHN_ABS + Value: 0x0 + - Name: y + Index: SHN_ABS + Value: 0x1 + - Name: z + Index: SHN_ABS + Value: 0x3 +)"); + + ASSERT_THAT_EXPECTED(ElfOrErr, Succeeded()); + const ELFFile &Elf = ElfOrErr->getELFFile(); + + auto HashOrErr = Elf.getSection(/*SHT_GNU_HASH=*/1); + ASSERT_THAT_EXPECTED(HashOrErr, Succeeded()); + + auto StrTabOrErr = Elf.getSection(/*SHT_STRTAB=*/3); + ASSERT_THAT_EXPECTED(StrTabOrErr, Succeeded()); + auto ContentsOrErr = Elf.getSectionContentsAsArray(**StrTabOrErr); + ASSERT_THAT_EXPECTED(ContentsOrErr, Succeeded()); + StringRef StrTab(ContentsOrErr->begin(), ContentsOrErr->size()); + + for (StringRef SymbolName : {"x", "y", "z"}) { + auto SymOrErr = Elf.getSymbolFromHashTable(**HashOrErr, SymbolName); + ASSERT_THAT_EXPECTED(SymOrErr, Succeeded()); + ASSERT_TRUE(*SymOrErr); + auto FoundSymNameOrErr = (*SymOrErr)->getName(StrTab); + ASSERT_THAT_EXPECTED(FoundSymNameOrErr, Succeeded()); + ASSERT_TRUE(*FoundSymNameOrErr == SymbolName); + } + + for (StringRef SymbolName : {"a", "b", "c", "d", "e", "f", "g", "h", "i"}) { + auto MissingOrErr = Elf.getSymbolFromHashTable(**HashOrErr, SymbolName); + ASSERT_THAT_EXPECTED(MissingOrErr, Succeeded()); + ASSERT_TRUE(!*MissingOrErr); + } + + const_cast(*HashOrErr)->sh_size = 17; + auto SymOrErr = Elf.getSymbolFromHashTable(**HashOrErr, "x"); + EXPECT_THAT_EXPECTED( + SymOrErr, FailedWithMessage("section [index 1] has invalid sh_size: 17")); +} + +// Test for getSymbolFromHashTable: check that it returns the expected symbol +// using its associated name using the GNU_HASH table with a 32-bit bloom +// filter. +TEST(ELFObjectFileTest, SymbolFromGnuHashTableTest32) { + SmallString<0> Storage; + Expected> ElfOrErr = toBinary(Storage, R"( +--- !ELF +FileHeader: + Class: ELFCLASS32 + Data: ELFDATA2LSB + Type: ET_DYN +Sections: + - Name: .gnu.hash + Type: SHT_GNU_HASH + Link: .dynsym + Header: + SymNdx: 0x1 + Shift2: 0x1A + BloomFilter: [ 0xE0000001 ] + HashBuckets: [ 0x1 ] + HashValues: [ 0x2B61C, 0x2B61E, 0x2B61F ] + - Name: .dynsym + Type: SHT_DYNSYM + Link: .dynstr + - Name: .dynstr + Type: SHT_STRTAB +DynamicSymbols: + - Name: x + Index: SHN_ABS + Value: 0x0 + - Name: y + Index: SHN_ABS + Value: 0x1 + - Name: z + Index: SHN_ABS + Value: 0x3 +)"); + + ASSERT_THAT_EXPECTED(ElfOrErr, Succeeded()); + const ELFFile &Elf = ElfOrErr->getELFFile(); + + auto HashOrErr = Elf.getSection(/*SHT_GNU_HASH=*/1); + ASSERT_THAT_EXPECTED(HashOrErr, Succeeded()); + + auto StrTabOrErr = Elf.getSection(/*SHT_STRTAB=*/3); + ASSERT_THAT_EXPECTED(StrTabOrErr, Succeeded()); + auto ContentsOrErr = Elf.getSectionContentsAsArray(**StrTabOrErr); + ASSERT_THAT_EXPECTED(ContentsOrErr, Succeeded()); + StringRef StrTab(ContentsOrErr->begin(), ContentsOrErr->size()); + + for (StringRef SymbolName : {"a", "b", "c", "d", "e", "f", "g", "h", "i"}) { + auto MissingOrErr = Elf.getSymbolFromHashTable(**HashOrErr, SymbolName); + ASSERT_THAT_EXPECTED(MissingOrErr, Succeeded()); + ASSERT_TRUE(!*MissingOrErr); + } +}