Skip to content

Commit ab4babd

Browse files
committed
Add option to make the rpath relative under a specified root directory
Running "patchelf" with the option "--make-rpath-relative ROOTDIR" will modify or delete the RPATHDIRs according the following rules: RPATHDIR starts with "$ORIGIN": The original build-system already took care of setting a relative RPATH, resolve it and test if it's valid according to the rules below. RPATHDIR starts with ROOTDIR: The original build-system added some absolute RPATH (absolute on the build machine). While this is not OK , it can still be fixed; so test if it is worthwhile to keep it. ROOTDIR/RPATHDIR exists: The original build-system already took care of setting an absolute RPATH (absolute in the final rootfs), resolve it and test if it's worthwhile to keep it. RPATHDIR points somewhere else: (can be anywhere: build trees, staging tree, host location, non-existing location, etc.). Just discard such a path. In addition, the option "--no-standard-libs" will discard RPATHDIRs ROOTDIR/lib and ROOTDIR/usr/lib. Like "--shrink-rpath", RPATHDIRs are discarded if the directories do not contain a library referenced by DT_NEEDED fields. If the option "--relative-to-file" is given, the rpath will start with "$ORIGIN" making it relative to the ELF file, otherwise an absolute path relative to ROOTDIR will be used. This option is useful to sanitize the ELF files of a target root filesystem and to help making a SDK/toolchain relocatable, e.g. of the buildroot project [1]. [1] http://lists.busybox.net/pipermail/buildroot/2016-April/159422.html fixes
1 parent 382246e commit ab4babd

File tree

2 files changed

+190
-25
lines changed

2 files changed

+190
-25
lines changed

README

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,22 @@ libraries. In particular, it can do the following:
2727

2828
$ patchelf --shrink-rpath --allowed-rpath-prefixes /usr/lib:/foo/lib my-program
2929

30+
* Sanitize and make the RPATH relative to a specified root directory:
31+
32+
$ patchelf --make-rpath-relative rootdir my-program
33+
34+
This removes from the RPATH all directories, which are not under the
35+
specified rootdir. "$ORIGIN" and directories already relative to
36+
rootdir will be resolved first. If the option '--no-standard-lib' is
37+
given, "rootdir/lib" and "rootdir/usr/lib" directories are discarded
38+
as well. If the option "--relative-to-file" is given, the RPATH will
39+
start with "$ORIGIN" making it relative to the ELF file, otherwise
40+
an absolute path relative to ROOTDIR will be used. Furthermore, all
41+
directories that do not contain a library referenced by DT_NEEDED
42+
fields of the executable or library will be removed. Finally, the
43+
directory path is converted to a path relative to rootdir starting
44+
with "$ORIGIN".
45+
3046
* Remove declared dependencies on dynamic libraries (DT_NEEDED
3147
entries):
3248

src/patchelf.cc

Lines changed: 174 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,15 @@ static bool debugMode = false;
4444

4545
static bool forceRPath = false;
4646

47+
static bool noStandardLibDirs = false;
48+
49+
static bool relativeToFile = false;
50+
4751
static std::string fileName;
4852
static int pageSize = PAGESIZE;
4953

5054
typedef std::shared_ptr<std::vector<unsigned char>> FileContents;
5155

52-
5356
#define ElfFileParams class Elf_Ehdr, class Elf_Phdr, class Elf_Shdr, class Elf_Addr, class Elf_Off, class Elf_Dyn, class Elf_Sym, class Elf_Verneed
5457
#define ElfFileParamNames Elf_Ehdr, Elf_Phdr, Elf_Shdr, Elf_Addr, Elf_Off, Elf_Dyn, Elf_Sym, Elf_Verneed
5558

@@ -83,6 +86,49 @@ static unsigned int getPageSize()
8386
return pageSize;
8487
}
8588

