From c55a6ee0cf5aaf7c8a1a882a4a89f8ed86e9b863 Mon Sep 17 00:00:00 2001 From: Farid Zakaria Date: Fri, 17 Dec 2021 15:34:35 -0800 Subject: [PATCH] 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;