From ab4babd6c4ca519a36d83820f5ea280e8a5148a6 Mon Sep 17 00:00:00 2001 From: Wolfgang Grandegger Date: Mon, 20 Feb 2017 16:29:24 +0100 Subject: [PATCH] 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 --- README | 16 ++++ src/patchelf.cc | 199 ++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 190 insertions(+), 25 deletions(-) diff --git a/README b/README index 406cefda..609ac6a2 100644 --- a/README +++ b/README @@ -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): diff --git a/src/patchelf.cc b/src/patchelf.cc index 5077cd5d..63181e1a 100644 --- a/src/patchelf.cc +++ b/src/patchelf.cc @@ -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> 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 @@ -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 class ElfFile @@ -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 neededLibs, + std::vector & neededLibFound); - void modifyRPath(RPathOp op, const std::vector & allowedRpathPrefixes, std::string newRPath); + void modifyRPath(RPathOp op, + const std::vector & allowedRpathPrefixes, + std::string rootDir, std::string newRPath); void addNeeded(const std::set & libs); @@ -1099,10 +1151,33 @@ static void concatToRPath(std::string & rpath, const std::string & path) rpath += path; } +template +bool ElfFile::libFoundInRPath(const std::string & dirName, + const std::vector neededLibs, std::vector & 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 void ElfFile::modifyRPath(RPathOp op, - const std::vector & allowedRpathPrefixes, std::string newRPath) + const std::vector & allowedRpathPrefixes, + std::string rootDir, std::string newRPath) { Elf_Shdr & shdrDynamic = findSection(".dynamic"); @@ -1153,6 +1228,10 @@ void ElfFile::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. */ @@ -1177,27 +1256,79 @@ void ElfFile::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 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()); } } @@ -1528,7 +1659,9 @@ static std::vector 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 neededLibsToRemove; static std::map neededLibsToReplace; static std::set neededLibsToAdd; @@ -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(); @@ -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\ @@ -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; }