Skip to content

Commit 3d20df9

Browse files
committed
Add option to make the rpath relative under a specified root directory
NixOS/patchelf#118
1 parent 523f401 commit 3d20df9

File tree

3 files changed

+221
-27
lines changed

3 files changed

+221
-27
lines changed

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,24 @@ libraries. In particular, it can do the following:
3535
$ patchelf --shrink-rpath --allowed-rpath-prefixes /usr/lib:/foo/lib my-program
3636
```
3737

38+
* Sanitize and make the RPATH relative to a specified root directory:
39+
40+
```console
41+
$ patchelf --make-rpath-relative rootdir my-program
42+
```
43+
44+
This removes from the RPATH all directories, which are not under the
45+
specified rootdir. `$ORIGIN` and directories already relative to
46+
rootdir will be resolved first. If the option `--no-standard-lib` is
47+
given, `rootdir/lib` and `rootdir/usr/lib` directories are discarded
48+
as well. If the option `--relative-to-file` is given, the RPATH will
49+
start with `$ORIGIN` making it relative to the ELF file, otherwise
50+
an absolute path relative to ROOTDIR will be used. Furthermore, all
51+
directories that do not contain a library referenced by DT_NEEDED
52+
fields of the executable or library will be removed. Finally, the
53+
directory path is converted to a path relative to rootdir starting
54+
with `$ORIGIN`.
55+
3856
* Remove declared dependencies on dynamic libraries (`DT_NEEDED`
3957
entries):
4058

src/patchelf.cc

Lines changed: 196 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,10 @@ static bool debugMode = false;
6060
static bool forceRPath = false;
6161
static bool clobberOldSections = true;
6262

63+
static bool noStandardLibDirs = false;
64+
65+
static bool relativeToFile = false;
66+
6367
static std::vector<std::string> fileNames;
6468
static std::string outputFileName;
6569
static bool alwaysWrite = false;
@@ -264,6 +268,49 @@ static std::string extractString(const FileContents & contents, size_t offset, s
264268
return { reinterpret_cast<const char *>(contents->data()) + offset, size };
265269
}
266270

271+
static bool absolutePathExists(const std::string & path, std::string & canonicalPath)
272+
{
273+
char *cpath = realpath(path.c_str(), NULL);
274+
if (cpath) {
275+
canonicalPath = cpath;
276+
free(cpath);
277+
return true;
278+
} else {
279+
return false;
280+
}
281+
}
282+
283+
static std::string makePathRelative(const std::string & path,
284+
const std::string & refPath)
285+
{
286+
std::string relPath = "$ORIGIN";
287+
std::string p = path, refP = refPath;
288+
std::size_t pos;
289+
290+
/* Strip the common part of path and refPath */
291+
while (true) {
292+
pos = p.find_first_of('/', 1);
293+
if (refP.find_first_of('/', 1) != pos)
294+
break;
295+
if (p.substr(0, pos) != refP.substr(0, pos))
296+
break;
297+
if (pos == std::string::npos)
298+
break;
299+
p = p.substr(pos);
300+
refP = refP.substr(pos);
301+
}
302+
/* Check if both pathes are equal */
303+
if (p != refP) {
304+
pos = 0;
305+
while (pos != std::string::npos) {
306+
pos =refP.find_first_of('/', pos + 1);
307+
relPath.append("/..");
308+
}
309+
relPath.append(p);
310+
}
311+
312+
return relPath;
313+
}
267314

268315
template<ElfFileParams>
269316
ElfFile<ElfFileParamNames>::ElfFile(FileContents fContents)
@@ -1524,6 +1571,30 @@ static void appendRPath(std::string & rpath, const std::string & path)
15241571
rpath += path;
15251572
}
15261573

1574+
template<ElfFileParams>
1575+
bool ElfFile<ElfFileParamNames>::libFoundInRPath(const std::string & dirName,
1576+
const std::vector<std::string> neededLibs, std::vector<bool> & neededLibFound)
1577+
{
1578+
/* For each library that we haven't found yet, see if it
1579+
exists in this directory. */
1580+
bool libFound = false;
1581+
for (unsigned int j = 0; j < neededLibs.size(); ++j)
1582+
if (!neededLibFound.at(j)) {
1583+
std::string libName = dirName + "/" + neededLibs.at(j);
1584+
try {
1585+
Elf32_Half library_e_machine = getElfType(readFile(libName, sizeof(Elf32_Ehdr))).machine;
1586+
if (rdi(library_e_machine) == rdi(hdr()->e_machine)) {
1587+
neededLibFound.at(j) = true;
1588+
libFound = true;
1589+
} else
1590+
debug("ignoring library '%s' because its machine type differs\n", libName.c_str());
1591+
} catch (SysError & e) {
1592+
if (e.errNo != ENOENT) throw;
1593+
}
1594+
}
1595+
return libFound;
1596+
}
1597+
15271598
/* For each directory in the RPATH, check if it contains any
15281599
needed library. */
15291600
template<ElfFileParams>
@@ -1548,25 +1619,7 @@ std::string ElfFile<ElfFileParamNames>::shrinkRPath(char* rpath, std::vector<std
15481619
continue;
15491620
}
15501621

1551-
/* For each library that we haven't found yet, see if it
1552-
exists in this directory. */
1553-
bool libFound = false;
1554-
for (unsigned int j = 0; j < neededLibs.size(); ++j)
1555-
if (!neededLibFound.at(j)) {
1556-
std::string libName = dirName + "/" + neededLibs.at(j);
1557-
try {
1558-
Elf32_Half library_e_machine = getElfType(readFile(libName, sizeof(Elf32_Ehdr))).machine;
1559-
if (rdi(library_e_machine) == rdi(hdr()->e_machine)) {
1560-
neededLibFound.at(j) = true;
1561-
libFound = true;
1562-
} else
1563-
debug("ignoring library '%s' because its machine type differs\n", libName.c_str());
1564-
} catch (SysError & e) {
1565-
if (e.errNo != ENOENT) throw;
1566-
}
1567-
}
1568-
1569-
if (!libFound)
1622+
if (!libFoundInRPath(dirName, neededLibs, neededLibFound))
15701623
debug("removing directory '%s' from RPATH\n", dirName.c_str());
15711624
else
15721625
appendRPath(newRPath, dirName);
@@ -1575,6 +1628,86 @@ std::string ElfFile<ElfFileParamNames>::shrinkRPath(char* rpath, std::vector<std
15751628
return newRPath;
15761629
}
15771630

1631+
/* Make the the RPATH relative to the specified path */
1632+
template<ElfFileParams>
1633+
std::string ElfFile<ElfFileParamNames>::makeRelativeRPath(char* rpath, std::vector<std::string> &neededLibs, const std::string & rootDir, const std::string & fileName) {
1634+
std::vector<bool> neededLibFound(neededLibs.size(), false);
1635+
std::string fileDir = fileName.substr(0, fileName.find_last_of("/"));
1636+
std::string newRPath = "";
1637+
1638+
debug("makeRelativeRPath: fileName = '%s'\n", fileName.c_str());
1639+
1640+
for (auto & dirName : splitColonDelimitedString(rpath)) {
1641+
std::string canonicalPath;
1642+
std::string path;
1643+
1644+
debug("makeRelativeRPath: dirName = '%s'\n", dirName.c_str());
1645+
1646+
/* Figure out if we should keep or discard the path; there are several
1647+
cases to handle:
1648+
"dirName" starts with "$ORIGIN":
1649+
The original build-system already took care of setting a relative
1650+
RPATH, resolve it and test if it is worthwhile to keep it.
1651+
"dirName" start with "rootDir":
1652+
The original build-system added some absolute RPATH (absolute on
1653+
the build machine). While this is wrong, it can still be fixed; so
1654+
test if it is worthwhile to keep it.
1655+
"rootDir"/"dirName" exists:
1656+
The original build-system already took care of setting an absolute
1657+
RPATH (absolute in the final rootfs), resolve it and test if it is
1658+
worthwhile to keep it;
1659+
"dirName" points somewhere else:
1660+
(can be anywhere: build trees, staging tree, host location,
1661+
non-existing location, etc.). Just discard such a path. */
1662+
if (!dirName.compare(0, 7, "$ORIGIN")) {
1663+
path = fileDir + dirName.substr(7);
1664+
if (!absolutePathExists(path, canonicalPath)) {
1665+
debug("removing directory '%s' from RPATH because it doesn't exist\n", dirName.c_str());
1666+
continue;
1667+
}
1668+
} else if (!dirName.compare(0, rootDir.length(), rootDir)) {
1669+
if (!absolutePathExists(dirName, canonicalPath)) {
1670+
debug("removing directory '%s' from RPATH because it doesn't exist\n", dirName.c_str());
1671+
continue;
1672+
}
1673+
} else {
1674+
path = rootDir + dirName;
1675+
if (!absolutePathExists(path, canonicalPath)) {
1676+
debug("removing directory '%s' from RPATH because it's not under the root directory\n",
1677+
dirName.c_str());
1678+
continue;
1679+
}
1680+
}
1681+
1682+
if (noStandardLibDirs) {
1683+
if (!canonicalPath.compare(rootDir + "/lib") ||
1684+
!canonicalPath.compare(rootDir + "/usr/lib")) {
1685+
debug("removing directory '%s' from RPATH because it's a standard library directory\n",
1686+
dirName.c_str());
1687+
continue;
1688+
}
1689+
}
1690+
1691+
if (!libFoundInRPath(canonicalPath, neededLibs, neededLibFound)) {
1692+
debug("removing directory '%s' from RPATH\n", dirName.c_str());
1693+
continue;
1694+
}
1695+
1696+
/* Finally make "canonicalPath" relative to "filedir" in "rootDir" */
1697+
if (relativeToFile) {
1698+
debug("making RPATH relative to FILE\n");
1699+
appendRPath(newRPath, makePathRelative(canonicalPath, fileDir));
1700+
}
1701+
else {
1702+
debug("not making RPATH relative to FILE\n");
1703+
appendRPath(newRPath, canonicalPath.substr(rootDir.length()));
1704+
}
1705+
debug("keeping relative path of %s\n", canonicalPath.c_str());
1706+
}
1707+
1708+
return newRPath;
1709+
}
1710+
15781711
template<ElfFileParams>
15791712
void ElfFile<ElfFileParamNames>::removeRPath(Elf_Shdr & shdrDynamic) {
15801713
auto dyn = (Elf_Dyn *)(fileContents->data() + rdi(shdrDynamic.sh_offset));
@@ -1596,7 +1729,7 @@ void ElfFile<ElfFileParamNames>::removeRPath(Elf_Shdr & shdrDynamic) {
15961729

15971730
template<ElfFileParams>
15981731
void ElfFile<ElfFileParamNames>::modifyRPath(RPathOp op,
1599-
const std::vector<std::string> & allowedRpathPrefixes, std::string newRPath)
1732+
const std::vector<std::string> & allowedRpathPrefixes, std::string newRPath, const std::string & rootDir, const std::string & fileName)
16001733
{
16011734
auto shdrDynamic = findSectionHeader(".dynamic");
16021735

@@ -1643,6 +1776,8 @@ void ElfFile<ElfFileParamNames>::modifyRPath(RPathOp op,
16431776
neededLibs.push_back(std::string(strTab + rdi(dyn->d_un.d_val)));
16441777
}
16451778

1779+
printf("modifyRPath: op = %d\n", op);
1780+
16461781
switch (op) {
16471782
case rpPrint: {
16481783
printf("%s\n", rpath ? rpath : "");
@@ -1671,6 +1806,15 @@ void ElfFile<ElfFileParamNames>::modifyRPath(RPathOp op,
16711806
break;
16721807
}
16731808
case rpSet: { break; } /* new rpath was provied as input to this function */
1809+
case rpMakeRelative: {
1810+
if (!rpath) {
1811+
debug("no RPATH to make relative\n");
1812+
return;
1813+
}
1814+
debug("making RPATH relative\n");
1815+
newRPath = makeRelativeRPath(rpath, neededLibs, rootDir, fileName);
1816+
break;
1817+
}
16741818
}
16751819

16761820
if (!forceRPath && dynRPath && !dynRunPath) { /* convert DT_RPATH to DT_RUNPATH */
@@ -2429,7 +2573,9 @@ static bool addRPath = false;
24292573
static bool addDebugTag = false;
24302574
static bool renameDynamicSymbols = false;
24312575
static bool printRPath = false;
2576+
static bool makeRPathRelative = false;
24322577
static std::string newRPath;
2578+
static std::string rootDir;
24332579
static std::set<std::string> neededLibsToRemove;
24342580
static std::map<std::string, std::string> neededLibsToReplace;
24352581
static std::set<std::string> neededLibsToAdd;
@@ -2464,23 +2610,31 @@ static void patchElf2(ElfFile && elfFile, const FileContents & fileContents, con
24642610
elfFile.setInterpreter(newInterpreter);
24652611

24662612
if (printRPath)
2467-
elfFile.modifyRPath(elfFile.rpPrint, {}, "");
2613+
elfFile.modifyRPath(elfFile.rpPrint, {}, "", rootDir, fileName);
24682614

24692615
if (printExecstack)
24702616
elfFile.modifyExecstack(ElfFile::ExecstackMode::print);
24712617
else if (clearExecstack)
24722618
elfFile.modifyExecstack(ElfFile::ExecstackMode::clear);
24732619
else if (setExecstack)
24742620
elfFile.modifyExecstack(ElfFile::ExecstackMode::set);
2621+
else
2622+
printf("patchElf2: nothing done 1\n");
2623+
2624+
printf("patchElf2: makeRPathRelativeop = %d\n", makeRPathRelative);
24752625

24762626
if (shrinkRPath)
2477-
elfFile.modifyRPath(elfFile.rpShrink, allowedRpathPrefixes, "");
2627+
elfFile.modifyRPath(elfFile.rpShrink, allowedRpathPrefixes, "", rootDir, fileName);
24782628
else if (removeRPath)
2479-
elfFile.modifyRPath(elfFile.rpRemove, {}, "");
2629+
elfFile.modifyRPath(elfFile.rpRemove, {}, "", rootDir, fileName);
24802630
else if (setRPath)
2481-
elfFile.modifyRPath(elfFile.rpSet, {}, newRPath);
2631+
elfFile.modifyRPath(elfFile.rpSet, {}, newRPath, rootDir, fileName);
24822632
else if (addRPath)
2483-
elfFile.modifyRPath(elfFile.rpAdd, {}, newRPath);
2633+
elfFile.modifyRPath(elfFile.rpAdd, {}, newRPath, rootDir, fileName);
2634+
else if (makeRPathRelative)
2635+
elfFile.modifyRPath(elfFile.rpMakeRelative, {}, "", rootDir, fileName);
2636+
else
2637+
printf("patchElf2: nothing done 2\n");
24842638

24852639
if (printNeeded) elfFile.printNeededLibs();
24862640

@@ -2548,6 +2702,9 @@ static void showHelp(const std::string & progName)
25482702
[--remove-rpath]\n\
25492703
[--shrink-rpath]\n\
25502704
[--allowed-rpath-prefixes PREFIXES]\t\tWith '--shrink-rpath', reject rpath entries not starting with the allowed prefix\n\
2705+
[--make-rpath-relative ROOTDIR]\n\
2706+
[--no-standard-lib-dirs]\n\
2707+
[--relative-to-file]\n\
25512708
[--print-rpath]\n\
25522709
[--force-rpath]\n\
25532710
[--add-needed LIBRARY]\n\
@@ -2583,6 +2740,7 @@ static int mainWrapped(int argc, char * * argv)
25832740
int i;
25842741
for (i = 1; i < argc; ++i) {
25852742
std::string arg(argv[i]);
2743+
debug("mainWrapped: arg = '%s'\n", argv[i]);
25862744
if (arg == "--set-interpreter" || arg == "--interpreter") {
25872745
if (++i == argc) error("missing argument");
25882746
newInterpreter = resolveArgument(argv[i]);
@@ -2631,6 +2789,19 @@ static int mainWrapped(int argc, char * * argv)
26312789
addRPath = true;
26322790
newRPath = resolveArgument(argv[i]);
26332791
}
2792+
else if (arg == "--make-rpath-relative") {
2793+
if (++i == argc) error("missing argument to --make-rpath-relative");
2794+
debug("mainWrapped: makeRPathRelative = true\n");
2795+
makeRPathRelative = true;
2796+
rootDir = argv[i];
2797+
}
2798+
else if (arg == "--no-standard-lib-dirs") {
2799+
noStandardLibDirs = true;
2800+
}
2801+
else if (arg == "--relative-to-file") {
2802+
debug("mainWrapped: relativeToFile = true\n");
2803+
relativeToFile = true;
2804+
}
26342805
else if (arg == "--print-rpath") {
26352806
printRPath = true;
26362807
}

src/patchelf.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,10 +150,15 @@ class ElfFile
150150

151151
void setInterpreter(const std::string & newInterpreter);
152152

153-
typedef enum { rpPrint, rpShrink, rpSet, rpAdd, rpRemove } RPathOp;
153+
typedef enum { rpPrint, rpShrink, rpSet, rpAdd, rpRemove, rpMakeRelative} RPathOp;
154154

155-
void modifyRPath(RPathOp op, const std::vector<std::string> & allowedRpathPrefixes, std::string newRPath);
155+
bool libFoundInRPath(const std::string & dirName,
156+
const std::vector<std::string> neededLibs,
157+
std::vector<bool> & neededLibFound);
158+
159+
void modifyRPath(RPathOp op, const std::vector<std::string> & allowedRpathPrefixes, std::string newRPath, const std::string & rootDir, const std::string & fileName);
156160
std::string shrinkRPath(char* rpath, std::vector<std::string> &neededLibs, const std::vector<std::string> & allowedRpathPrefixes);
161+
std::string makeRelativeRPath(char* rpath, std::vector<std::string> &neededLibs, const std::string & rootDir, const std::string & fileName);
157162
void removeRPath(Elf_Shdr & shdrDynamic);
158163

159164
void addNeeded(const std::set<std::string> & libs);

0 commit comments

Comments
 (0)