Skip to content

Commit

Permalink
Add option to make the rpath relative under a specified root directory
Browse files Browse the repository at this point in the history
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
  • Loading branch information
grandwolf committed Jul 6, 2017
1 parent 382246e commit ab4babd
Show file tree
Hide file tree
Showing 2 changed files with 190 additions and 25 deletions.
16 changes: 16 additions & 0 deletions README
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,22 @@ libraries. In particular, it can do the following:

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

* Sanitize and make the RPATH relative to a specified root directory:

$ patchelf --make-rpath-relative rootdir my-program

This removes from the RPATH all directories, which are not under the
specified rootdir. "$ORIGIN" and directories already relative to
rootdir will be resolved first. If the option '--no-standard-lib' is
given, "rootdir/lib" and "rootdir/usr/lib" directories are discarded
as well. 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. Furthermore, all
directories that do not contain a library referenced by DT_NEEDED
fields of the executable or library will be removed. Finally, the
directory path is converted to a path relative to rootdir starting
with "$ORIGIN".

* Remove declared dependencies on dynamic libraries (DT_NEEDED
entries):

Expand Down
199 changes: 174 additions & 25 deletions src/patchelf.cc
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,15 @@ static bool debugMode = false;

static bool forceRPath = false;

static bool noStandardLibDirs = false;

static bool relativeToFile = false;

static std::string fileName;
static int pageSize = PAGESIZE;

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


#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
#define ElfFileParamNames Elf_Ehdr, Elf_Phdr, Elf_Shdr, Elf_Addr, Elf_Off, Elf_Dyn, Elf_Sym, Elf_Verneed

Expand Down Expand Up @@ -83,6 +86,49 @@ static unsigned int getPageSize()
return pageSize;
}

static bool absolutePathExists(const std::string & path, std::string & canonicalPath)
{
char *cpath = realpath(path.c_str(), NULL);
if (cpath) {
canonicalPath = cpath;
free(cpath);
return true;
} else {
return false;
}
}

static std::string makePathRelative(const std::string & path,
const std::string & refPath)
{
std::string relPath = "$ORIGIN";
std::string p = path, refP = refPath;
std::size_t pos;

/* Strip the common part of path and refPath */
while (true) {
pos = p.find_first_of('/', 1);
if (refP.find_first_of('/', 1) != pos)
break;
if (p.substr(0, pos) != refP.substr(0, pos))
break;
if (pos == std::string::npos)
break;
p = p.substr(pos);
refP = refP.substr(pos);
}
/* Check if both pathes are equal */
if (p != refP) {
pos = 0;
while (pos != std::string::npos) {
pos =refP.find_first_of('/', pos + 1);
relPath.append("/..");
}
relPath.append(p);
}

return relPath;
}

template<ElfFileParams>
class ElfFile
Expand Down Expand Up @@ -191,9 +237,15 @@ class ElfFile

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

typedef enum { rpPrint, rpShrink, rpSet, rpRemove } RPathOp;
typedef enum { rpPrint, rpShrink, rpMakeRelative, rpSet, rpRemove} RPathOp;

bool libFoundInRPath(const std::string & dirName,
const std::vector<std::string> neededLibs,
std::vector<bool> & neededLibFound);

void modifyRPath(RPathOp op, const std::vector<std::string> & allowedRpathPrefixes, std::string newRPath);
void modifyRPath(RPathOp op,
const std::vector<std::string> & allowedRpathPrefixes,
std::string rootDir, std::string newRPath);

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

Expand Down Expand Up @@ -1099,10 +1151,33 @@ static void concatToRPath(std::string & rpath, const std::string & path)
rpath += path;
}

template<ElfFileParams>
bool ElfFile<ElfFileParamNames>::libFoundInRPath(const std::string & dirName,
const std::vector<std::string> neededLibs, std::vector<bool> & neededLibFound)
{
/* For each library that we haven't found yet, see if it
exists in this directory. */
bool libFound = false;
for (unsigned int j = 0; j < neededLibs.size(); ++j)
if (!neededLibFound[j]) {
std::string libName = dirName + "/" + neededLibs[j];
try {
if (getElfType(readFile(libName, sizeof(Elf32_Ehdr))).machine == rdi(hdr->e_machine)) {
neededLibFound[j] = true;
libFound = true;
} else
debug("ignoring library '%s' because its machine type differs\n", libName.c_str());
} catch (SysError & e) {
if (e.errNo != ENOENT) throw;
}
}
return libFound;
}

template<ElfFileParams>
void ElfFile<ElfFileParamNames>::modifyRPath(RPathOp op,
const std::vector<std::string> & allowedRpathPrefixes, std::string newRPath)
const std::vector<std::string> & allowedRpathPrefixes,
std::string rootDir, std::string newRPath)
{
Elf_Shdr & shdrDynamic = findSection(".dynamic");

Expand Down Expand Up @@ -1153,6 +1228,10 @@ void ElfFile<ElfFileParamNames>::modifyRPath(RPathOp op,
return;
}

if (op == rpMakeRelative && !rpath) {
debug("no RPATH to make relative\n");
return;
}

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

/* For each library that we haven't found yet, see if it
exists in this directory. */
bool libFound = false;
for (unsigned int j = 0; j < neededLibs.size(); ++j)
if (!neededLibFound[j]) {
std::string libName = dirName + "/" + neededLibs[j];
try {
if (getElfType(readFile(libName, sizeof(Elf32_Ehdr))).machine == rdi(hdr->e_machine)) {
neededLibFound[j] = true;
libFound = true;
} else
debug("ignoring library '%s' because its machine type differs\n", libName.c_str());
} catch (SysError & e) {
if (e.errNo != ENOENT) throw;
}
if (!libFoundInRPath(dirName, neededLibs, neededLibFound))
debug("removing directory '%s' from RPATH\n", dirName.c_str());
else
concatToRPath(newRPath, dirName);
}
}