89+
static bool absolutePathExists(const std::string & path, std::string & canonicalPath)
90+
{
91+
char *cpath = realpath(path.c_str(), NULL);
92+
if (cpath) {
93+
canonicalPath = cpath;
94+
free(cpath);
95+
return true;
96+
} else {
97+
return false;
98+
}
99+
}
100+
101+
static std::string makePathRelative(const std::string & path,
102+
const std::string & refPath)
103+
{
104+
std::string relPath = "$ORIGIN";
105+
std::string p = path, refP = refPath;
106+
std::size_t pos;
107+
108+
/* Strip the common part of path and refPath */
109+
while (true) {
110+
pos = p.find_first_of('/', 1);
111+
if (refP.find_first_of('/', 1) != pos)
112+
break;
113+
if (p.substr(0, pos) != refP.substr(0, pos))
114+
break;
115+
if (pos == std::string::npos)
116+
break;
117+
p = p.substr(pos);
118+
refP = refP.substr(pos);
119+
}
120+
/* Check if both pathes are equal */
121+
if (p != refP) {
122+
pos = 0;
123+
while (pos != std::string::npos) {
124+
pos =refP.find_first_of('/', pos + 1);
125+
relPath.append("/..");
126+
}
127+
relPath.append(p);
128+
}
129+
130+
return relPath;
131+
}
86132

87133
template<ElfFileParams>
88134
class ElfFile
@@ -191,9 +237,15 @@ class ElfFile
191237

192238
void setInterpreter(const std::string & newInterpreter);
193239

194-
typedef enum { rpPrint, rpShrink, rpSet, rpRemove } RPathOp;
240+
typedef enum { rpPrint, rpShrink, rpMakeRelative, rpSet, rpRemove} RPathOp;
241+
242+
bool libFoundInRPath(const std::string & dirName,
243+
const std::vector<std::string> neededLibs,
244+
std::vector<bool> & neededLibFound);
195245

196-
void modifyRPath(RPathOp op, const std::vector<std::string> & allowedRpathPrefixes, std::string newRPath);
246+
void modifyRPath(RPathOp op,
247+
const std::vector<std::string> & allowedRpathPrefixes,
248+
std::string rootDir, std::string newRPath);
197249

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

@@ -1099,10 +1151,33 @@ static void concatToRPath(std::string & rpath, const std::string & path)
10991151
rpath += path;
11001152
}
11011153

1154+
template<ElfFileParams>
1155+
bool ElfFile<ElfFileParamNames>::libFoundInRPath(const std::string & dirName,
1156+
const std::vector<std::string> neededLibs, std::vector<bool> & neededLibFound)
1157+
{
1158+
/* For each library that we haven't found yet, see if it
1159+
exists in this directory. */
1160+
bool libFound = false;
1161+
for (unsigned int j = 0; j < neededLibs.size(); ++j)
1162+
if (!neededLibFound[j]) {
1163+
std::string libName = dirName + "/" + neededLibs[j];
1164+
try {
1165+
if (getElfType(readFile(libName, sizeof(Elf32_Ehdr))).machine == rdi(hdr->e_machine)) {
1166+
neededLibFound[j] = true;
1167+
libFound = true;
1168+
} else
1169+
debug("ignoring library '%s' because its machine type differs\n", libName.c_str());
1170+
} catch (SysError & e) {
1171+
if (e.errNo != ENOENT) throw;
1172+
}
1173+
}
1174+
return libFound;
1175+
}
11021176

