diff --git a/lld/ELF/InputFiles.h b/lld/ELF/InputFiles.h --- a/lld/ELF/InputFiles.h +++ b/lld/ELF/InputFiles.h @@ -370,6 +370,11 @@ // Used for --as-needed bool isNeeded; + +private: + template + std::vector parseVerneed(const llvm::object::ELFFile &obj, + const typename ELFT::Shdr *sec); }; class BinaryFile : public InputFile { diff --git a/lld/ELF/InputFiles.cpp b/lld/ELF/InputFiles.cpp --- a/lld/ELF/InputFiles.cpp +++ b/lld/ELF/InputFiles.cpp @@ -1211,6 +1211,42 @@ return verdefs; } +// Parse SHT_GNU_verneed to properly set the name of a versioned undefined +// symbol. We detect fatal issues which would cause vulnerabilities, but do not +// implement sophisticated error checking like in llvm-readobj because the value +// of such diagnostics is low. +template +std::vector SharedFile::parseVerneed(const ELFFile &obj, + const typename ELFT::Shdr *sec) { + if (!sec) + return {}; + std::vector verneeds; + ArrayRef data = CHECK(obj.getSectionContents(sec), this); + const uint8_t *verneedBuf = data.begin(); + for (unsigned i = 0; i != sec->sh_info; ++i) { + if (verneedBuf + sizeof(typename ELFT::Verneed) > data.end() || + uintptr_t(verneedBuf) % sizeof(uint32_t) != 0) + fatal(toString(this) + " has an invalid Verneed"); + auto *vn = reinterpret_cast(verneedBuf); + const uint8_t *vernauxBuf = verneedBuf + vn->vn_aux; + for (unsigned j = 0; j != vn->vn_cnt; ++j) { + if (vernauxBuf + sizeof(typename ELFT::Vernaux) > data.end() || + uintptr_t(vernauxBuf) % sizeof(uint32_t) != 0) + fatal(toString(this) + " has an invalid Vernaux"); + auto *aux = reinterpret_cast(vernauxBuf); + if (aux->vna_name >= this->stringTable.size()) + fatal(toString(this) + " has a Vernaux with an invalid vna_name"); + uint16_t version = aux->vna_other & VERSYM_VERSION; + if (version >= verneeds.size()) + verneeds.resize(version + 1); + verneeds[version] = aux->vna_name; + vernauxBuf += aux->vna_next; + } + verneedBuf += vn->vn_next; + } + return verneeds; +} + // We do not usually care about alignments of data in shared object // files because the loader takes care of it. However, if we promote a // DSO symbol to point to .bss due to copy relocation, we need to keep @@ -1254,6 +1290,7 @@ const Elf_Shdr *versymSec = nullptr; const Elf_Shdr *verdefSec = nullptr; + const Elf_Shdr *verneedSec = nullptr; // Search for .dynsym, .dynamic, .symtab, .gnu.version and .gnu.version_d. for (const Elf_Shdr &sec : sections) { @@ -1270,6 +1307,9 @@ case SHT_GNU_verdef: verdefSec = &sec; break; + case SHT_GNU_verneed: + verneedSec = &sec; + break; } } @@ -1309,12 +1349,13 @@ sharedFiles.push_back(this); verdefs = parseVerdefs(obj.base(), verdefSec); + std::vector verneeds = parseVerneed(obj, verneedSec); // Parse ".gnu.version" section which is a parallel array for the symbol // table. If a given file doesn't have a ".gnu.version" section, we use // VER_NDX_GLOBAL. size_t size = numELFSyms - firstGlobal; - std::vector versyms(size, VER_NDX_GLOBAL); + std::vector versyms(size, VER_NDX_GLOBAL); if (versymSec) { ArrayRef versym = CHECK(obj.template getSectionContentsAsArray(versymSec), @@ -1345,7 +1386,22 @@ continue; } + uint16_t idx = versyms[i] & ~VERSYM_HIDDEN; if (sym.isUndefined()) { + // For unversioned undefined symbols, VER_NDX_GLOBAL makes more sense but + // as of binutils 2.34, GNU ld produces VER_NDX_LOCAL. + if (idx != VER_NDX_LOCAL && idx != VER_NDX_GLOBAL) { + if (idx >= verneeds.size()) { + error("corrupt input file: version need index " + Twine(idx) + + " for symbol " + name + " is out of bounds\n>>> defined in " + + toString(this)); + continue; + } + StringRef verName = this->stringTable.data() + verneeds[idx]; + versionedNameBuffer.clear(); + name = + saver.save((name + "@" + verName).toStringRef(versionedNameBuffer)); + } Symbol *s = symtab->addSymbol( Undefined{this, name, sym.getBinding(), sym.st_other, sym.getType()}); s->exportDynamic = true; @@ -1355,7 +1411,6 @@ // MIPS BFD linker puts _gp_disp symbol into DSO files and incorrectly // assigns VER_NDX_LOCAL to this section global symbol. Here is a // workaround for this bug. - uint32_t idx = versyms[i] & ~VERSYM_HIDDEN; if (config->emachine == EM_MIPS && idx == VER_NDX_LOCAL && name == "_gp_disp") continue; diff --git a/lld/test/ELF/invalid/verneed-shared.yaml b/lld/test/ELF/invalid/verneed-shared.yaml new file mode 100644 --- /dev/null +++ b/lld/test/ELF/invalid/verneed-shared.yaml @@ -0,0 +1,79 @@ +## REQUIRES: x86 +## Test that we can parse SHT_GNU_verneed in a shared object and report certain errors. + +# RUN: echo '.globl _start; _start:' | llvm-mc -filetype=obj -triple=x86_64 - -o %t.o + +## sh_offset(SHT_GNU_verneed) is out of bounds. +# RUN: yaml2obj --docnum=1 %s -o %t1.so +# RUN: not ld.lld %t.o %t1.so -o /dev/null 2>&1 | FileCheck --check-prefix=SHOFFSET %s +# SHOFFSET: error: {{.*}}.so: section [index 1] has a sh_offset (0xffffffff) + sh_size (0x0) that is greater than the file size (0x228) +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_DYN + Machine: EM_X86_64 +Sections: + - Name: .gnu.version_r + Type: SHT_GNU_verneed + Flags: [ SHF_ALLOC ] + ShOffset: 0xFFFFFFFF + +## A Verneed entry is misaligned (not a multiple of 4). +# RUN: yaml2obj --docnum=2 %s -o %t2.so +# RUN: not ld.lld %t.o %t2.so -o /dev/null 2>&1 | FileCheck --check-prefix=VN-MISALIGNED %s +# VN-MISALIGNED: {{.*}}.so has an invalid Verneed +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_DYN + Machine: EM_X86_64 +Sections: + - Type: Fill + Size: 0x1 + - Name: .gnu.version_r + Type: SHT_GNU_verneed + Flags: [ SHF_ALLOC ] + Info: 1 + Link: .dynstr + Dependencies: + - Version: 1 + File: foo + Entries: + - Name: 'foo' + Hash: 0 + Flags: 0 + Other: 0 +DynamicSymbols: + - Name: foo + +## vn_aux points to a place outside of the file. +# RUN: yaml2obj --docnum=3 -D VERNEED=0100010001000000040200000000000000000000 %s -o %t3.so +# RUN: not ld.lld %t.o %t3.so -o /dev/null 2>&1 | FileCheck --check-prefix=AUX-OOB %s +# AUX-OOB: {{.*}}.so has an invalid Vernaux +--- !ELF +FileHeader: + Class: ELFCLASS64 + Data: ELFDATA2LSB + Type: ET_DYN + Machine: EM_X86_64 +Sections: + - Name: .gnu.version_r + Type: SHT_GNU_verneed + Flags: [ SHF_ALLOC ] + Info: 1 + Link: .dynstr + Content: "[[VERNEED]]" +DynamicSymbols: + - Name: foo + +## vn_aux is misaligned. +# RUN: yaml2obj --docnum=3 -D VERNEED=0100010001000000110000000000000000000000 %s -o %t4.so +# RUN: not ld.lld %t.o %t4.so -o /dev/null 2>&1 | FileCheck --check-prefix=AUX-MISALIGNED %s +# AUX-MISALIGNED: {{.*}}.so has an invalid Vernaux + +## vna_name is out of bounds. +# RUN: yaml2obj --docnum=3 -D VERNEED=010001000000000010000000000000009107000000000000ff00000000000000 %s -o %t5.so +# RUN: not ld.lld %t.o %t5.so -o /dev/null 2>&1 | FileCheck --check-prefix=NAME-OOB %s +# NAME-OOB: {{.*}}.so has a Vernaux with an invalid vna_name diff --git a/lld/test/ELF/verneed-shared.s b/lld/test/ELF/verneed-shared.s new file mode 100644 --- /dev/null +++ b/lld/test/ELF/verneed-shared.s @@ -0,0 +1,37 @@ +# REQUIRES: x86 +# RUN: echo 'v1 { f; }; v2 { g; };' > %t.ver +# RUN: llvm-mc -filetype=obj -triple=x86_64 %s -o %t.o +# RUN: ld.lld -shared --version-script %t.ver %t.o -o %t.so + +# RUN: ld.lld --version-script %t.ver %t.o %t.so -o /dev/null -y f@v1 | \ +# RUN: FileCheck --check-prefix=TRACE %s --implicit-check-not=f@v1 + +## TRACE: {{.*}}.o: definition of f@v1 +## TRACE-NEXT: {{.*}}.so: shared definition of f@v1 + +# RUN: echo '.symver f,f@v1; .symver g,g@v2; call f; call g' | \ +# RUN: llvm-mc -filetype=obj -triple=x86_64 - -o %t1.o +# RUN: ld.lld -shared %t1.o %t.so -o %t1.so + +## Test that we can parse SHT_GNU_verneed to know that the undefined symbols in +## %t1.so are called 'f@v1' and 'g@v2', which can be satisfied by the executable. +## We will thus export the symbols. +# RUN: ld.lld -pie %t.o %t1.so -o %t +# RUN: llvm-nm -D %t | FileCheck --check-prefix=NM %s + +# NM: T f +# NM: T g + +## The default is --no-allow-shlib-undefined. +## Don't error because undefined symbols in %t1.so are satisfied by %t.so +# RUN: llvm-mc -filetype=obj -triple=x86_64 /dev/null -o %t2.o +# RUN: ld.lld %t2.o %t1.so %t.so -y f@v1 -o /dev/null | FileCheck %s + +# CHECK: {{.*}}1.so: reference to f@v1 +# CHECK-NEXT: {{.*}}.so: shared definition of f@v1 + +.globl f_v1, g_v2 +.symver f_v1,f@v1 +.symver g_v2,g@v2 +f_v1: +g_v2: