Skip to content

Commit

Permalink
shrinkwrap: introduce new shrinkwrap option
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
fzakaria committed Dec 18, 2021
1 parent fbf108f commit 89b7252
Show file tree
Hide file tree
Showing 2 changed files with 90 additions and 8 deletions.
5 changes: 5 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,8 @@ Makefile
/tests/libbig-dynstr.debug
/tests/contiguous-note-sections
/tests/simple-pie

.direnv/
result
.idea/
.vscode/
93 changes: 85 additions & 8 deletions src/patchelf.cc
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,12 @@
#include <cstring>

#include <fcntl.h>
#include <dlfcn.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include <regex>
#include <array>

#include "elf.h"

Expand Down Expand Up @@ -93,6 +96,8 @@ class ElfFile

const FileContents fileContents;

const std::string fileName;

private:

std::vector<Elf_Phdr> phdrs;
Expand All @@ -118,7 +123,7 @@ class ElfFile
std::vector<SectionName> sectionsByOldIndex;

public:
explicit ElfFile(FileContents fileContents);
explicit ElfFile(FileContents fileContents, std::string fileName);

bool isChanged()
{
Expand Down Expand Up @@ -210,6 +215,10 @@ class ElfFile

void replaceNeeded(const std::map<std::string, std::string> & libs);

void shrinkWrap(std::map<std::string, std::string> & neededLibsToReplace, std::set<std::string> & neededLibsToAdd);

std::vector<std::string> getNeededLibs();

void printNeededLibs() /* should be const */;

void noDefaultLib();
Expand Down Expand Up @@ -382,8 +391,8 @@ static void checkPointer(const FileContents & contents, void * p, unsigned int s


template<ElfFileParams>
ElfFile<ElfFileParamNames>::ElfFile(FileContents fContents)
: fileContents(fContents)
ElfFile<ElfFileParamNames>::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");
Expand Down Expand Up @@ -1237,7 +1246,7 @@ template<ElfFileParams>
std::string ElfFile<ElfFileParamNames>::getInterpreter()
{
auto shdr = findSection(".interp");
return std::string((char *) fileContents->data() + rdi(shdr.sh_offset), rdi(shdr.sh_size));
return std::string((char *) fileContents->data() + rdi(shdr.sh_offset), rdi(shdr.sh_size) - 1);
}

template<ElfFileParams>
Expand Down Expand Up @@ -1745,21 +1754,81 @@ void ElfFile<ElfFileParamNames>::addNeeded(const std::set<std::string> & libs)
changed = true;
}

// https://stackoverflow.com/a/478960/143733
std::string exec(const char* cmd) {
std::array<char, 128> buffer;
std::string result;
std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd, "r"), pclose);
if (!pipe) {
throw std::runtime_error("popen() failed!");
}
while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
result += buffer.data();
}
return result;
}

template<ElfFileParams>
void ElfFile<ElfFileParamNames>::printNeededLibs() // const
void ElfFile<ElfFileParamNames>::shrinkWrap(std::map<std::string, std::string> & neededLibsToReplace, std::set<std::string> & neededLibsToAdd)
{
const std::string interpreter = getInterpreter();
const std::vector<std::string> needed = getNeededLibs();
std::stringstream ss;
ss << interpreter << " --list " << this->fileName;
const std::string cmd = ss.str();
const std::string lddOut = exec(cmd.c_str());

std::istringstream iss(lddOut);
std::string line;
while (std::getline(iss, line)) {
std::regex r("\\s*([^ ]+) => ([^ ]+)");
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<ElfFileParams>
std::vector<std::string> ElfFile<ElfFileParamNames>::getNeededLibs() // const
{
const auto shdrDynamic = findSection(".dynamic");
const auto shdrDynStr = findSection(".dynstr");
const char *strTab = (char *)fileContents->data() + rdi(shdrDynStr.sh_offset);

const Elf_Dyn *dyn = (Elf_Dyn *) (fileContents->data() + rdi(shdrDynamic.sh_offset));

std::vector<std::string> 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<ElfFileParams>
void ElfFile<ElfFileParamNames>::printNeededLibs() // const
{
const std::vector<std::string> needed = getNeededLibs();
for (std::string soname : needed) {
printf("%s\n", soname.c_str());
}
}


Expand Down Expand Up @@ -1832,6 +1901,7 @@ void ElfFile<ElfFileParamNames>::clearSymbolVersions(const std::set<std::string>

static bool printInterpreter = false;
static bool printSoname = false;
static bool shrinkWrap = false;
static bool setSoname = false;
static std::string newSoname;
static std::string newInterpreter;
Expand All @@ -1855,6 +1925,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, "");

Expand Down Expand Up @@ -1906,9 +1979,9 @@ static void patchElf()
const std::string & outputFileName2 = outputFileName.empty() ? fileName : outputFileName;

if (getElfType(fileContents).is32Bit)
patchElf2(ElfFile<Elf32_Ehdr, Elf32_Phdr, Elf32_Shdr, Elf32_Addr, Elf32_Off, Elf32_Dyn, Elf32_Sym, Elf32_Verneed, Elf32_Versym>(fileContents), fileContents, outputFileName2);
patchElf2(ElfFile<Elf32_Ehdr, Elf32_Phdr, Elf32_Shdr, Elf32_Addr, Elf32_Off, Elf32_Dyn, Elf32_Sym, Elf32_Verneed, Elf32_Versym>(fileContents, fileName), fileContents, outputFileName2);
else
patchElf2(ElfFile<Elf64_Ehdr, Elf64_Phdr, Elf64_Shdr, Elf64_Addr, Elf64_Off, Elf64_Dyn, Elf64_Sym, Elf64_Verneed, Elf64_Versym>(fileContents), fileContents, outputFileName2);
patchElf2(ElfFile<Elf64_Ehdr, Elf64_Phdr, Elf64_Shdr, Elf64_Addr, Elf64_Off, Elf64_Dyn, Elf64_Sym, Elf64_Verneed, Elf64_Versym>(fileContents, fileName), fileContents, outputFileName2);
}
}

Expand All @@ -1927,6 +2000,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\
Expand Down Expand Up @@ -1978,6 +2052,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;
Expand Down

0 comments on commit 89b7252

Please sign in to comment.