From 25cb97cc4faa8e2661ecc1ee41b8fdebed86f2f9 Mon Sep 17 00:00:00 2001 From: "Nathaniel J. Smith" Date: Thu, 31 Mar 2016 18:06:47 -0700 Subject: [PATCH 1/2] Teach --replace-needed to update .gnu.version_r table If the ELF binary that we're patching is linked to a DSO that uses symbol versioning, then the DSO's SONAME appears in two different places: once as a DT_NEEDED entry, and once in the .gnu.version_r version requirements section. Previously, patchelf --replace-needed would update DT_NEEDED entry, but fail to update the .gnu.version_r table. This resulted in completely broken binaries -- trying to load them would trigger an assertion failure inside the dynamic loader, as it tries to check the version of a library that was never loaded: Inconsistency detected by ld.so: dl-version.c: 224: _dl_check_map_versions: Assertion `needed != ((void *)0)' failed! This commit teaches --replace-needed to update the .gnu.version_r table. Fixes: gh-84 --- src/patchelf.cc | 38 ++++++++++++++++++++++++++++++++++---- 1 file changed, 34 insertions(+), 4 deletions(-) diff --git a/src/patchelf.cc b/src/patchelf.cc index 136098fb..bceeba3e 100644 --- a/src/patchelf.cc +++ b/src/patchelf.cc @@ -53,8 +53,8 @@ off_t fileSize, maxSize; unsigned char * contents = 0; -#define ElfFileParams class Elf_Ehdr, class Elf_Phdr, class Elf_Shdr, class Elf_Addr, class Elf_Off, class Elf_Dyn, class Elf_Sym -#define ElfFileParamNames Elf_Ehdr, Elf_Phdr, Elf_Shdr, Elf_Addr, Elf_Off, Elf_Dyn, Elf_Sym +#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 static unsigned int getPageSize(){ @@ -1260,6 +1260,8 @@ void ElfFile::replaceNeeded(map& libs) Elf_Dyn * dyn = (Elf_Dyn *) (contents + rdi(shdrDynamic.sh_offset)); + unsigned int verNeedNum = 0; + unsigned int dynStrAddedBytes = 0; for ( ; rdi(dyn->d_tag) != DT_NULL; dyn++) { @@ -1287,6 +1289,34 @@ void ElfFile::replaceNeeded(map& libs) debug("keeping DT_NEEDED entry `%s'\n", name); } } + if (rdi(dyn->d_tag) == DT_VERNEEDNUM) { + verNeedNum = rdi(dyn->d_un.d_val); + } + } + + // If a replaced library uses symbol versions, then there will also be + // references to it in the "version needed" table, and these also need to + // be replaced. + + if (verNeedNum) { + Elf_Shdr & shdrVersionR = findSection3(".gnu.version_r"); + // The filename strings in the .gnu.version_r aren't necessarily in + // .dynstr -- we have to look at ->sh_link to find the section with + // the strings in it. + Elf_Shdr & shdrVersionRStrings = *shdrs[rdi(shdrVersionR->sh_link)]; + + Elf_Verneed * need = (Elf_Verneed *) (contents + rdi(shdrVersionR.sh_offset)); + while (verNeedNum > 0) { + + // XX check need->vn_file, which is an offset into the + // shdrVersionRStrings section + // if if matches one of the entries in `libs`, then use + // replaceSection logic like that above to add the new string to + // this section and change vn_file + + need = (Elf_Verneed *) (contents + rdi(need->vn_next)); + verNeedNum--; + } } } @@ -1474,13 +1504,13 @@ static void patchElf() if (contents[EI_CLASS] == ELFCLASS32 && contents[EI_VERSION] == EV_CURRENT) { - ElfFile elfFile; + ElfFile elfFile; patchElf2(elfFile); } else if (contents[EI_CLASS] == ELFCLASS64 && contents[EI_VERSION] == EV_CURRENT) { - ElfFile elfFile; + ElfFile elfFile; patchElf2(elfFile); } else { From 907c02009e9f4e6ad122fa940fdb59572fddfc87 Mon Sep 17 00:00:00 2001 From: "Nathaniel J. Smith" Date: Fri, 1 Apr 2016 00:12:38 -0700 Subject: [PATCH 2/2] Small cleanups to replaceNeeded No semantic changes, but I noticed some small errors in the DT_NEEDED handling loop while I was adding the .gnu.version_r handling, so might as well fix them while I'm here. --- src/patchelf.cc | 51 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 14 deletions(-) diff --git a/src/patchelf.cc b/src/patchelf.cc index bceeba3e..91abc6ed 100644 --- a/src/patchelf.cc +++ b/src/patchelf.cc @@ -1274,13 +1274,13 @@ void ElfFile::replaceNeeded(map& libs) // technically, the string referred by d_val could be used otherwise, too (although unlikely) // we'll therefore add a new string - debug("resizing .dynstr ..."); + debug("resizing .dynstr ...\n"); string & newDynStr = replaceSection(".dynstr", rdi(shdrDynStr.sh_size) + replacement.size() + 1 + dynStrAddedBytes); setSubstr(newDynStr, rdi(shdrDynStr.sh_size) + dynStrAddedBytes, replacement + '\0'); - dyn->d_un.d_val = shdrDynStr.sh_size + dynStrAddedBytes; + wri(dyn->d_un.d_val, rdi(shdrDynStr.sh_size) + dynStrAddedBytes); dynStrAddedBytes += replacement.size() + 1; @@ -1299,23 +1299,46 @@ void ElfFile::replaceNeeded(map& libs) // be replaced. if (verNeedNum) { - Elf_Shdr & shdrVersionR = findSection3(".gnu.version_r"); - // The filename strings in the .gnu.version_r aren't necessarily in - // .dynstr -- we have to look at ->sh_link to find the section with - // the strings in it. - Elf_Shdr & shdrVersionRStrings = *shdrs[rdi(shdrVersionR->sh_link)]; + Elf_Shdr & shdrVersionR = findSection(".gnu.version_r"); + // The filename strings in the .gnu.version_r are different from the + // ones in .dynamic: instead of being in .dynstr, they're in some + // arbitrary section and we have to look in ->sh_link to figure out + // which one. + Elf_Shdr & shdrVersionRStrings = shdrs[rdi(shdrVersionR.sh_link)]; + // this is where we find the actual filename strings + char * verStrTab = (char *) contents + rdi(shdrVersionRStrings.sh_offset); + // and we also need the name of the section containing the strings, so + // that we can pass it to replaceSection + string versionRStringsSName = getSectionName(shdrVersionRStrings); + + debug("found .gnu.version_r with %i entries, strings in %s\n", verNeedNum, versionRStringsSName.c_str()); + + unsigned int verStrAddedBytes = 0; Elf_Verneed * need = (Elf_Verneed *) (contents + rdi(shdrVersionR.sh_offset)); while (verNeedNum > 0) { + char * file = verStrTab + rdi(need->vn_file); + if (libs.find(file) != libs.end()) { + const string & replacement = libs[file]; - // XX check need->vn_file, which is an offset into the - // shdrVersionRStrings section - // if if matches one of the entries in `libs`, then use - // replaceSection logic like that above to add the new string to - // this section and change vn_file + debug("replacing .gnu.version_r entry `%s' with `%s'\n", file, replacement.c_str()); + debug("resizing string section %s ...\n", versionRStringsSName.c_str()); - need = (Elf_Verneed *) (contents + rdi(need->vn_next)); - verNeedNum--; + string & newVerDynStr = replaceSection(versionRStringsSName, + rdi(shdrVersionRStrings.sh_size) + replacement.size() + 1 + verStrAddedBytes); + setSubstr(newVerDynStr, rdi(shdrVersionRStrings.sh_size) + verStrAddedBytes, replacement + '\0'); + + wri(need->vn_file, rdi(shdrVersionRStrings.sh_size) + verStrAddedBytes); + + verStrAddedBytes += replacement.size() + 1; + + changed = true; + } else { + debug("keeping .gnu.version_r entry `%s'\n", file); + } + // the Elf_Verneed structures form a linked list, so jump to next entry + need = (Elf_Verneed *) (contents + rdi(shdrVersionR.sh_offset) + rdi(need->vn_next)); + --verNeedNum; } } }