From c55a6ee0cf5aaf7c8a1a882a4a89f8ed86e9b863 Mon Sep 17 00:00:00 2001 From: Farid Zakaria Date: Fri, 17 Dec 2021 15:34:35 -0800 Subject: [PATCH 1/2] shrinkwrap: introduce new shrinkwrap option Add a new command to patchelf `--shrink-wrap` which shrink-wraps the binary file. patchelf at the moment works by modifying the RUNPATH to help locate files to the store however it only does this generally for it's immediate DT_NEEDED. There can be cases where binaries correctly run but are doing so just by chance that the linker has found the library previously during it's walk. To avoid this, lets pull up all DT_NEEDED to the top-level executable and immortalize them by having their entries point to very specific location. --- .gitignore | 1 + src/patchelf.cc | 96 +++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 90 insertions(+), 7 deletions(-) diff --git a/.gitignore b/.gitignore index 921b9f85..c3f60a5d 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,4 @@ Makefile .direnv/ .vscode/ .idea/ +result diff --git a/src/patchelf.cc b/src/patchelf.cc index eaf2a42f..e4c848d2 100644 --- a/src/patchelf.cc +++ b/src/patchelf.cc @@ -36,9 +36,12 @@ #include #include +#include #include #include #include +#include +#include #include "elf.h" @@ -93,6 +96,8 @@ class ElfFile const FileContents fileContents; + const std::string fileName; + private: std::vector phdrs; @@ -118,7 +123,7 @@ class ElfFile std::vector sectionsByOldIndex; public: - explicit ElfFile(FileContents fileContents); + explicit ElfFile(FileContents fileContents, std::string fileName); bool isChanged() { @@ -210,6 +215,10 @@ class ElfFile void replaceNeeded(const std::map & libs); + void shrinkWrap(std::map & neededLibsToReplace, std::set & neededLibsToAdd); + + std::vector getNeededLibs(); + void printNeededLibs() /* should be const */; void noDefaultLib(); @@ -382,8 +391,8 @@ static void checkPointer(const FileContents & contents, void * p, unsigned int s template -ElfFile::ElfFile(FileContents fContents) - : fileContents(fContents) +ElfFile::ElfFile(FileContents fContents, std::string fileName) + : fileContents(fContents), fileName(fileName) { /* Check the ELF header for basic validity. */ if (fileContents->size() < (off_t) sizeof(Elf_Ehdr)) error("missing ELF header"); @@ -1745,8 +1754,59 @@ void ElfFile::addNeeded(const std::set & libs) changed = true; } +// https://stackoverflow.com/a/54738358/143733 +// https://stackoverflow.com/a/478960/143733 +std::pair exec(const std::string & cmd) { + std::array buffer; + std::string result; + // overwrite the destructor to capture also the return code + int return_code = -1; + auto pclose_wrapper = [&return_code](FILE* cmd){ return_code = pclose(cmd); }; + { + std::unique_ptr pipe(popen(cmd.c_str(), "r"), pclose_wrapper); + if (pipe) { + while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) { + result += buffer.data(); + } + } + } + return make_pair(result, return_code); +} + template -void ElfFile::printNeededLibs() // const +void ElfFile::shrinkWrap(std::map & neededLibsToReplace, std::set & neededLibsToAdd) +{ + const std::string interpreter = getInterpreter(); + const std::vector needed = getNeededLibs(); + const std::string cmd = fmt(interpreter, " --list ", this->fileName); + const std::pair result = exec(cmd); + if (result.second) { + error(fmt("ldd failed. ", result.second, "-", result.first)); + } + std::istringstream iss(result.first); + std::string line; + std::regex r("\\s*([^ ]+) => ([^ ]+)"); + while (std::getline(iss, line)) { + std::smatch matches; + if (!std::regex_search(line, matches, r)) { + continue; + } + + std::string soname = matches.str(1); + std::string location = matches.str(2); + debug("Found %s => %s\n", soname.c_str(), location.c_str()); + + // if the ELF file has this soname, then merely replace it + if (std::find(needed.begin(), needed.end(), soname) != needed.end()) { + neededLibsToReplace.insert({soname, location}); + } else { + neededLibsToAdd.insert(location); + } + } +} + +template +std::vector ElfFile::getNeededLibs() // const { const auto shdrDynamic = findSection(".dynamic"); const auto shdrDynStr = findSection(".dynstr"); @@ -1754,12 +1814,26 @@ void ElfFile::printNeededLibs() // const const Elf_Dyn *dyn = (Elf_Dyn *) (fileContents->data() + rdi(shdrDynamic.sh_offset)); + std::vector results; + for (; rdi(dyn->d_tag) != DT_NULL; dyn++) { if (rdi(dyn->d_tag) == DT_NEEDED) { const char *name = strTab + rdi(dyn->d_un.d_val); - printf("%s\n", name); + results.push_back(std::string(name)); } } + + return results; +} + + +template +void ElfFile::printNeededLibs() // const +{ + const std::vector needed = getNeededLibs(); + for (std::string soname : needed) { + printf("%s\n", soname.c_str()); + } } @@ -1832,6 +1906,7 @@ void ElfFile::clearSymbolVersions(const std::set static bool printInterpreter = false; static bool printSoname = false; +static bool shrinkWrap = false; static bool setSoname = false; static std::string newSoname; static std::string newInterpreter; @@ -1855,6 +1930,9 @@ static void patchElf2(ElfFile && elfFile, const FileContents & fileContents, con if (printInterpreter) printf("%s\n", elfFile.getInterpreter().c_str()); + if (shrinkWrap) + elfFile.shrinkWrap(neededLibsToReplace, neededLibsToAdd); + if (printSoname) elfFile.modifySoname(elfFile.printSoname, ""); @@ -1906,9 +1984,9 @@ static void patchElf() const std::string & outputFileName2 = outputFileName.empty() ? fileName : outputFileName; if (getElfType(fileContents).is32Bit) - patchElf2(ElfFile(fileContents), fileContents, outputFileName2); + patchElf2(ElfFile(fileContents, fileName), fileContents, outputFileName2); else - patchElf2(ElfFile(fileContents), fileContents, outputFileName2); + patchElf2(ElfFile(fileContents, fileName), fileContents, outputFileName2); } } @@ -1927,6 +2005,7 @@ void showHelp(const std::string & progName) fprintf(stderr, "syntax: %s\n\ [--set-interpreter FILENAME]\n\ [--page-size SIZE]\n\ + [--shrink-wrap]\n\ [--print-interpreter]\n\ [--print-soname]\t\tPrints 'DT_SONAME' entry of .dynamic section. Raises an error if DT_SONAME doesn't exist\n\ [--set-soname SONAME]\t\tSets 'DT_SONAME' entry to SONAME.\n\ @@ -1978,6 +2057,9 @@ int mainWrapped(int argc, char * * argv) else if (arg == "--print-soname") { printSoname = true; } + else if (arg == "--shrink-wrap") { + shrinkWrap = true; + } else if (arg == "--set-soname") { if (++i == argc) error("missing argument"); setSoname = true; From fbad2d5b3c976033fd0b0c12e39fddb30df38109 Mon Sep 17 00:00:00 2001 From: Farid Zakaria Date: Tue, 21 Dec 2021 16:54:21 -0800 Subject: [PATCH 2/2] Squashed commit of the following: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit commit c172ce6efb001c5be3f887ccb185211d9f6af69e Merge: b73dbc1 4604393 Author: Jörg Thalheim Date: Tue Dec 21 19:47:20 2021 +0000 Merge pull request #360 from fzakaria/faridzakaria/fix-add-replace Quality of life readability improvements commit 4604393fd680c3d117fb9897d779dbc4919a8853 Author: Farid Zakaria Date: Mon Dec 20 15:04:57 2021 -0800 Renamed findSection2 to tryFindSectionHeader commit 10712373aa6bf832ebac1f99b5c273e3dfa38efc Author: Farid Zakaria Date: Mon Dec 20 14:59:15 2021 -0800 Renamed findSection3 to getSectionIndex commit 05e8f673f7328cea74b937bd0836f1812d98cde7 Author: Farid Zakaria Date: Mon Dec 20 14:54:37 2021 -0800 Added patchelf.h Added a header file to make things easier to navigate. Renamed findSection3 -> getSectionIndex since thats more sensible to read. Renamed findSection -> findSectionHeader to better distinguish sections from headers. --- src/Makefile.am | 2 +- src/patchelf.cc | 261 +++++++++--------------------------------------- src/patchelf.h | 164 ++++++++++++++++++++++++++++++ 3 files changed, 212 insertions(+), 215 deletions(-) create mode 100644 src/patchelf.h diff --git a/src/Makefile.am b/src/Makefile.am index ed7a19b1..fa0a9cc1 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -19,4 +19,4 @@ endif bin_PROGRAMS = patchelf -patchelf_SOURCES = patchelf.cc elf.h +patchelf_SOURCES = patchelf.cc elf.h patchelf.h diff --git a/src/patchelf.cc b/src/patchelf.cc index e4c848d2..6a57daf2 100644 --- a/src/patchelf.cc +++ b/src/patchelf.cc @@ -44,6 +44,7 @@ #include #include "elf.h" +#include "patchelf.h" #ifndef PACKAGE_STRING #define PACKAGE_STRING "patchelf" @@ -66,11 +67,6 @@ static int forcedPageSize = -1; #define EM_LOONGARCH 258 #endif -using FileContents = std::shared_ptr>; - -#define ElfFileParams class Elf_Ehdr, class Elf_Phdr, class Elf_Shdr, class Elf_Addr, class Elf_Off, class Elf_Dyn, class Elf_Sym, class Elf_Verneed, class Elf_Versym -#define ElfFileParamNames Elf_Ehdr, Elf_Phdr, Elf_Shdr, Elf_Addr, Elf_Off, Elf_Dyn, Elf_Sym, Elf_Verneed, Elf_Versym - static std::vector splitColonDelimitedString(const char * s) { @@ -88,169 +84,6 @@ static bool hasAllowedPrefix(const std::string & s, const std::vector -class ElfFile -{ -public: - - const FileContents fileContents; - - const std::string fileName; - -private: - - std::vector phdrs; - std::vector shdrs; - - bool littleEndian; - - bool changed = false; - - bool isExecutable = false; - - using SectionName = std::string; - using ReplacedSections = std::map; - - ReplacedSections replacedSections; - - std::string sectionNames; /* content of the .shstrtab section */ - - /* Align on 4 or 8 bytes boundaries on 32- or 64-bit platforms - respectively. */ - size_t sectionAlignment = sizeof(Elf_Off); - - std::vector sectionsByOldIndex; - -public: - explicit ElfFile(FileContents fileContents, std::string fileName); - - bool isChanged() - { - return changed; - } - -private: - - struct CompPhdr - { - ElfFile * elfFile; - bool operator ()(const Elf_Phdr & x, const Elf_Phdr & y) - { - // A PHDR comes before everything else. - if (elfFile->rdi(y.p_type) == PT_PHDR) return false; - if (elfFile->rdi(x.p_type) == PT_PHDR) return true; - - // Sort non-PHDRs by address. - return elfFile->rdi(x.p_paddr) < elfFile->rdi(y.p_paddr); - } - }; - - friend struct CompPhdr; - - void sortPhdrs(); - - struct CompShdr - { - ElfFile * elfFile; - bool operator ()(const Elf_Shdr & x, const Elf_Shdr & y) - { - return elfFile->rdi(x.sh_offset) < elfFile->rdi(y.sh_offset); - } - }; - - friend struct CompShdr; - - unsigned int getPageSize() const; - - void sortShdrs(); - - void shiftFile(unsigned int extraPages, Elf_Addr startPage); - - std::string getSectionName(const Elf_Shdr & shdr) const; - - Elf_Shdr & findSection(const SectionName & sectionName); - - std::optional> findSection2(const SectionName & sectionName); - - unsigned int findSection3(const SectionName & sectionName); - - std::string & replaceSection(const SectionName & sectionName, - unsigned int size); - - bool haveReplacedSection(const SectionName & sectionName) const; - - void writeReplacedSections(Elf_Off & curOff, - Elf_Addr startAddr, Elf_Off startOffset); - - void rewriteHeaders(Elf_Addr phdrAddress); - - void rewriteSectionsLibrary(); - - void rewriteSectionsExecutable(); - - void normalizeNoteSegments(); - -public: - - void rewriteSections(); - - std::string getInterpreter(); - - typedef enum { printSoname, replaceSoname } sonameMode; - - void modifySoname(sonameMode op, const std::string & newSoname); - - void setInterpreter(const std::string & newInterpreter); - - typedef enum { rpPrint, rpShrink, rpSet, rpAdd, rpRemove } RPathOp; - - void modifyRPath(RPathOp op, const std::vector & allowedRpathPrefixes, std::string newRPath); - std::string shrinkRPath(char* rpath, std::vector &neededLibs, const std::vector & allowedRpathPrefixes); - void removeRPath(Elf_Shdr & shdrDynamic); - - void addNeeded(const std::set & libs); - - void removeNeeded(const std::set & libs); - - void replaceNeeded(const std::map & libs); - - void shrinkWrap(std::map & neededLibsToReplace, std::set & neededLibsToAdd); - - std::vector getNeededLibs(); - - void printNeededLibs() /* should be const */; - - void noDefaultLib(); - - void clearSymbolVersions(const std::set & syms); - -private: - - /* Convert an integer in big or little endian representation (as - specified by the ELF header) to this platform's integer - representation. */ - template - I rdi(I i) const; - - /* Convert back to the ELF representation. */ - template - I wri(I & t, unsigned long long i) const - { - t = rdi((I) i); - return i; - } - - Elf_Ehdr *hdr() { - return (Elf_Ehdr *)fileContents->data(); - } - - const Elf_Ehdr *hdr() const { - return (const Elf_Ehdr *)fileContents->data(); - } -}; - - /* !!! G++ creates broken code if this function is inlined, don't know why... */ template @@ -522,14 +355,14 @@ void ElfFile::sortShdrs() for (unsigned int i = 1; i < rdi(hdr()->e_shnum); ++i) if (rdi(shdrs[i].sh_link) != 0) wri(shdrs[i].sh_link, - findSection3(linkage[getSectionName(shdrs[i])])); + getSectionIndex(linkage[getSectionName(shdrs[i])])); /* And the st_info mappings. */ for (unsigned int i = 1; i < rdi(hdr()->e_shnum); ++i) if (rdi(shdrs.at(i).sh_info) != 0 && (rdi(shdrs.at(i).sh_type) == SHT_REL || rdi(shdrs.at(i).sh_type) == SHT_RELA)) wri(shdrs.at(i).sh_info, - findSection3(info.at(getSectionName(shdrs.at(i))))); + getSectionIndex(info.at(getSectionName(shdrs.at(i))))); /* And the .shstrtab index. Note: the match here is done by checking the offset as searching * by name can yield incorrect results in case there are multiple sections with the same @@ -639,9 +472,9 @@ std::string ElfFile::getSectionName(const Elf_Shdr & shdr) co template -Elf_Shdr & ElfFile::findSection(const SectionName & sectionName) +Elf_Shdr & ElfFile::findSectionHeader(const SectionName & sectionName) { - auto shdr = findSection2(sectionName); + auto shdr = tryFindSectionHeader(sectionName); if (!shdr) { std::string extraMsg; if (sectionName == ".interp" || sectionName == ".dynamic" || sectionName == ".dynstr") @@ -653,9 +486,9 @@ Elf_Shdr & ElfFile::findSection(const SectionName & sectionNa template -std::optional> ElfFile::findSection2(const SectionName & sectionName) +std::optional> ElfFile::tryFindSectionHeader(const SectionName & sectionName) { - auto i = findSection3(sectionName); + auto i = getSectionIndex(sectionName); if (i) return shdrs.at(i); return {}; @@ -663,7 +496,7 @@ std::optional> ElfFile::find template -unsigned int ElfFile::findSection3(const SectionName & sectionName) +unsigned int ElfFile::getSectionIndex(const SectionName & sectionName) { for (unsigned int i = 1; i < rdi(hdr()->e_shnum); ++i) if (getSectionName(shdrs.at(i)) == sectionName) return i; @@ -686,7 +519,7 @@ std::string & ElfFile::replaceSection(const SectionName & sec if (i != replacedSections.end()) { s = std::string(i->second); } else { - auto shdr = findSection(sectionName); + auto shdr = findSectionHeader(sectionName); s = std::string((char *) fileContents->data() + rdi(shdr.sh_offset), rdi(shdr.sh_size)); } @@ -706,7 +539,7 @@ void ElfFile::writeReplacedSections(Elf_Off & curOff, clobbering previously written new section contents. */ for (auto & i : replacedSections) { const std::string & sectionName = i.first; - Elf_Shdr & shdr = findSection(sectionName); + Elf_Shdr & shdr = findSectionHeader(sectionName); if (rdi(shdr.sh_type) != SHT_NOBITS) memset(fileContents->data() + rdi(shdr.sh_offset), 'X', rdi(shdr.sh_size)); } @@ -714,7 +547,7 @@ void ElfFile::writeReplacedSections(Elf_Off & curOff, std::set noted_phdrs = {}; for (auto & i : replacedSections) { const std::string & sectionName = i.first; - auto & shdr = findSection(sectionName); + auto & shdr = findSectionHeader(sectionName); Elf_Shdr orig_shdr = shdr; debug("rewriting section '%s' from offset 0x%x (size %d) to offset 0x%x (size %d)\n", sectionName.c_str(), rdi(shdr.sh_offset), rdi(shdr.sh_size), curOff, i.second.size()); @@ -1033,7 +866,7 @@ void ElfFile::normalizeNoteSegments() /* We don't need to do anything if no note segments were replaced. */ bool replaced_note = std::any_of(replacedSections.begin(), replacedSections.end(), - [this](std::pair & i) { return rdi(findSection(i.first).sh_type) == SHT_NOTE; }); + [this](std::pair & i) { return rdi(findSectionHeader(i.first).sh_type) == SHT_NOTE; }); if (!replaced_note) return; std::vector newPhdrs; @@ -1137,59 +970,59 @@ void ElfFile::rewriteHeaders(Elf_Addr phdrAddress) /* Update all those nasty virtual addresses in the .dynamic section. Note that not all executables have .dynamic sections (e.g., those produced by klibc's klcc). */ - auto shdrDynamic = findSection2(".dynamic"); + auto shdrDynamic = tryFindSectionHeader(".dynamic"); if (shdrDynamic) { auto dyn_table = (Elf_Dyn *) (fileContents->data() + rdi((*shdrDynamic).get().sh_offset)); unsigned int d_tag; for (auto dyn = dyn_table; (d_tag = rdi(dyn->d_tag)) != DT_NULL; dyn++) if (d_tag == DT_STRTAB) - dyn->d_un.d_ptr = findSection(".dynstr").sh_addr; + dyn->d_un.d_ptr = findSectionHeader(".dynstr").sh_addr; else if (d_tag == DT_STRSZ) - dyn->d_un.d_val = findSection(".dynstr").sh_size; + dyn->d_un.d_val = findSectionHeader(".dynstr").sh_size; else if (d_tag == DT_SYMTAB) - dyn->d_un.d_ptr = findSection(".dynsym").sh_addr; + dyn->d_un.d_ptr = findSectionHeader(".dynsym").sh_addr; else if (d_tag == DT_HASH) - dyn->d_un.d_ptr = findSection(".hash").sh_addr; + dyn->d_un.d_ptr = findSectionHeader(".hash").sh_addr; else if (d_tag == DT_GNU_HASH) { - auto shdr = findSection2(".gnu.hash"); + auto shdr = tryFindSectionHeader(".gnu.hash"); // some binaries might this section stripped // in which case we just ignore the value. if (shdr) dyn->d_un.d_ptr = (*shdr).get().sh_addr; } else if (d_tag == DT_JMPREL) { - auto shdr = findSection2(".rel.plt"); - if (!shdr) shdr = findSection2(".rela.plt"); + auto shdr = tryFindSectionHeader(".rel.plt"); + if (!shdr) shdr = tryFindSectionHeader(".rela.plt"); /* 64-bit Linux, x86-64 */ - if (!shdr) shdr = findSection2(".rela.IA_64.pltoff"); /* 64-bit Linux, IA-64 */ + if (!shdr) shdr = tryFindSectionHeader(".rela.IA_64.pltoff"); /* 64-bit Linux, IA-64 */ if (!shdr) error("cannot find section corresponding to DT_JMPREL"); dyn->d_un.d_ptr = (*shdr).get().sh_addr; } else if (d_tag == DT_REL) { /* !!! hack! */ - auto shdr = findSection2(".rel.dyn"); + auto shdr = tryFindSectionHeader(".rel.dyn"); /* no idea if this makes sense, but it was needed for some program */ - if (!shdr) shdr = findSection2(".rel.got"); + if (!shdr) shdr = tryFindSectionHeader(".rel.got"); /* some programs have neither section, but this doesn't seem to be a problem */ if (!shdr) continue; dyn->d_un.d_ptr = (*shdr).get().sh_addr; } else if (d_tag == DT_RELA) { - auto shdr = findSection2(".rela.dyn"); + auto shdr = tryFindSectionHeader(".rela.dyn"); /* some programs lack this section, but it doesn't seem to be a problem */ if (!shdr) continue; dyn->d_un.d_ptr = (*shdr).get().sh_addr; } else if (d_tag == DT_VERNEED) - dyn->d_un.d_ptr = findSection(".gnu.version_r").sh_addr; + dyn->d_un.d_ptr = findSectionHeader(".gnu.version_r").sh_addr; else if (d_tag == DT_VERSYM) - dyn->d_un.d_ptr = findSection(".gnu.version").sh_addr; + dyn->d_un.d_ptr = findSectionHeader(".gnu.version").sh_addr; 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 */ - auto shdr = findSection2(".rld_map"); + auto shdr = tryFindSectionHeader(".rld_map"); if (shdr) { - auto rld_map_addr = findSection(".rld_map").sh_addr; + auto rld_map_addr = findSectionHeader(".rld_map").sh_addr; auto dyn_offset = ((char*)dyn) - ((char*)dyn_table); dyn->d_un.d_ptr = rld_map_addr + dyn_offset - (*shdrDynamic).get().sh_addr; } else { @@ -1221,7 +1054,7 @@ void ElfFile::rewriteHeaders(Elf_Addr phdrAddress) } const std::string & section = sectionsByOldIndex.at(shndx); assert(!section.empty()); - auto newIndex = findSection3(section); // inefficient + auto newIndex = getSectionIndex(section); // inefficient //debug("rewriting symbol %d: index = %d (%s) -> %d\n", entry, shndx, section.c_str(), newIndex); wri(sym->st_shndx, newIndex); /* Rewrite st_value. FIXME: we should do this for all @@ -1245,7 +1078,7 @@ static void setSubstr(std::string & s, unsigned int pos, const std::string & t) template std::string ElfFile::getInterpreter() { - auto shdr = findSection(".interp"); + auto shdr = findSectionHeader(".interp"); return std::string((char *) fileContents->data() + rdi(shdr.sh_offset), rdi(shdr.sh_size) - 1); } @@ -1257,8 +1090,8 @@ void ElfFile::modifySoname(sonameMode op, const std::string & return; } - auto shdrDynamic = findSection(".dynamic"); - auto shdrDynStr = findSection(".dynstr"); + auto shdrDynamic = findSectionHeader(".dynamic"); + auto shdrDynStr = findSectionHeader(".dynstr"); char * strTab = (char *) fileContents->data() + rdi(shdrDynStr.sh_offset); /* Walk through the dynamic section, look for the DT_SONAME entry. */ @@ -1410,7 +1243,7 @@ template void ElfFile::modifyRPath(RPathOp op, const std::vector & allowedRpathPrefixes, std::string newRPath) { - auto shdrDynamic = findSection(".dynamic"); + auto shdrDynamic = findSectionHeader(".dynamic"); if (rdi(shdrDynamic.sh_type) == SHT_NOBITS) { debug("no dynamic section\n"); @@ -1419,7 +1252,7 @@ void ElfFile::modifyRPath(RPathOp op, /* !!! We assume that the virtual address in the DT_STRTAB entry of the dynamic section corresponds to the .dynstr section. */ - auto shdrDynStr = findSection(".dynstr"); + auto shdrDynStr = findSectionHeader(".dynstr"); char * strTab = (char *) fileContents->data() + rdi(shdrDynStr.sh_offset); @@ -1558,8 +1391,8 @@ void ElfFile::removeNeeded(const std::set & libs { if (libs.empty()) return; - auto shdrDynamic = findSection(".dynamic"); - auto shdrDynStr = findSection(".dynstr"); + auto shdrDynamic = findSectionHeader(".dynamic"); + auto shdrDynStr = findSectionHeader(".dynstr"); char * strTab = (char *) fileContents->data() + rdi(shdrDynStr.sh_offset); auto dyn = (Elf_Dyn *)(fileContents->data() + rdi(shdrDynamic.sh_offset)); @@ -1586,8 +1419,8 @@ void ElfFile::replaceNeeded(const std::mapdata() + rdi(shdrDynStr.sh_offset); auto dyn = (Elf_Dyn *)(fileContents->data() + rdi(shdrDynamic.sh_offset)); @@ -1643,7 +1476,7 @@ void ElfFile::replaceNeeded(const std::mapsh_link to figure out @@ -1709,8 +1542,8 @@ void ElfFile::addNeeded(const std::set & libs) { if (libs.empty()) return; - auto shdrDynamic = findSection(".dynamic"); - auto shdrDynStr = findSection(".dynstr"); + auto shdrDynamic = findSectionHeader(".dynamic"); + auto shdrDynStr = findSectionHeader(".dynstr"); unsigned int length = 0; @@ -1808,8 +1641,8 @@ void ElfFile::shrinkWrap(std::map & template std::vector ElfFile::getNeededLibs() // const { - const auto shdrDynamic = findSection(".dynamic"); - const auto shdrDynStr = findSection(".dynstr"); + const auto shdrDynamic = findSectionHeader(".dynamic"); + const auto shdrDynStr = findSectionHeader(".dynstr"); const char *strTab = (char *)fileContents->data() + rdi(shdrDynStr.sh_offset); const Elf_Dyn *dyn = (Elf_Dyn *) (fileContents->data() + rdi(shdrDynamic.sh_offset)); @@ -1840,7 +1673,7 @@ void ElfFile::printNeededLibs() // const template void ElfFile::noDefaultLib() { - auto shdrDynamic = findSection(".dynamic"); + auto shdrDynamic = findSectionHeader(".dynamic"); auto dyn = (Elf_Dyn *)(fileContents->data() + rdi(shdrDynamic.sh_offset)); auto dynFlags1 = (Elf_Dyn *)nullptr; @@ -1881,9 +1714,9 @@ void ElfFile::clearSymbolVersions(const std::set { if (syms.empty()) return; - auto shdrDynStr = findSection(".dynstr"); - auto shdrDynsym = findSection(".dynsym"); - auto shdrVersym = findSection(".gnu.version"); + auto shdrDynStr = findSectionHeader(".dynstr"); + auto shdrDynsym = findSectionHeader(".dynsym"); + auto shdrVersym = findSectionHeader(".gnu.version"); auto strTab = (char *)fileContents->data() + rdi(shdrDynStr.sh_offset); auto dynsyms = (Elf_Sym *)(fileContents->data() + rdi(shdrDynsym.sh_offset)); diff --git a/src/patchelf.h b/src/patchelf.h new file mode 100644 index 00000000..57e84611 --- /dev/null +++ b/src/patchelf.h @@ -0,0 +1,164 @@ +using FileContents = std::shared_ptr>; + +#define ElfFileParams class Elf_Ehdr, class Elf_Phdr, class Elf_Shdr, class Elf_Addr, class Elf_Off, class Elf_Dyn, class Elf_Sym, class Elf_Verneed, class Elf_Versym +#define ElfFileParamNames Elf_Ehdr, Elf_Phdr, Elf_Shdr, Elf_Addr, Elf_Off, Elf_Dyn, Elf_Sym, Elf_Verneed, Elf_Versym + +template +class ElfFile +{ +public: + + const FileContents fileContents; + const std::string fileName; + +private: + + std::vector phdrs; + std::vector shdrs; + + bool littleEndian; + + bool changed = false; + + bool isExecutable = false; + + using SectionName = std::string; + using ReplacedSections = std::map; + + ReplacedSections replacedSections; + + std::string sectionNames; /* content of the .shstrtab section */ + + /* Align on 4 or 8 bytes boundaries on 32- or 64-bit platforms + respectively. */ + size_t sectionAlignment = sizeof(Elf_Off); + + std::vector sectionsByOldIndex; + +public: + explicit ElfFile(FileContents fileContents, std::string filename); + + bool isChanged() + { + return changed; + } + +private: + + struct CompPhdr + { + ElfFile * elfFile; + bool operator ()(const Elf_Phdr & x, const Elf_Phdr & y) + { + // A PHDR comes before everything else. + if (elfFile->rdi(y.p_type) == PT_PHDR) return false; + if (elfFile->rdi(x.p_type) == PT_PHDR) return true; + + // Sort non-PHDRs by address. + return elfFile->rdi(x.p_paddr) < elfFile->rdi(y.p_paddr); + } + }; + + friend struct CompPhdr; + + void sortPhdrs(); + + struct CompShdr + { + ElfFile * elfFile; + bool operator ()(const Elf_Shdr & x, const Elf_Shdr & y) + { + return elfFile->rdi(x.sh_offset) < elfFile->rdi(y.sh_offset); + } + }; + + friend struct CompShdr; + + unsigned int getPageSize() const; + + void sortShdrs(); + + void shiftFile(unsigned int extraPages, Elf_Addr startPage); + + std::string getSectionName(const Elf_Shdr & shdr) const; + + Elf_Shdr & findSectionHeader(const SectionName & sectionName); + + std::optional> tryFindSectionHeader(const SectionName & sectionName); + + unsigned int getSectionIndex(const SectionName & sectionName); + + std::string & replaceSection(const SectionName & sectionName, + unsigned int size); + + bool haveReplacedSection(const SectionName & sectionName) const; + + void writeReplacedSections(Elf_Off & curOff, + Elf_Addr startAddr, Elf_Off startOffset); + + void rewriteHeaders(Elf_Addr phdrAddress); + + void rewriteSectionsLibrary(); + + void rewriteSectionsExecutable(); + + void normalizeNoteSegments(); + +public: + + void rewriteSections(); + + std::string getInterpreter(); + + typedef enum { printSoname, replaceSoname } sonameMode; + + void modifySoname(sonameMode op, const std::string & newSoname); + + void setInterpreter(const std::string & newInterpreter); + + typedef enum { rpPrint, rpShrink, rpSet, rpAdd, rpRemove } RPathOp; + + void modifyRPath(RPathOp op, const std::vector & allowedRpathPrefixes, std::string newRPath); + std::string shrinkRPath(char* rpath, std::vector &neededLibs, const std::vector & allowedRpathPrefixes); + void removeRPath(Elf_Shdr & shdrDynamic); + + void addNeeded(const std::set & libs); + + void removeNeeded(const std::set & libs); + + void replaceNeeded(const std::map & libs); + + void shrinkWrap(std::map & neededLibsToReplace, std::set & neededLibsToAdd); + + std::vector getNeededLibs(); + + void printNeededLibs() /* should be const */; + + void noDefaultLib(); + + void clearSymbolVersions(const std::set & syms); + +private: + + /* Convert an integer in big or little endian representation (as + specified by the ELF header) to this platform's integer + representation. */ + template + I rdi(I i) const; + + /* Convert back to the ELF representation. */ + template + I wri(I & t, unsigned long long i) const + { + t = rdi((I) i); + return i; + } + + Elf_Ehdr *hdr() { + return (Elf_Ehdr *)fileContents->data(); + } + + const Elf_Ehdr *hdr() const { + return (const Elf_Ehdr *)fileContents->data(); + } +};