11031177
template<ElfFileParams>
11041178
void ElfFile<ElfFileParamNames>::modifyRPath(RPathOp op,
1105-
const std::vector<std::string> & allowedRpathPrefixes, std::string newRPath)
1179+
const std::vector<std::string> & allowedRpathPrefixes,
1180+
std::string rootDir, std::string newRPath)
11061181
{
11071182
Elf_Shdr & shdrDynamic = findSection(".dynamic");
11081183

@@ -1153,6 +1228,10 @@ void ElfFile<ElfFileParamNames>::modifyRPath(RPathOp op,
11531228
return;
11541229
}
11551230

1231+
if (op == rpMakeRelative && !rpath) {
1232+
debug("no RPATH to make relative\n");
1233+
return;
1234+
}
11561235

11571236
/* For each directory in the RPATH, check if it contains any
11581237
needed library. */
@@ -1177,27 +1256,79 @@ void ElfFile<ElfFileParamNames>::modifyRPath(RPathOp op,
11771256
continue;
11781257
}
11791258

1180-
/* For each library that we haven't found yet, see if it
1181-
exists in this directory. */
1182-
bool libFound = false;
1183-
for (unsigned int j = 0; j < neededLibs.size(); ++j)
1184-
if (!neededLibFound[j]) {
1185-
std::string libName = dirName + "/" + neededLibs[j];
1186-
try {
1187-
if (getElfType(readFile(libName, sizeof(Elf32_Ehdr))).machine == rdi(hdr->e_machine)) {
1188-
neededLibFound[j] = true;
1189-
libFound = true;
1190-
} else
1191-
debug("ignoring library '%s' because its machine type differs\n", libName.c_str());
1192-
} catch (SysError & e) {
1193-
if (e.errNo != ENOENT) throw;
1194-
}
1259+
if (!libFoundInRPath(dirName, neededLibs, neededLibFound))
1260+
debug("removing directory '%s' from RPATH\n", dirName.c_str());
1261+
else
1262+
concatToRPath(newRPath, dirName);
1263+
}
1264+
}
1265+
1266+
/* Make the the RPATH relative to the specified path */
1267+
if (op == rpMakeRelative) {
1268+
std::vector<bool> neededLibFound(neededLibs.size(), false);
1269+
std::string fileDir = fileName.substr(0, fileName.find_last_of("/"));
1270+
newRPath = "";
1271+
1272+
for (auto & dirName : splitColonDelimitedString(rpath)) {
1273+
std::string canonicalPath;
1274+
std::string path;
1275+
1276+
/* Figure out if we should keep or discard the path; there are several
1277+
cases to handled:
1278+
"dirName" starts with "$ORIGIN":
1279+
The original build-system already took care of setting a relative
1280+
RPATH, resolve it and test if it is worthwhile to keep it.
1281+
"dirName" start with "rootDir":
1282+
The original build-system added some absolute RPATH (absolute on
1283+
the build machine). While this is wrong, it can still be fixed; so
1284+
test if it is worthwhile to keep it.
1285+
"rootDir"/"dirName" exists:
1286+
The original build-system already took care of setting an absolute
1287+
RPATH (absolute in the final rootfs), resolve it and test if it is
1288+
worthwhile to keep it;
1289+
"dirName" points somewhere else:
1290+
(can be anywhere: build trees, staging tree, host location,
1291+
non-existing location, etc.). Just discard such a path. */
1292+
if (!dirName.compare(0, 7, "$ORIGIN")) {
1293+
path = fileDir + dirName.substr(7);
1294+
if (!absolutePathExists(path, canonicalPath)) {
1295+
debug("removing directory '%s' from RPATH because it doesn't exist\n", dirName.c_str());
1296+
continue;
1297+
}
1298+
} else if (!dirName.compare(0, rootDir.length(), rootDir)) {
1299+
if (!absolutePathExists(dirName, canonicalPath)) {
1300+
debug("removing directory '%s' from RPATH because it doesn't exist\n", dirName.c_str());
1301+
continue;
1302+
}
1303+
} else {
1304+
path = rootDir + dirName;
1305+
if (!absolutePathExists(path, canonicalPath)) {
1306+
debug("removing directory '%s' from RPATH because it's not under the root directory\n",
1307+
dirName.c_str());
1308+
continue;
11951309
}
1310+
}
1311+
1312+
if (noStandardLibDirs) {
1313+
if (!canonicalPath.compare(rootDir + "/lib") ||
1314+
!canonicalPath.compare(rootDir + "/usr/lib")) {
1315+
debug("removing directory '%s' from RPATH because it's a standard library directory\n",
1316+
dirName.c_str());
1317+
continue;
1318+
}
1319+
}
11961320

1197-
if (!libFound)
1321+
if (!libFoundInRPath(canonicalPath, neededLibs, neededLibFound)) {
11981322
debug("removing directory '%s' from RPATH\n", dirName.c_str());
1323+
continue;
1324+
}
1325+
1326+
/* Finally make "canonicalPath" relative to "filedir" in "rootDir" */
1327+
if (relativeToFile)
1328+
concatToRPath(newRPath, makePathRelative(canonicalPath, fileDir));
11991329
else
1200-
concatToRPath(newRPath, dirName);
1330+
concatToRPath(newRPath, canonicalPath.substr(rootDir.length()));
1331+
debug("keeping relative path of %s\n", canonicalPath.c_str());
12011332
}
12021333
}
12031334

