diff --git a/src/elf.h b/src/elf.h index ac7032b7..ed1a67db 100644 --- a/src/elf.h +++ b/src/elf.h @@ -55,6 +55,8 @@ typedef uint16_t Elf64_Section; typedef Elf32_Half Elf32_Versym; typedef Elf64_Half Elf64_Versym; +#define VERSYM_HIDDEN 0x8000 +#define VERSYM_VERSION 0x7fff /* The ELF file header. This appears at the start of every ELF file. */ diff --git a/src/patchelf.cc b/src/patchelf.cc index b42111dd..3384233a 100644 --- a/src/patchelf.cc +++ b/src/patchelf.cc @@ -1021,7 +1021,7 @@ void ElfFile::rewriteSectionsExecutable() debug("needed space is %d\n", neededSpace); /* Calculate how many bytes are needed out of the additional pages. */ - size_t extraSpace = neededSpace - startOffset; + size_t extraSpace = neededSpace - startOffset; // Always give one extra page to avoid colliding with segments that start at // unaligned addresses and will be rounded down when loaded unsigned int neededPages = 1 + roundUp(extraSpace, getPageSize()) / getPageSize(); @@ -1237,8 +1237,14 @@ void ElfFile::rewriteHeaders(Elf_Addr phdrAddress) } else if (d_tag == DT_VERNEED) dyn->d_un.d_ptr = findSectionHeader(".gnu.version_r").sh_addr; + else if (d_tag == DT_VERNEEDNUM) + dyn->d_un.d_val = findSectionHeader(".gnu.version_r").sh_info; else if (d_tag == DT_VERSYM) dyn->d_un.d_ptr = findSectionHeader(".gnu.version").sh_addr; + else if (d_tag == DT_VERDEF) + dyn->d_un.d_ptr = findSectionHeader(".gnu.version_d").sh_addr; + else if (d_tag == DT_VERDEFNUM) + dyn->d_un.d_val = findSectionHeader(".gnu.version_d").sh_info; else if (d_tag == DT_MIPS_RLD_MAP_REL) { /* the MIPS_RLD_MAP_REL tag stores the offset to the debug pointer, relative to the address of the tag */ @@ -2019,7 +2025,7 @@ auto ElfFile::parseGnuHashTable(span sectionData) -> Gn } template -void ElfFile::rebuildGnuHashTable(span strTab, span dynsyms) +void ElfFile::rebuildGnuHashTable(span strTab, span dynsyms, span versyms) { auto sectionData = tryGetSectionSpan(".gnu.hash"); if (!sectionData) @@ -2031,12 +2037,20 @@ void ElfFile::rebuildGnuHashTable(span strTab, span(newSection.data(), newSection.size()); + ght = parseGnuHashTable(sectionData); + } + // The hash table includes only a subset of dynsyms auto firstSymIdx = rdi(ght.m_hdr.symndx); dynsyms = span(&dynsyms[firstSymIdx], dynsyms.end()); // Only use the range of symbol versions that will be changed - auto versyms = tryGetSectionSpan(".gnu.version"); + if (!versyms) + versyms = tryGetSectionSpan(".gnu.version"); if (versyms) versyms = span(&versyms[firstSymIdx], versyms.end()); @@ -2098,7 +2112,7 @@ void ElfFile::rebuildGnuHashTable(span strTab, span::rebuildGnuHashTable(span strTab, span::parseHashTable(span sectionData) -> HashT } template -void ElfFile::rebuildHashTable(span strTab, span dynsyms) +void ElfFile::rebuildHashTable(span strTab, span dynsyms, int moreSyms) { auto sectionData = tryGetSectionSpan(".hash"); if (!sectionData) @@ -2159,6 +2173,15 @@ void ElfFile::rebuildHashTable(span strTab, span 0) + { + auto & newSection = replaceSection(".hash", sectionData.size() + moreSyms * sizeof(uint32_t)); + sectionData = span(newSection.data(), newSection.size()); + auto hdr = (typename HashTable::Header*)sectionData.begin(); + wri(hdr->nchain, rdi(hdr->nchain) + moreSyms); + ht = parseHashTable(sectionData); + } + std::fill(ht.m_buckets.begin(), ht.m_buckets.end(), 0); std::fill(ht.m_chain.begin(), ht.m_chain.end(), 0); @@ -2323,6 +2346,281 @@ void ElfFile::modifyExecstack(ExecstackMode op) printf("execstack: %c\n", result); } +template +void ElfFile::remapSymvers(const std::string & mapTo, const std::vector & mapFrom, bool alsoPatchVerNeed) +{ + auto shdrDynStr = findSectionHeader(".dynstr"); + auto shdrDynsym = findSectionHeader(".dynsym"); + auto shdrVersym = findSectionHeader(".gnu.version"); + + auto strTab = (char *)fileContents->data() + rdi(shdrDynStr.sh_offset); + auto strTabSize = rdi(shdrDynStr.sh_size); + auto dynsyms = (Elf_Sym *)(fileContents->data() + rdi(shdrDynsym.sh_offset)); + auto versyms = (Elf_Versym *)(fileContents->data() + rdi(shdrVersym.sh_offset)); + const size_t count = rdi(shdrDynsym.sh_size) / sizeof(Elf_Sym); + + if (count != rdi(shdrVersym.sh_size) / sizeof(Elf_Versym)) + error("versym size mismatch"); + + auto &shdrVerdef = findSectionHeader(".gnu.version_d"); + auto verdef = (char *)(fileContents->data() + rdi(shdrVerdef.sh_offset)); + + std::map verdefMap; + + debug("Parsing .gnu.version_d\n"); + + int verdef_entries = rdi(shdrVerdef.sh_info); + debug(".gnu.version_d: %d entries\n", verdef_entries); + + auto verdef_end = verdef + rdi(shdrVerdef.sh_size); + off_t curoff = 0; + + int map_to_ndx = -1; + int max_ndx = 0; + off_t last_verdef_off = 0; + off_t mapToStrOff = 0; + bool mapToAdded = false; + + for(int i = 0; i < verdef_entries; i++){ + Elf_Verdef *vd = (Elf_Verdef *) (verdef + curoff); + if ((char *)vd + sizeof(Elf_Verdef) > verdef_end) + error(fmt("verdef entry overflow: idx=", i)); + auto ndx = rdi(vd->vd_ndx); + if ((char *)vd + rdi(vd->vd_aux) >= verdef_end) + error(fmt("verdef entry aux out of bounds: idx=", i)); + auto aux = (Elf_Verdaux *) ((char *)vd + rdi(vd->vd_aux)); + if ((char *)aux + sizeof(Elf_Verdaux) > verdef_end) + error(fmt("verdef entry aux overflow: idx=", i)); + std::string_view name = &strTab[rdi(aux->vda_name)]; + debug("verdef entry %d: %s, ndx=%d\n", i, name.data(), ndx); + if (ndx > max_ndx) + max_ndx = ndx; + if (name == mapTo) { + map_to_ndx = ndx; + mapToStrOff = rdi(aux->vda_name); + } + if(ndx != 0 && ndx != 1){ + verdefMap[ndx] = name; + } + + if(rdi(vd->vd_next) == 0){ + if(i == verdef_entries - 1){ + last_verdef_off = curoff; + break; + } + else + error(fmt("verdef entry should have next entry: idx=", i)); + } + if((char *)vd + rdi(vd->vd_next) >= verdef_end) + error(fmt("verdef entry next out of bounds: idx=", i)); + curoff += rdi(vd->vd_next); + } + if (map_to_ndx == -1){ + debug("no version index for %s, adding\n", mapTo.c_str()); + auto & newDynStr = replaceSection(".dynstr", rdi(shdrDynStr.sh_size) + mapTo.size() + 1); + mapToStrOff = rdi(shdrDynStr.sh_size); + setSubstr(newDynStr, mapToStrOff, mapTo + '\0'); + strTab = newDynStr.data(); + strTabSize = newDynStr.size(); + } + debug("parsing verneed entries\n", mapTo.c_str()); + auto verneedhdr = tryFindSectionHeader(".gnu.version_r"); + std::map verneedMap; + if(verneedhdr){ + auto &shdrVerNeed = verneedhdr->get(); + auto verneed = (char *)(fileContents->data() + rdi(shdrVerNeed.sh_offset)); + + debug("found .gnu.version_r, parsing\n"); + int verneed_entries = rdi(shdrVerNeed.sh_info); + debug(".gnu.version_r: %d entries\n", verdef_entries); + + auto verneed_end = verneed + rdi(shdrVerNeed.sh_size); + off_t curoff = 0; + for(int i = 0; i < verneed_entries; i++){ + Elf_Verneed *vn = (Elf_Verneed *) (verneed + curoff); + if ((char *)vn + sizeof(Elf_Verneed) > verneed_end) + error(fmt("verneed entry overflow: idx=", i)); + auto aux_cnt = rdi(vn->vn_cnt); + debug("file: %s, %d versions\n", &strTab[rdi(vn->vn_file)], aux_cnt); + off_t aux_off = rdi(vn->vn_aux); + if ((char *)vn + aux_off >= verneed_end) + error(fmt("verneed entry aux out of bounds: idx=", i)); + for(int j = 0; j < aux_cnt; j++){ + auto aux = (Elf_Vernaux *) ((char *)vn + aux_off); + if ((char *)aux + sizeof(Elf_Vernaux) > verneed_end) + error(fmt("verneed entry aux overflow: idx=", i, "aux idx=", j)); + auto ndx = rdi(aux->vna_other) & VERSYM_VERSION; + debug(" %s, ndx=%d\n", &strTab[rdi(aux->vna_name)], ndx); + if(alsoPatchVerNeed){ + for (auto it : mapFrom){ + if (it == &strTab[rdi(aux->vna_name)]){ + debug(" found %s, changing to %s\n", it.c_str(), mapTo.c_str()); + wri(aux->vna_name, mapToStrOff); + wri(aux->vna_hash, sysvHash(mapTo)); + break; + } + } + } + if(map_to_ndx == -1 && ndx >= max_ndx + 1){ + verneedMap[ndx] = ndx + 1; + ndx = ndx + 1; + wri(aux->vna_other, (rdi(aux->vna_other) & ~VERSYM_VERSION) | (ndx & VERSYM_VERSION)); + debug(" changing ndx to %d\n", ndx); + } + if (rdi(aux->vna_next) == 0){ + if (j == aux_cnt - 1) + break; + else + error(fmt("verneed entry should have next entry: idx=", i, "aux idx=", j)); + } + if ((char *)aux + rdi(aux->vna_next) >= verneed_end) + error(fmt("verneed entry next out of bounds: idx=", i, "aux idx=", j)); + aux_off += rdi(aux->vna_next); + } + if (rdi(vn->vn_next) == 0){ + if (i == verneed_entries - 1) + break; + else + error(fmt("verneed entry should have next entry: idx=", i)); + } + if ((char *)vn + rdi(vn->vn_next) >= verneed_end) + error(fmt("verneed entry next out of bounds: idx=", i)); + curoff += rdi(vn->vn_next); + } + }else{ + debug("no .gnu.version_r found\n"); + } + if (map_to_ndx == -1){ + map_to_ndx = max_ndx + 1; + debug("decided to use %d for %s\n", map_to_ndx, mapTo.c_str()); + if(map_to_ndx > VERSYM_VERSION){ + error(fmt("version index %d is too large", map_to_ndx)); + } + verdefMap[map_to_ndx] = mapTo; + auto & newVerdef = replaceSection(".gnu.version_d", rdi(shdrVerdef.sh_size) + sizeof(Elf_Verdef) + sizeof(Elf_Verdaux)); + char * newVerdefData = newVerdef.data(); + Elf_Verdef *lastVd = (Elf_Verdef *)(newVerdefData + last_verdef_off); + Elf_Verdef *newVd = (Elf_Verdef *)(newVerdefData + rdi(shdrVerdef.sh_size)); + wri(lastVd->vd_next, (char *)newVd - (char *)lastVd); + wri(newVd->vd_version, 1); + wri(newVd->vd_flags, 0); + wri(newVd->vd_ndx, map_to_ndx); + wri(newVd->vd_cnt, 1); + wri(newVd->vd_hash, sysvHash(mapTo)); + wri(newVd->vd_aux, sizeof(Elf_Verdef)); + wri(newVd->vd_next, 0); + Elf_Verdaux *newVda = (Elf_Verdaux *)((char *)newVd + sizeof(Elf_Verdef)); + wri(newVda->vda_next, 0); + wri(((Elf_Shdr *)(&shdrVerdef))->sh_info, rdi(shdrVerdef.sh_info) + 1); + verdef_entries += 1; + + wri(newVda->vda_name, mapToStrOff); + mapToAdded = true; + }else{ + debug("verdef entry for %s found at ndx=%d\n", mapTo.c_str(), map_to_ndx); + } + std::map> symVersionMap; + + debug("Parsing .dynsym\n"); + for(size_t i = 0; i < count; i++){ + auto dynsym = &dynsyms[i]; + std::string name = strTab + rdi(dynsym->st_name); + auto verndx = rdi(versyms[i]); + auto verdef_ndx = verndx & VERSYM_VERSION; + + if(verneedMap.find(verdef_ndx) != verneedMap.end()){ + debug("verneed entry remapping for %s found at ndx=%d\n", name.c_str(), verdef_ndx); + verdef_ndx = verneedMap[verdef_ndx]; + wri(versyms[i], (verndx & ~VERSYM_VERSION) | (verdef_ndx & VERSYM_VERSION)); + } + + if(name.empty()) + continue; + debug("dynsym entry %d: %s ", i, name.c_str()); + auto shndx = rdi(dynsym->st_shndx); + if(shndx == SHN_UNDEF){ + debug("(undefined)\n"); + continue; + }else if(shndx == SHN_ABS){ + debug("(absolute)\n"); + continue; + }else if(shndx == SHN_COMMON){ + debug("(common)\n"); + continue; + } + if(verndx == 0){ + debug("(local)\n"); + continue; + }else if(verndx == 1){ + debug("(global)\n"); + continue; + } + if(verdefMap.find(verdef_ndx) == verdefMap.end()){ + debug("(verdef %d not found)\n", verdef_ndx); + continue; + } + debug("(ver: %s)\n", verdefMap[verdef_ndx].c_str()); + symVersionMap[verdefMap[verdef_ndx]][name] = i; + } + + debug("Generating new dsyms list\n"); + std::map newDsyms; + for(const auto &fromVer : mapFrom){ + if(symVersionMap.find(fromVer) == symVersionMap.end()){ + debug("No symbols with version %s found\n", fromVer.c_str()); + continue; + } + for(auto sym : symVersionMap[fromVer]){ + debug("Adding %s@%s to new dsyms list\n", sym.first.c_str(), fromVer.c_str()); + newDsyms[sym.first] = sym.second; + } + } + for(const auto &syms : symVersionMap[mapTo]){ + debug("removing %s@%s from new dsyms list\n", syms.first.c_str(), mapTo.c_str()); + newDsyms.erase(syms.first); + } + auto newDynsymSize = (newDsyms.size() + (mapToAdded ? 1 : 0)) * sizeof(Elf_Sym) + rdi(shdrDynsym.sh_size); + auto newVersymSize = (newDsyms.size() + (mapToAdded ? 1 : 0)) * sizeof(Elf_Versym) + rdi(shdrVersym.sh_size); + + auto& newDynsym = replaceSection(".dynsym", newDynsymSize); + auto& newVersym = replaceSection(".gnu.version", newVersymSize); + + auto newDynsymSpan = span((Elf_Sym *)newDynsym.data(), newDynsymSize / sizeof(Elf_Sym)); + auto newVersymSpan = span((Elf_Versym *)newVersym.data(), newVersymSize / sizeof(Elf_Versym)); + + { + int i = count; + for(auto it = newDsyms.cbegin(); it != newDsyms.cend(); ++it){ + auto sym = it->second; + debug("Adding %s@%s to dynsym list\n", it->first.c_str(), mapTo.c_str()); + newDynsymSpan[i] = dynsyms[sym]; + bool is_hidden = rdi(newVersymSpan[sym]) & VERSYM_HIDDEN; + wri(newVersymSpan[i], map_to_ndx | (is_hidden ? VERSYM_HIDDEN : 0)); + wri(newVersymSpan[sym], rdi(newVersymSpan[sym]) | VERSYM_HIDDEN); + i += 1; + } + if(mapToAdded){ + debug("Adding %s@%s to dynsym list\n", mapTo.c_str(), mapTo.c_str()); + wri(newDynsymSpan[i].st_name, mapToStrOff); + wri(newDynsymSpan[i].st_info, STB_GLOBAL << 4 | STT_OBJECT); + wri(newDynsymSpan[i].st_other, STV_DEFAULT); + wri(newDynsymSpan[i].st_shndx, SHN_ABS); + wri(newDynsymSpan[i].st_value, 0); + wri(newDynsymSpan[i].st_size, 0); + wri(newVersymSpan[i], map_to_ndx); + } + } + + debug("Rebuilding hash tables\n"); + + rebuildGnuHashTable(span(strTab, strTabSize), newDynsymSpan, newVersymSpan); + rebuildHashTable(span(strTab, strTabSize), newDynsymSpan, newDsyms.size() + (mapToAdded ? 1 : 0)); + + this->rewriteSections(); + + changed = true; +} + template template void ElfFile::forAllStringReferences(const Elf_Shdr& strTabHdr, StrIdxCallback&& fn) @@ -2387,6 +2685,10 @@ static bool noDefaultLib = false; static bool printExecstack = false; static bool clearExecstack = false; static bool setExecstack = false; +static bool remapSymvers = false; +static bool remapVerneed = false; +static std::string symverMapTo; +static std::vector symverMapFrom; template static void patchElf2(ElfFile && elfFile, const FileContents & fileContents, const std::string & fileName) @@ -2444,6 +2746,9 @@ static void patchElf2(ElfFile && elfFile, const FileContents & fileContents, con if (renameDynamicSymbols) elfFile.renameDynamicSymbols(symbolsToRename); + if (remapSymvers) + elfFile.remapSymvers(symverMapTo, symverMapFrom, remapVerneed); + if (elfFile.isChanged()){ writeFile(fileName, elfFile.fileContents); } else if (alwaysWrite) { @@ -2509,6 +2814,8 @@ static void showHelp(const std::string & progName) [--set-execstack]\n\ [--rename-dynamic-symbols NAME_MAP_FILE]\tRenames dynamic symbols. The map file should contain two symbols (old_name new_name) per line\n\ [--no-clobber-old-sections]\t\tDo not clobber old section values - only use when the binary expects to find section info at the old location.\n\ + [--remap-symvers TO=FROM1,FROM2...]\n\ + [--also-remap-verneed]\n\ [--output FILE]\n\ [--debug]\n\ [--version]\n\ @@ -2668,6 +2975,44 @@ static int mainWrapped(int argc, char * * argv) else if (arg == "--no-clobber-old-sections") { clobberOldSections = false; } + else if (arg == "--remap-symvers") { + remapSymvers = true; + if (++i == argc) error("missing argument"); + + const char* mapping = argv[i]; + for(int i = 0; mapping[i]; ++i) + { + if (mapping[i] == '=') + { + char *mapto = strndup(mapping, i); + symverMapTo = mapto; + free(mapto); + mapping += i + 1; + break; + } + } + if (symverMapTo.empty()) + error(fmt("Invalid symver mapping, must contains =: ", mapping)); + for(int i = 0; mapping[i]; ++i) + { + if (mapping[i] == ',') + { + char *mapfrom = strndup(mapping, i); + if(strlen(mapfrom) != 0) + symverMapFrom.push_back(mapfrom); + free(mapfrom); + mapping += i + 1; + i = -1; + } + } + if (strlen(mapping) != 0) + symverMapFrom.push_back(mapping); + if (symverMapFrom.empty()) + error(fmt("Invalid symver mapping, must contains at least one from: ", mapping)); + } + else if (arg == "--also-remap-verneed") { + remapVerneed = true; + } else if (arg == "--help" || arg == "-h" ) { showHelp(argv[0]); return 0; diff --git a/src/patchelf.h b/src/patchelf.h index 4e229d67..2477ba6b 100644 --- a/src/patchelf.h +++ b/src/patchelf.h @@ -175,6 +175,8 @@ class ElfFile void modifyExecstack(ExecstackMode op); + void remapSymvers(const std::string & mapTo, const std::vector & mapFrom, bool alsoRemapVerneed); + private: struct GnuHashTable { using BloomWord = Elf_Addr; @@ -194,8 +196,8 @@ class ElfFile }; HashTable parseHashTable(span gh); - void rebuildGnuHashTable(span strTab, span dynsyms); - void rebuildHashTable(span strTab, span dynsyms); + void rebuildGnuHashTable(span strTab, span dynsyms, span versyms = {nullptr, nullptr}); + void rebuildHashTable(span strTab, span dynsyms, int moreSyms = 0); using Elf_Rel_Info = decltype(Elf_Rel::r_info); @@ -282,7 +284,7 @@ class ElfFile constexpr inline I wri(I & t, U i) const { I val = static_cast(i); - if (static_cast(val) != i) + if (static_cast(val) != i) throw std::runtime_error { "value truncation" }; t = rdi(val); return val;