diff --git a/llvm/test/tools/llvm-readobj/ELF/gnuhash.test b/llvm/test/tools/llvm-readobj/ELF/gnuhash.test --- a/llvm/test/tools/llvm-readobj/ELF/gnuhash.test +++ b/llvm/test/tools/llvm-readobj/ELF/gnuhash.test @@ -301,3 +301,8 @@ # ERR-NEXT: Shift Count: 2 # ERR-NEXT: warning: '[[FILE]]': unable to dump the SHT_GNU_HASH section at 0x78: it goes past the end of the file # ERR-NEXT: } + +## Check we report a single warning about the broken GNU hash table when both +## --gnu-hash-table and --elf-hash-histogram options are requested. +# RUN: llvm-readelf --gnu-hash-table --elf-hash-histogram %t.err.nbuckets 2>&1 | \ +# RUN: FileCheck %s -DFILE=%t.err.nbuckets -DMASKWORDS=2 -DNBUCKETS=4294967295 --check-prefix=ERR --implicit-check-not=warning diff --git a/llvm/test/tools/llvm-readobj/ELF/hash-histogram.test b/llvm/test/tools/llvm-readobj/ELF/hash-histogram.test --- a/llvm/test/tools/llvm-readobj/ELF/hash-histogram.test +++ b/llvm/test/tools/llvm-readobj/ELF/hash-histogram.test @@ -266,3 +266,55 @@ - Section: .hash - Section: .gnu.hash - Section: .dynamic + +## Check we report a proper warning when the GNU hash table goes past the end of the file. + +## Case A: the 'nbuckets' field is set so that the GNU hash table goes past the end of the file. +## The value of 1 for the NBUCKETS is no-op. +# RUN: yaml2obj --docnum=6 -D MASKWORDS=4294967295 -D NBUCKETS=1 %s -o %t7 +# RUN: llvm-readelf --elf-hash-histogram %t7 2>&1 | \ +# RUN: FileCheck %s -DFILE=%t7 --check-prefix=ERR5 --implicit-check-not="Histogram" + +# ERR5: warning: '[[FILE]]': unable to dump the SHT_GNU_HASH section at 0x78: it goes past the end of the file + +## Case B: the 'maskwords' field is set so that the GNU hash table goes past the end of the file. +## The value of 1 for the MASKWORDS is no-op. +# RUN: yaml2obj --docnum=6 -D MASKWORDS=1 -D NBUCKETS=4294967295 %s -o %t8 +# RUN: llvm-readelf --elf-hash-histogram %t8 2>&1 | \ +# RUN: FileCheck %s -DFILE=%t8 --check-prefix=ERR5 --implicit-check-not="Histogram" + +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_DYN + Machine: EM_X86_64 +Sections: + - Name: .gnu.hash + Type: SHT_GNU_HASH + Flags: [ SHF_ALLOC ] + Header: + SymNdx: 0x0 + Shift2: 0x0 +## The number of words in the Bloom filter. + MaskWords: [[MASKWORDS]] +## The number of hash buckets. + NBuckets: [[NBUCKETS]] + BloomFilter: [ 0x0 ] + HashBuckets: [ 0x0 ] + HashValues: [ 0x0 ] + - Name: .dynamic + Type: SHT_DYNAMIC + Flags: [ SHF_ALLOC ] + Link: .dynstr + Entries: + - Tag: DT_GNU_HASH + Value: 0x0 + - Tag: DT_NULL + Value: 0x0 +DynamicSymbols: [] +ProgramHeaders: + - Type: PT_LOAD + Sections: + - Section: .gnu.hash + - Section: .dynamic diff --git a/llvm/tools/llvm-readobj/ELFDumper.cpp b/llvm/tools/llvm-readobj/ELFDumper.cpp --- a/llvm/tools/llvm-readobj/ELFDumper.cpp +++ b/llvm/tools/llvm-readobj/ELFDumper.cpp @@ -2666,6 +2666,28 @@ return true; } +template +static Error checkGNUHashTable(const ELFFile *Obj, + const typename ELFT::GnuHash *GnuHashTable, + bool *IsHeaderValid = nullptr) { + const uint8_t *TableData = reinterpret_cast(GnuHashTable); + assert(TableData >= Obj->base() && + TableData < Obj->base() + Obj->getBufSize() && + "GnuHashTable must always point to a location inside the file"); + + uint64_t TableOffset = TableData - Obj->base(); + if (IsHeaderValid) + *IsHeaderValid = TableOffset + /*Header size:*/ 16 < Obj->getBufSize(); + if (TableOffset + 16 + GnuHashTable->nbuckets * 4 + + GnuHashTable->maskwords * sizeof(typename ELFT::Off) >= + Obj->getBufSize()) + return createError("unable to dump the SHT_GNU_HASH " + "section at 0x" + + Twine::utohexstr(TableOffset) + + ": it goes past the end of the file"); + return Error::success(); +} + template void ELFDumper::printHashTable() { DictScope D(W, "HashTable"); if (!HashTable || @@ -2682,27 +2704,19 @@ DictScope D(W, "GnuHashTable"); if (!GnuHashTable) return; - W.printNumber("Num Buckets", GnuHashTable->nbuckets); - W.printNumber("First Hashed Symbol Index", GnuHashTable->symndx); - W.printNumber("Num Mask Words", GnuHashTable->maskwords); - W.printNumber("Shift Count", GnuHashTable->shift2); - - MemoryBufferRef File = Obj->getMemoryBufferRef(); - const char *TableData = reinterpret_cast(GnuHashTable); - assert(TableData >= File.getBufferStart() && - TableData < File.getBufferEnd() && - "GnuHashTable must always point to a location inside the file"); - uint64_t TableOffset = TableData - File.getBufferStart(); - if (TableOffset + - /*Header size:*/ 16 + GnuHashTable->nbuckets * 4 + - GnuHashTable->maskwords * sizeof(typename ELFT::Off) >= - File.getBufferSize()) { - reportWarning(createError("unable to dump the SHT_GNU_HASH " - "section at 0x" + - Twine::utohexstr(TableOffset) + - ": it goes past the end of the file"), - ObjF->getFileName()); + bool IsHeaderValid; + Error Err = + checkGNUHashTable(ObjF->getELFFile(), GnuHashTable, &IsHeaderValid); + if (IsHeaderValid) { + W.printNumber("Num Buckets", GnuHashTable->nbuckets); + W.printNumber("First Hashed Symbol Index", GnuHashTable->symndx); + W.printNumber("Num Mask Words", GnuHashTable->maskwords); + W.printNumber("Shift Count", GnuHashTable->shift2); + } + + if (Err) { + reportUniqueWarning(std::move(Err)); return; } @@ -4680,7 +4694,10 @@ // Print histogram for the .gnu.hash section. if (const Elf_GnuHash *GnuHashTable = this->dumper()->getGnuHashTable()) - printGnuHashHistogram(*GnuHashTable); + if (Error E = checkGNUHashTable(Obj, GnuHashTable)) + this->reportUniqueWarning(std::move(E)); + else + printGnuHashHistogram(*GnuHashTable); } template