Skip to content

Commit 89b7252

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 fbf108f commit 89b7252

File tree

2 files changed

+90
-8
lines changed

2 files changed

+90
-8
lines changed

.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,8 @@ Makefile
3232
/tests/libbig-dynstr.debug
3333
/tests/contiguous-note-sections
3434
/tests/simple-pie
35+
36+
.direnv/
37+
result
38+
.idea/
39+
.vscode/

src/patchelf.cc

+85-8
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");
@@ -1237,7 +1246,7 @@ template<ElfFileParams>
12371246
std::string ElfFile<ElfFileParamNames>::getInterpreter()
12381247
{
12391248
auto shdr = findSection(".interp");
1240-
return std::string((char *) fileContents->data() + rdi(shdr.sh_offset), rdi(shdr.sh_size));
1249+
return std::string((char *) fileContents->data() + rdi(shdr.sh_offset), rdi(shdr.sh_size) - 1);
12411250
}
12421251

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

1757+
// https://stackoverflow.com/a/478960/143733
1758+
std::string exec(const char* cmd) {
1759+
std::array<char, 128> buffer;
1760+
std::string result;
1761+
std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd, "r"), pclose);
1762+
if (!pipe) {
1763+
throw std::runtime_error("popen() failed!");
1764+
}
1765+
while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
1766+
result += buffer.data();
1767+
}
1768+
return result;
1769+
}
1770+
17481771
template<ElfFileParams>
1749-
void ElfFile<ElfFileParamNames>::printNeededLibs() // const
1772+
void ElfFile<ElfFileParamNames>::shrinkWrap(std::map<std::string, std::string> & neededLibsToReplace, std::set<std::string> & neededLibsToAdd)
1773+
{
1774+
const std::string interpreter = getInterpreter();
1775+
const std::vector<std::string> needed = getNeededLibs();
1776+
std::stringstream ss;
1777+
ss << interpreter << " --list " << this->fileName;
1778+
const std::string cmd = ss.str();
1779+
const std::string lddOut = exec(cmd.c_str());
1780+
1781+
std::istringstream iss(lddOut);
1782+
std::string line;
1783+
while (std::getline(iss, line)) {
1784+
std::regex r("\\s*([^ ]+) => ([^ ]+)");
1785+
std::smatch matches;
1786+
if (!std::regex_search(line, matches, r)) {
1787+
continue;
1788+
}
1789+
1790+
std::string soname = matches.str(1);
1791+
std::string location = matches.str(2);
1792+
debug("Found %s => %s\n", soname.c_str(), location.c_str());
1793+
1794+
// if the ELF file has this soname, then merely replace it
1795+
if (std::find(needed.begin(), needed.end(), soname) != needed.end()) {
1796+
neededLibsToReplace.insert({soname, location});
1797+
} else {
1798+
neededLibsToAdd.insert(location);
1799+
}
1800+
}
1801+
}
1802+
1803+
template<ElfFileParams>
1804+
std::vector<std::string> ElfFile<ElfFileParamNames>::getNeededLibs() // const
17501805
{
17511806
const auto shdrDynamic = findSection(".dynamic");
17521807
const auto shdrDynStr = findSection(".dynstr");
17531808
const char *strTab = (char *)fileContents->data() + rdi(shdrDynStr.sh_offset);
17541809

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

1812+
std::vector<std::string> results;
1813+
17571814
for (; rdi(dyn->d_tag) != DT_NULL; dyn++) {
17581815
if (rdi(dyn->d_tag) == DT_NEEDED) {
17591816
const char *name = strTab + rdi(dyn->d_un.d_val);
1760-
printf("%s\n", name);
1817+
results.push_back(std::string(name));
17611818
}
17621819
}
1820+
1821+
return results;
1822+
}
1823+
1824+
1825+
template<ElfFileParams>
1826+
void ElfFile<ElfFileParamNames>::printNeededLibs() // const
1827+
{
1828+
const std::vector<std::string> needed = getNeededLibs();
1829+
for (std::string soname : needed) {
1830+
printf("%s\n", soname.c_str());
1831+
}
17631832
}
17641833

17651834

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

18331902
static bool printInterpreter = false;
18341903
static bool printSoname = false;
1904+
static bool shrinkWrap = false;
18351905
static bool setSoname = false;
18361906
static std::string newSoname;
18371907
static std::string newInterpreter;
@@ -1855,6 +1925,9 @@ static void patchElf2(ElfFile && elfFile, const FileContents & fileContents, con
18551925
if (printInterpreter)
18561926
printf("%s\n", elfFile.getInterpreter().c_str());
18571927

1928+
if (shrinkWrap)
1929+
elfFile.shrinkWrap(neededLibsToReplace, neededLibsToAdd);
1930+
18581931
if (printSoname)
18591932
elfFile.modifySoname(elfFile.printSoname, "");
18601933

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

19081981
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);
1982+
patchElf2(ElfFile<Elf32_Ehdr, Elf32_Phdr, Elf32_Shdr, Elf32_Addr, Elf32_Off, Elf32_Dyn, Elf32_Sym, Elf32_Verneed, Elf32_Versym>(fileContents, fileName), fileContents, outputFileName2);
19101983
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);
1984+
patchElf2(ElfFile<Elf64_Ehdr, Elf64_Phdr, Elf64_Shdr, Elf64_Addr, Elf64_Off, Elf64_Dyn, Elf64_Sym, Elf64_Verneed, Elf64_Versym>(fileContents, fileName), fileContents, outputFileName2);
19121985
}
19131986
}
19141987

@@ -1927,6 +2000,7 @@ void showHelp(const std::string & progName)
19272000
fprintf(stderr, "syntax: %s\n\
19282001
[--set-interpreter FILENAME]\n\
19292002
[--page-size SIZE]\n\
2003+
[--shrink-wrap]\n\
19302004
[--print-interpreter]\n\
19312005
[--print-soname]\t\tPrints 'DT_SONAME' entry of .dynamic section. Raises an error if DT_SONAME doesn't exist\n\
19322006
[--set-soname SONAME]\t\tSets 'DT_SONAME' entry to SONAME.\n\
@@ -1978,6 +2052,9 @@ int mainWrapped(int argc, char * * argv)
19782052
else if (arg == "--print-soname") {
19792053
printSoname = true;
19802054
}
2055+
else if (arg == "--shrink-wrap") {
2056+
shrinkWrap = true;
2057+
}
19812058
else if (arg == "--set-soname") {
19822059
if (++i == argc) error("missing argument");
19832060
setSoname = true;

0 commit comments

Comments
 (0)