Skip to content

Commit c55a6ee

Browse files
committed
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.
1 parent b73dbc1 commit c55a6ee

File tree

2 files changed

+90
-7
lines changed

2 files changed

+90
-7
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,4 @@ Makefile
3636
.direnv/
3737
.vscode/
3838
.idea/
39+
result

src/patchelf.cc

+89-7
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,12 @@
3636
#include <cstring>
3737

3838
#include <fcntl.h>
39+
#include <dlfcn.h>
3940
#include <sys/stat.h>
4041
#include <sys/types.h>
4142
#include <unistd.h>
43+
#include <regex>
44+
#include <array>
4245

4346
#include "elf.h"
4447

@@ -93,6 +96,8 @@ class ElfFile
9396

9497
const FileContents fileContents;
9598

99+
const std::string fileName;
100+
96101
private:
97102

98103
std::vector<Elf_Phdr> phdrs;
@@ -118,7 +123,7 @@ class ElfFile
118123
std::vector<SectionName> sectionsByOldIndex;
119124

120125
public:
121-
explicit ElfFile(FileContents fileContents);
126+
explicit ElfFile(FileContents fileContents, std::string fileName);
122127

123128
bool isChanged()
124129
{
@@ -210,6 +215,10 @@ class ElfFile
210215

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

218+
void shrinkWrap(std::map<std::string, std::string> & neededLibsToReplace, std::set<std::string> & neededLibsToAdd);
219+
220+
std::vector<std::string> getNeededLibs();
221+
213222
void printNeededLibs() /* should be const */;
214223

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

383392

384393
template<ElfFileParams>
385-
ElfFile<ElfFileParamNames>::ElfFile(FileContents fContents)
386-
: fileContents(fContents)
394+
ElfFile<ElfFileParamNames>::ElfFile(FileContents fContents, std::string fileName)
395+
: fileContents(fContents), fileName(fileName)
387396
{
388397
/* Check the ELF header for basic validity. */
389398
if (fileContents->size() < (off_t) sizeof(Elf_Ehdr)) error("missing ELF header");
@@ -1745,21 +1754,86 @@ void ElfFile<ElfFileParamNames>::addNeeded(const std::set<std::string> & libs)
17451754
changed = true;
17461755
}
17471756

1757+
// https://stackoverflow.com/a/54738358/143733
1758+
// https://stackoverflow.com/a/478960/143733
1759+
std::pair<std::string, int> exec(const std::string & cmd) {
1760+
std::array<char, 128> buffer;
1761+
std::string result;
1762+
// overwrite the destructor to capture also the return code
1763+
int return_code = -1;
1764+
auto pclose_wrapper = [&return_code](FILE* cmd){ return_code = pclose(cmd); };
1765+
{
1766+
std::unique_ptr<FILE, decltype(pclose_wrapper)> pipe(popen(cmd.c_str(), "r"), pclose_wrapper);
1767+
if (pipe) {
1768+
while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
1769+
result += buffer.data();
1770+
}
1771+
}
1772+
}
1773+
return make_pair(result, return_code);
1774+
}
1775+
17481776
template<ElfFileParams>
1749-
void ElfFile<ElfFileParamNames>::printNeededLibs() // const
1777+
void ElfFile<ElfFileParamNames>::shrinkWrap(std::map<std::string, std::string> & neededLibsToReplace, std::set<std::string> & neededLibsToAdd)
1778+
{
1779+
const std::string interpreter = getInterpreter();
1780+
const std::vector<std::string> needed = getNeededLibs();
1781+
const std::string cmd = fmt(interpreter, " --list ", this->fileName);
1782+
const std::pair<std::string, int> result = exec(cmd);
1783+
if (result.second) {
1784+
error(fmt("ldd failed. ", result.second, "-", result.first));
1785+
}
1786+
std::istringstream iss(result.first);
1787+
std::string line;
1788+
std::regex r("\\s*([^ ]+) => ([^ ]+)");
1789+
while (std::getline(iss, line)) {
1790+
std::smatch matches;
1791+
if (!std::regex_search(line, matches, r)) {
1792+
continue;
1793+
}
1794+
1795+
std::string soname = matches.str(1);
1796+
std::string location = matches.str(2);
1797+
debug("Found %s => %s\n", soname.c_str(), location.c_str());
1798+
1799+
// if the ELF file has this soname, then merely replace it
1800+
if (std::find(needed.begin(), needed.end(), soname) != needed.end()) {
1801+
neededLibsToReplace.insert({soname, location});
1802+
} else {
1803+
neededLibsToAdd.insert(location);
1804+
}
1805+
}
1806+
}
1807+
1808+
template<ElfFileParams>
1809+
std::vector<std::string> ElfFile<ElfFileParamNames>::getNeededLibs() // const
17501810
{
17511811
const auto shdrDynamic = findSection(".dynamic");
17521812
const auto shdrDynStr = findSection(".dynstr");
17531813
const char *strTab = (char *)fileContents->data() + rdi(shdrDynStr.sh_offset);
17541814

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

1817+
std::vector<std::string> results;
1818+
17571819
for (; rdi(dyn->d_tag) != DT_NULL; dyn++) {
17581820
if (rdi(dyn->d_tag) == DT_NEEDED) {
17591821
const char *name = strTab + rdi(dyn->d_un.d_val);
1760-
printf("%s\n", name);
1822+
results.push_back(std::string(name));
17611823
}
17621824
}
1825+
1826+
return results;
1827+
}
1828+
1829+
1830+
template<ElfFileParams>
1831+
void ElfFile<ElfFileParamNames>::printNeededLibs() // const
1832+
{
1833+
const std::vector<std::string> needed = getNeededLibs();
1834+
for (std::string soname : needed) {
1835+
printf("%s\n", soname.c_str());
1836+
}
17631837
}
17641838

