diff --git a/llvm/test/tools/llvm-readobj/ELF/hash-histogram.test b/llvm/test/tools/llvm-readobj/ELF/hash-histogram.test index cdbec32efa24d..f7551b481a863 100644 --- a/llvm/test/tools/llvm-readobj/ELF/hash-histogram.test +++ b/llvm/test/tools/llvm-readobj/ELF/hash-histogram.test @@ -112,3 +112,98 @@ ProgramHeaders: Sections: - Section: .hash - Section: .dynamic + +## Each SHT_HASH section starts with two 32-bit fields: nbucket and nchain. +## Check we report an error when a DT_HASH value points to data that has size less than 8 bytes. + +# RUN: yaml2obj --docnum=3 %s -o %t3.o +# RUN: llvm-readelf --elf-hash-histogram %t3.o 2>&1 | FileCheck %s --check-prefix=ERR1 -DFILE=%t3.o + +# ERR1: warning: '[[FILE]]': the hash table at offset 0x2b1 goes past the end of the file (0x2b8){{$}} + +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_DYN + Machine: EM_X86_64 +Sections: + - Name: .hash + Type: SHT_HASH + Flags: [ SHF_ALLOC ] + Bucket: [ 0 ] + Chain: [ 0 ] + - Name: .dynamic + Type: SHT_DYNAMIC + Flags: [ SHF_WRITE, SHF_ALLOC ] + Entries: + - Tag: DT_HASH + Value: 0x239 + - Tag: DT_NULL + Value: 0x0 +DynamicSymbols: [] +ProgramHeaders: + - Type: PT_LOAD + FileSize: 0x23a + Sections: + - Section: .hash + - Section: .dynamic + +## Check we report a warning when the hash table goes past the end of the file. + +## Case A.1: the hash table ends right before the EOF. We have a broken nbucket +## field that has a value larger than the number of buckets. +# RUN: yaml2obj --docnum=4 %s -o %t4.1.o -DNBUCKET=0x5d -DNCHAIN=0x1 +# RUN: llvm-readelf --elf-hash-histogram %t4.1.o 2>&1 | \ +# RUN: FileCheck %s --implicit-check-not={{.}} --allow-empty + +## Case A.2: the hash table ends 1 byte past the EOF. We have a broken nbucket +## field that has a value larger than the number of buckets. +# RUN: yaml2obj --docnum=4 %s -o %t4.2.o -DNBUCKET=0x5e -DNCHAIN=0x1 +# RUN: llvm-readelf --elf-hash-histogram %t4.2.o 2>&1 | \ +# RUN: FileCheck %s --check-prefix=ERR2 -DFILE=%t4.2.o --implicit-check-not="warning:" +# ERR2: warning: '[[FILE]]': the hash table at offset 0x54 goes past the end of the file (0x1d4), nbucket = 94, nchain = 1{{$}} + +## Case B.1: the hash table ends right before the EOF. We have a broken nchain +## field that has a value larger than the number of chains. +# RUN: yaml2obj --docnum=4 %s -o %t4.3.o -DNBUCKET=0x1 -DNCHAIN=0x5d +# RUN: llvm-readelf --elf-hash-histogram %t4.3.o 2>&1 | \ +# RUN: FileCheck %s --check-prefix=ERR3 -DFILE=%t4.3.o --implicit-check-not="warning:" +# ERR3: warning: '[[FILE]]': hash table nchain (93) differs from symbol count derived from SHT_DYNSYM section header (1){{$}} + +## Case B.2: the hash table ends 1 byte past the EOF. We have a broken nchain +## field that has a value larger than the number of chains. +# RUN: yaml2obj --docnum=4 %s -o %t4.4.o -DNBUCKET=0x1 -DNCHAIN=0x5e +# RUN: llvm-readelf --elf-hash-histogram %t4.4.o 2>&1 | \ +# RUN: FileCheck %s --check-prefix=ERR4 -DFILE=%t4.4.o --implicit-check-not="warning:" +# ERR4: warning: '[[FILE]]': hash table nchain (94) differs from symbol count derived from SHT_DYNSYM section header (1){{$}} +# ERR4: warning: '[[FILE]]': the hash table at offset 0x54 goes past the end of the file (0x1d4), nbucket = 1, nchain = 94{{$}} + +--- !ELF +FileHeader: + Class: ELFCLASS32 + Data: ELFDATA2LSB + Type: ET_DYN + Machine: EM_X86_64 +Sections: + - Name: .hash + Type: SHT_HASH + Flags: [ SHF_ALLOC ] + Bucket: [ 0 ] + NBucket: [[NBUCKET]] + Chain: [ 0 ] + NChain: [[NCHAIN]] + - Name: .dynamic + Type: SHT_DYNAMIC + Flags: [ SHF_WRITE, SHF_ALLOC ] + Entries: + - Tag: DT_HASH + Value: 0x0 + - Tag: DT_NULL + Value: 0x0 +DynamicSymbols: [] +ProgramHeaders: + - Type: PT_LOAD + Sections: + - Section: .hash + - Section: .dynamic diff --git a/llvm/test/tools/llvm-readobj/ELF/hash-symbols.test b/llvm/test/tools/llvm-readobj/ELF/hash-symbols.test index 9434da1882a8a..40e680e3b7511 100644 --- a/llvm/test/tools/llvm-readobj/ELF/hash-symbols.test +++ b/llvm/test/tools/llvm-readobj/ELF/hash-symbols.test @@ -347,3 +347,104 @@ ProgramHeaders: Sections: - Section: .hash - Section: .dynamic + +## Each SHT_HASH section starts with two 32-bit fields: nbucket and nchain. +## Check we report an error when a DT_HASH value points to data that has size less than 8 bytes. + +# RUN: yaml2obj --docnum=6 %s -o %t6.o +# RUN: llvm-readelf --hash-symbols %t6.o 2>&1 | FileCheck %s --check-prefix=ERR1 -DFILE=%t6.o + +# ERR1: warning: '[[FILE]]': the hash table at offset 0x2b1 goes past the end of the file (0x2b8){{$}} + +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_DYN + Machine: EM_X86_64 +Sections: + - Name: .hash + Type: SHT_HASH + Flags: [ SHF_ALLOC ] + Bucket: [ 0 ] + Chain: [ 0 ] + - Name: .dynamic + Type: SHT_DYNAMIC + Flags: [ SHF_WRITE, SHF_ALLOC ] + Entries: + - Tag: DT_HASH + Value: 0x239 + - Tag: DT_NULL + Value: 0x0 +DynamicSymbols: [] +ProgramHeaders: + - Type: PT_LOAD + FileSize: 0x23a + Sections: + - Section: .hash + - Section: .dynamic + +## Check we report a warning when the hash table goes past the end of the file. + +## Case A.1: the hash table ends right before the EOF. We have a broken nbucket +## field that has a value larger than the number of buckets. +# RUN: yaml2obj --docnum=7 %s -o %t7.1.o -DNBUCKET=0x5d -DNCHAIN=0x1 +# RUN: llvm-readelf --hash-symbols %t7.1.o 2>&1 | FileCheck %s --check-prefix=NOERR1 +# NOERR1: Symbol table of .hash for image: +# NOERR1-NEXT: Num Buc: Value Size Type Bind Vis Ndx Name +# NOERR1-NEXT-EMPTY: + +## Case A.2: the hash table ends 1 byte past the EOF. We have a broken nbucket +## field that has a value larger than the number of buckets. +# RUN: yaml2obj --docnum=7 %s -o %t7.2.o -DNBUCKET=0x5e -DNCHAIN=0x1 +# RUN: llvm-readelf --hash-symbols %t7.2.o 2>&1 | FileCheck %s --check-prefix=ERR2 -DFILE=%t7.2.o +# ERR2: Symbol table of .hash for image: +# ERR2-NEXT: warning: '[[FILE]]': the hash table at offset 0x54 goes past the end of the file (0x1d4), nbucket = 94, nchain = 1{{$}} +# ERR2-NOT: {{.}} + +## Case B.1: the hash table ends right before the EOF. We have a broken nchain +## field that has a value larger than the number of chains. +# RUN: yaml2obj --docnum=7 %s -o %t7.3.o -DNBUCKET=0x1 -DNCHAIN=0x5d +# RUN: llvm-readelf --hash-symbols %t7.3.o 2>&1 | \ +# RUN: FileCheck %s --implicit-check-not="warning:" --check-prefix=NOERR2 -DFILE=%t7.3.o +# NOERR2: warning: '[[FILE]]': hash table nchain (93) differs from symbol count derived from SHT_DYNSYM section header (1) +# NOERR2: Symbol table of .hash for image: +# NOERR2-NEXT: Num Buc: Value Size Type Bind Vis Ndx Name +# NOERR2-NOT: {{.}} + +## Case B.2: the hash table ends 1 byte past the EOF. We have a broken nchain +## field that has a value larger than the number of chains. +# RUN: yaml2obj --docnum=7 %s -o %t7.4.o -DNBUCKET=0x1 -DNCHAIN=0x5e +# RUN: llvm-readelf --hash-symbols %t7.4.o 2>&1 | FileCheck %s --check-prefix=ERR3 -DFILE=%t7.4.o +# ERR3: Symbol table of .hash for image: +# ERR3-NEXT: warning: '[[FILE]]': the hash table at offset 0x54 goes past the end of the file (0x1d4), nbucket = 1, nchain = 94{{$}} +# ERR3-NOT: {{.}} + +--- !ELF +FileHeader: + Class: ELFCLASS32 + Data: ELFDATA2LSB + Type: ET_DYN + Machine: EM_X86_64 +Sections: + - Name: .hash + Type: SHT_HASH + Flags: [ SHF_ALLOC ] + Bucket: [ 0 ] + NBucket: [[NBUCKET]] + Chain: [ 0 ] + NChain: [[NCHAIN]] + - Name: .dynamic + Type: SHT_DYNAMIC + Flags: [ SHF_WRITE, SHF_ALLOC ] + Entries: + - Tag: DT_HASH + Value: 0x0 + - Tag: DT_NULL + Value: 0x0 +DynamicSymbols: [] +ProgramHeaders: + - Type: PT_LOAD + Sections: + - Section: .hash + - Section: .dynamic diff --git a/llvm/test/tools/llvm-readobj/ELF/hash-table.test b/llvm/test/tools/llvm-readobj/ELF/hash-table.test index 2abfdd01baf23..8cbe615eee22e 100644 --- a/llvm/test/tools/llvm-readobj/ELF/hash-table.test +++ b/llvm/test/tools/llvm-readobj/ELF/hash-table.test @@ -115,3 +115,133 @@ ProgramHeaders: VAddr: 0x1010 Sections: - Section: .dynamic + +## Each SHT_HASH section starts with two 32-bit fields: nbucket and nchain. +## Check we report an error when a DT_HASH value points to data that has size less than 8 bytes. + +# RUN: yaml2obj --docnum=4 %s -o %t4.o +# RUN: llvm-readelf --hash-table %t4.o 2>&1 | FileCheck %s --check-prefix=ERR1 -DFILE=%t4.o +# RUN: llvm-readobj --hash-table %t4.o 2>&1 | FileCheck %s --check-prefix=ERR1 -DFILE=%t4.o + +# ERR1: HashTable { +# ERR1-NEXT: warning: '[[FILE]]': the hash table at offset 0x2b1 goes past the end of the file (0x2b8){{$}} +# ERR1-NEXT: } + +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_DYN + Machine: EM_X86_64 +Sections: + - Name: .hash + Type: SHT_HASH + Flags: [ SHF_ALLOC ] + Bucket: [ 0 ] + Chain: [ 0 ] + - Name: .dynamic + Type: SHT_DYNAMIC + Flags: [ SHF_WRITE, SHF_ALLOC ] + Entries: + - Tag: DT_HASH + Value: 0x239 + - Tag: DT_NULL + Value: 0x0 +DynamicSymbols: [] +ProgramHeaders: + - Type: PT_LOAD + FileSize: 0x23a + Sections: + - Section: .hash + - Section: .dynamic + +## Check we report a warning when the hash table goes past the end of the file. + +## Case A.1: the hash table ends right before the EOF. We have a broken nbucket +## field that has a value larger than the number of buckets. +# RUN: yaml2obj --docnum=5 %s -o %t5.1.o -DNBUCKET=0x5d -DNCHAIN=0x1 +# RUN: llvm-readelf --hash-table %t5.1.o 2>&1 | \ +# RUN: FileCheck %s --check-prefix=NOERR1 --implicit-check-not="warning:" +# RUN: llvm-readobj --hash-table %t5.1.o 2>&1 | \ +# RUN: FileCheck %s --check-prefix=NOERR1 --implicit-check-not="warning:" + +# NOERR1: HashTable { +# NOERR1-NEXT: Num Buckets: 93 +# NOERR1-NEXT: Num Chains: 1 +## Here we would dump the rest of the file as buckets array because we have a broken nbucket field. +## No need to check what we dump, we only want to test that we have no unexpected warnings/crashes. +# NOERR1-NEXT: Buckets: +# NOERR1-NEXT: Chains: [0] +# NOERR1-NEXT: } + +## Case A.2: the hash table ends 1 byte past the EOF. We have a broken nbucket +## field that has a value larger than the number of buckets. +# RUN: yaml2obj --docnum=5 %s -o %t5.2.o -DNBUCKET=0x5e -DNCHAIN=0x1 +# RUN: llvm-readelf --hash-table %t5.2.o 2>&1 | \ +# RUN: FileCheck %s --check-prefix=ERR2 -DFILE=%t5.2.o --implicit-check-not="warning:" +# RUN: llvm-readobj --hash-table %t5.2.o 2>&1 | \ +# RUN: FileCheck %s --check-prefix=ERR2 -DFILE=%t5.2.o --implicit-check-not="warning:" + +# ERR2: HashTable { +# ERR2-NEXT: warning: '[[FILE]]': the hash table at offset 0x54 goes past the end of the file (0x1d4), nbucket = 94, nchain = 1{{$}} +# ERR2-NEXT: } + +## Case B.1: the hash table ends right before the EOF. We have a broken nchain +## field that has a value larger than the number of chains. +# RUN: yaml2obj --docnum=5 %s -o %t5.3.o -DNBUCKET=0x1 -DNCHAIN=0x5d +# RUN: llvm-readelf --hash-table %t5.3.o 2>&1 | \ +# RUN: FileCheck %s --check-prefix=NOERR2 -DFILE=%t5.3.o --implicit-check-not="warning:" +# RUN: llvm-readobj --hash-table %t5.3.o 2>&1 | \ +# RUN: FileCheck %s --check-prefix=NOERR2 -DFILE=%t5.3.o --implicit-check-not="warning:" + +# NOERR2: warning: '[[FILE]]': hash table nchain (93) differs from symbol count derived from SHT_DYNSYM section header (1) +# NOERR2: HashTable { +# NOERR2-NEXT: Num Buckets: 1 +# NOERR2-NEXT: Num Chains: 93 +# NOERR2-NEXT: Buckets: [0] +## Here we would dump the rest of the file as chain array because we have a broken nchain field. +## No need to check what we dump, we only want to test that we have no unexpected warnings/crashes. +# NOERR2-NEXT: Chains: +# NOERR2-NEXT: } + +## Case B.2: the hash table ends 1 byte past the EOF. We have a broken nchain +## field that has a value larger than the number of chains. +# RUN: yaml2obj --docnum=5 %s -o %t5.4.o -DNBUCKET=0x1 -DNCHAIN=0x5e +# RUN: llvm-readelf --hash-table %t5.4.o 2>&1 | \ +# RUN: FileCheck %s --check-prefix=ERR3 -DFILE=%t5.4.o --implicit-check-not="warning:" +# RUN: llvm-readobj --hash-table %t5.4.o 2>&1 | \ +# RUN: FileCheck %s --check-prefix=ERR3 -DFILE=%t5.4.o --implicit-check-not="warning:" + +# ERR3: warning: '[[FILE]]': hash table nchain (94) differs from symbol count derived from SHT_DYNSYM section header (1) +# ERR3: HashTable { +# ERR3-NEXT: warning: '[[FILE]]': the hash table at offset 0x54 goes past the end of the file (0x1d4), nbucket = 1, nchain = 94{{$}} +# ERR3-NEXT: } + +--- !ELF +FileHeader: + Class: ELFCLASS32 + Data: ELFDATA2LSB + Type: ET_DYN + Machine: EM_X86_64 +Sections: + - Name: .hash + Type: SHT_HASH + Flags: [ SHF_ALLOC ] + Bucket: [ 0 ] + NBucket: [[NBUCKET]] + Chain: [ 0 ] + NChain: [[NCHAIN]] + - Name: .dynamic + Type: SHT_DYNAMIC + Flags: [ SHF_WRITE, SHF_ALLOC ] + Entries: + - Tag: DT_HASH + Value: 0x0 + - Tag: DT_NULL + Value: 0x0 +DynamicSymbols: [] +ProgramHeaders: + - Type: PT_LOAD + Sections: + - Section: .hash + - Section: .dynamic diff --git a/llvm/tools/llvm-readobj/ELFDumper.cpp b/llvm/tools/llvm-readobj/ELFDumper.cpp index 6d785afd6530e..ca05f99aa715e 100644 --- a/llvm/tools/llvm-readobj/ELFDumper.cpp +++ b/llvm/tools/llvm-readobj/ELFDumper.cpp @@ -2607,9 +2607,35 @@ template void ELFDumper::printNeededLibraries() { W.startLine() << L << "\n"; } +template +static bool checkHashTable(const ELFFile *Obj, + const typename ELFT::Hash *H, StringRef FileName) { + auto WarnAndReturn = [&](uint64_t Off, const Twine &Msg = "") { + reportWarning(createError("the hash table at offset 0x" + + Twine::utohexstr(Off) + + " goes past the end of the file (0x" + + Twine::utohexstr(Obj->getBufSize()) + ")" + Msg), + FileName); + return false; + }; + + // Each SHT_HASH section starts from two 32-bit fields: nbucket and nchain. + const unsigned HeaderSize = 2 * sizeof(typename ELFT::Word); + const uint64_t SecOffset = (const uint8_t *)H - Obj->base(); + if (Obj->getBufSize() - SecOffset < HeaderSize) + return WarnAndReturn(SecOffset); + + if (Obj->getBufSize() - SecOffset - HeaderSize < + ((uint64_t)H->nbucket + H->nchain) * sizeof(typename ELFT::Word)) + return WarnAndReturn(SecOffset, ", nbucket = " + Twine(H->nbucket) + + ", nchain = " + Twine(H->nchain)); + return true; +} + template void ELFDumper::printHashTable() { DictScope D(W, "HashTable"); - if (!HashTable) + if (!HashTable || + !checkHashTable(ObjF->getELFFile(), HashTable, ObjF->getFileName())) return; W.printNumber("Num Buckets", HashTable->nbucket); W.printNumber("Num Chains", HashTable->nchain); @@ -3899,9 +3925,7 @@ template void GNUStyle::printHashSymbols(const ELFO *Obj) { auto StringTable = this->dumper()->getDynamicStringTable(); auto DynSyms = this->dumper()->dynamic_symbols(); - // Try printing .hash - if (auto SysVHash = this->dumper()->getHashTable()) { - OS << "\n Symbol table of .hash for image:\n"; + auto PrintHashTable = [&](const Elf_Hash *SysVHash) { if (ELFT::Is64Bits) OS << " Num Buc: Value Size Type Bind Vis Ndx Name"; else @@ -3930,6 +3954,12 @@ template void GNUStyle::printHashSymbols(const ELFO *Obj) { Visited[Ch] = true; } } + }; + + if (const Elf_Hash *SysVHash = this->dumper()->getHashTable()) { + OS << "\n Symbol table of .hash for image:\n"; + if (checkHashTable(Obj, SysVHash, this->FileName)) + PrintHashTable(SysVHash); } // Try printing .gnu.hash @@ -4452,6 +4482,9 @@ template void GNUStyle::printHashHistogram(const ELFFile *Obj) { // Print histogram for .hash section if (const Elf_Hash *HashTable = this->dumper()->getHashTable()) { + if (!checkHashTable(Obj, HashTable, this->FileName)) + return; + size_t NBucket = HashTable->nbucket; size_t NChain = HashTable->nchain; ArrayRef Buckets = HashTable->buckets();