/* Make the the RPATH relative to the specified path */
if (op == rpMakeRelative) {
std::vector<bool> neededLibFound(neededLibs.size(), false);
std::string fileDir = fileName.substr(0, fileName.find_last_of("/"));
newRPath = "";

for (auto & dirName : splitColonDelimitedString(rpath)) {
std::string canonicalPath;
std::string path;

/* Figure out if we should keep or discard the path; there are several
cases to handled:
"dirName" starts with "$ORIGIN":
The original build-system already took care of setting a relative
RPATH, resolve it and test if it is worthwhile to keep it.
"dirName" start with "rootDir":
The original build-system added some absolute RPATH (absolute on
the build machine). While this is wrong, it can still be fixed; so
test if it is worthwhile to keep it.
"rootDir"/"dirName" exists:
The original build-system already took care of setting an absolute
RPATH (absolute in the final rootfs), resolve it and test if it is
worthwhile to keep it;
"dirName" points somewhere else:
(can be anywhere: build trees, staging tree, host location,
non-existing location, etc.). Just discard such a path. */
if (!dirName.compare(0, 7, "$ORIGIN")) {
path = fileDir + dirName.substr(7);
if (!absolutePathExists(path, canonicalPath)) {
debug("removing directory '%s' from RPATH because it doesn't exist\n", dirName.c_str());
continue;
}
} else if (!dirName.compare(0, rootDir.length(), rootDir)) {
if (!absolutePathExists(dirName, canonicalPath)) {
debug("removing directory '%s' from RPATH because it doesn't exist\n", dirName.c_str());
continue;
}
} else {
path = rootDir + dirName;
if (!absolutePathExists(path, canonicalPath)) {
debug("removing directory '%s' from RPATH because it's not under the root directory\n",
dirName.c_str());
continue;
}
}

if (noStandardLibDirs) {
if (!canonicalPath.compare(rootDir + "/lib") ||
!canonicalPath.compare(rootDir + "/usr/lib")) {
debug("removing directory '%s' from RPATH because it's a standard library directory\n",
dirName.c_str());
continue;
}
}

if (!libFound)
if (!libFoundInRPath(canonicalPath, neededLibs, neededLibFound)) {
debug("removing directory '%s' from RPATH\n", dirName.c_str());
continue;
}

/* Finally make "canonicalPath" relative to "filedir" in "rootDir" */
if (relativeToFile)
concatToRPath(newRPath, makePathRelative(canonicalPath, fileDir));
else
concatToRPath(newRPath, dirName);
concatToRPath(newRPath, canonicalPath.substr(rootDir.length()));
debug("keeping relative path of %s\n", canonicalPath.c_str());
}
}

Expand Down Expand Up @@ -1528,7 +1659,9 @@ static std::vector<std::string> allowedRpathPrefixes;
static bool removeRPath = false;
static bool setRPath = false;
static bool printRPath = false;
static bool makeRPathRelative = false;
static std::string newRPath;
static std::string rootDir;
static std::set<std::string> neededLibsToRemove;
static std::map<std::string, std::string> neededLibsToReplace;
static std::set<std::string> neededLibsToAdd;
Expand All @@ -1551,14 +1684,16 @@ static void patchElf2(ElfFile && elfFile)
elfFile.setInterpreter(newInterpreter);

if (printRPath)
elfFile.modifyRPath(elfFile.rpPrint, {}, "");
elfFile.modifyRPath(elfFile.rpPrint, {}, {}, "");

if (shrinkRPath)
elfFile.modifyRPath(elfFile.rpShrink, allowedRpathPrefixes, "");
elfFile.modifyRPath(elfFile.rpShrink, allowedRpathPrefixes, "", "");
else if (removeRPath)
elfFile.modifyRPath(elfFile.rpRemove, {}, "");
elfFile.modifyRPath(elfFile.rpRemove, {}, "", "");
else if (setRPath)
elfFile.modifyRPath(elfFile.rpSet, {}, newRPath);
elfFile.modifyRPath(elfFile.rpSet, {}, "", newRPath);
else if (makeRPathRelative)
elfFile.modifyRPath(elfFile.rpMakeRelative, {}, rootDir, "");

if (printNeeded) elfFile.printNeededLibs();

Expand Down Expand Up @@ -1604,6 +1739,9 @@ void showHelp(const std::string & progName)
[--remove-rpath]\n\
[--shrink-rpath]\n\
[--allowed-rpath-prefixes PREFIXES]\t\tWith '--shrink-rpath', reject rpath entries not starting with the allowed prefix\n\
[--make-rpath-relative ROOTDIR]\n\
[--no-standard-lib-dirs]\n\
[--relative-to-file]\n\
[--print-rpath]\n\
[--force-rpath]\n\
[--add-needed LIBRARY]\n\
Expand Down Expand Up @@ -1664,6 +1802,17 @@ int mainWrapped(int argc, char * * argv)
setRPath = true;
newRPath = argv[i];
}
else if (arg == "--make-rpath-relative") {
if (++i == argc) error("missing argument to --make-rpath-relative");
makeRPathRelative = true;
rootDir = argv[i];
}
else if (arg == "--no-standard-lib-dirs") {
noStandardLibDirs = true;
}
else if (arg == "--relative-to-file") {
relativeToFile = true;
}
else if (arg == "--print-rpath") {
printRPath = true;
}
Expand Down

0 comments on commit ab4babd

Please sign in to comment.