17651839

@@ -1832,6 +1906,7 @@ void ElfFile<ElfFileParamNames>::clearSymbolVersions(const std::set<std::string>
18321906

18331907
static bool printInterpreter = false;
18341908
static bool printSoname = false;
1909+
static bool shrinkWrap = false;
18351910
static bool setSoname = false;
18361911
static std::string newSoname;
18371912
static std::string newInterpreter;
@@ -1855,6 +1930,9 @@ static void patchElf2(ElfFile && elfFile, const FileContents & fileContents, con
18551930
if (printInterpreter)
18561931
printf("%s\n", elfFile.getInterpreter().c_str());
18571932

1933+
if (shrinkWrap)
1934+
elfFile.shrinkWrap(neededLibsToReplace, neededLibsToAdd);
1935+
18581936
if (printSoname)
18591937
elfFile.modifySoname(elfFile.printSoname, "");
18601938

@@ -1906,9 +1984,9 @@ static void patchElf()
19061984
const std::string & outputFileName2 = outputFileName.empty() ? fileName : outputFileName;
19071985

19081986
if (getElfType(fileContents).is32Bit)
1909-
patchElf2(ElfFile<Elf32_Ehdr, Elf32_Phdr, Elf32_Shdr, Elf32_Addr, Elf32_Off, Elf32_Dyn, Elf32_Sym, Elf32_Verneed, Elf32_Versym>(fileContents), fileContents, outputFileName2);
1987+
patchElf2(ElfFile<Elf32_Ehdr, Elf32_Phdr, Elf32_Shdr, Elf32_Addr, Elf32_Off, Elf32_Dyn, Elf32_Sym, Elf32_Verneed, Elf32_Versym>(fileContents, fileName), fileContents, outputFileName2);
19101988
else
1911-
patchElf2(ElfFile<Elf64_Ehdr, Elf64_Phdr, Elf64_Shdr, Elf64_Addr, Elf64_Off, Elf64_Dyn, Elf64_Sym, Elf64_Verneed, Elf64_Versym>(fileContents), fileContents, outputFileName2);
1989+
patchElf2(ElfFile<Elf64_Ehdr, Elf64_Phdr, Elf64_Shdr, Elf64_Addr, Elf64_Off, Elf64_Dyn, Elf64_Sym, Elf64_Verneed, Elf64_Versym>(fileContents, fileName), fileContents, outputFileName2);
19121990
}
19131991
}
19141992

@@ -1927,6 +2005,7 @@ void showHelp(const std::string & progName)
19272005
fprintf(stderr, "syntax: %s\n\
19282006
[--set-interpreter FILENAME]\n\
19292007
[--page-size SIZE]\n\
2008+
[--shrink-wrap]\n\
19302009
[--print-interpreter]\n\
19312010
[--print-soname]\t\tPrints 'DT_SONAME' entry of .dynamic section. Raises an error if DT_SONAME doesn't exist\n\
19322011
[--set-soname SONAME]\t\tSets 'DT_SONAME' entry to SONAME.\n\
@@ -1978,6 +2057,9 @@ int mainWrapped(int argc, char * * argv)
19782057
else if (arg == "--print-soname") {
19792058
printSoname = true;
19802059
}
2060+
else if (arg == "--shrink-wrap") {
2061+
shrinkWrap = true;
2062+
}
19812063
else if (arg == "--set-soname") {
19822064
if (++i == argc) error("missing argument");
19832065
setSoname = true;

0 commit comments

Comments
 (0)