@@ -1528,7 +1659,9 @@ static std::vector<std::string> allowedRpathPrefixes;
15281659
static bool removeRPath = false;
15291660
static bool setRPath = false;
15301661
static bool printRPath = false;
1662+
static bool makeRPathRelative = false;
15311663
static std::string newRPath;
1664+
static std::string rootDir;
15321665
static std::set<std::string> neededLibsToRemove;
15331666
static std::map<std::string, std::string> neededLibsToReplace;
15341667
static std::set<std::string> neededLibsToAdd;
@@ -1551,14 +1684,16 @@ static void patchElf2(ElfFile && elfFile)
15511684
elfFile.setInterpreter(newInterpreter);
15521685

15531686
if (printRPath)
1554-
elfFile.modifyRPath(elfFile.rpPrint, {}, "");
1687+
elfFile.modifyRPath(elfFile.rpPrint, {}, {}, "");
15551688

15561689
if (shrinkRPath)
1557-
elfFile.modifyRPath(elfFile.rpShrink, allowedRpathPrefixes, "");
1690+
elfFile.modifyRPath(elfFile.rpShrink, allowedRpathPrefixes, "", "");
15581691
else if (removeRPath)
1559-
elfFile.modifyRPath(elfFile.rpRemove, {}, "");
1692+
elfFile.modifyRPath(elfFile.rpRemove, {}, "", "");
15601693
else if (setRPath)
1561-
elfFile.modifyRPath(elfFile.rpSet, {}, newRPath);
1694+
elfFile.modifyRPath(elfFile.rpSet, {}, "", newRPath);
1695+
else if (makeRPathRelative)
1696+
elfFile.modifyRPath(elfFile.rpMakeRelative, {}, rootDir, "");
15621697

15631698
if (printNeeded) elfFile.printNeededLibs();
15641699

@@ -1604,6 +1739,9 @@ void showHelp(const std::string & progName)
16041739
[--remove-rpath]\n\
16051740
[--shrink-rpath]\n\
16061741
[--allowed-rpath-prefixes PREFIXES]\t\tWith '--shrink-rpath', reject rpath entries not starting with the allowed prefix\n\
1742+
[--make-rpath-relative ROOTDIR]\n\
1743+
[--no-standard-lib-dirs]\n\
1744+
[--relative-to-file]\n\
16071745
[--print-rpath]\n\
16081746
[--force-rpath]\n\
16091747
[--add-needed LIBRARY]\n\
@@ -1664,6 +1802,17 @@ int mainWrapped(int argc, char * * argv)
16641802
setRPath = true;
16651803
newRPath = argv[i];
16661804
}
1805+
else if (arg == "--make-rpath-relative") {
1806+
if (++i == argc) error("missing argument to --make-rpath-relative");
1807+
makeRPathRelative = true;
1808+
rootDir = argv[i];
1809+
}
1810+
else if (arg == "--no-standard-lib-dirs") {
1811+
noStandardLibDirs = true;
1812+
}
1813+
else if (arg == "--relative-to-file") {
1814+
relativeToFile = true;
1815+
}
16671816
else if (arg == "--print-rpath") {
16681817
printRPath = true;
16691818
}

0 commit comments

Comments
 (0)