summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRolf Eike Beer <eb@emlix.com>2020-09-17 10:05:43 +0200
committerJörg Thalheim <joerg@thalheim.io>2021-11-27 11:06:49 +0100
commitb4cb6cac7844a112f3fbb1f64ffefd2624d0537e (patch)
treef99a560896eb62af2556662f4fa967569c5df3d1
parenta174cf3006baf31e0e9eaa62bc9adead93af63f7 (diff)
downloadpatchelf-b4cb6cac7844a112f3fbb1f64ffefd2624d0537e.tar.gz
fix corrupted library names when using --replace-needed multiple times
When it happens that the .gnu.version_r stores the strings in .dynstr it can come to corruption of the library names written into DT_NEEDED: -the library names in DT_NEEDED are replaced, new entries are written to the end of .dynstr -the version library names are replaced, and written to the end of the string section. If the section for the version strings is also ".dynstr", the previous modifications were _not_ taken into account and things were written from the old end of .dynstr again. The order in which these strings were written is not the same as the previous replacement, so things would end up with the same size, but different offsets. The .gnu.version_r table is correct, the file contents are fine, but the offsets in the DT_NEEDED entries are wrong. Since they are printed as 0-terminated strings the first one replaced will always be shown correct, which also is the case if the argument is only used once as the string is replaced with itself afterwards.
-rw-r--r--src/patchelf.cc4
-rw-r--r--tests/Makefile.am3
-rwxr-xr-xtests/replace-needed.sh21
3 files changed, 27 insertions, 1 deletions
diff --git a/src/patchelf.cc b/src/patchelf.cc
index 06f41c6..6148abd 100644
--- a/src/patchelf.cc
+++ b/src/patchelf.cc
@@ -1636,6 +1636,10 @@ void ElfFile<ElfFileParamNames>::replaceNeeded(const std::map<std::string, std::
debug("found .gnu.version_r with %i entries, strings in %s\n", verNeedNum, versionRStringsSName.c_str());
unsigned int verStrAddedBytes = 0;
+ // It may be that it is .dynstr again, in which case we must take the already
+ // added bytes into account.
+ if (versionRStringsSName == ".dynstr")
+ verStrAddedBytes += dynStrAddedBytes;
auto need = (Elf_Verneed *)(fileContents->data() + rdi(shdrVersionR.sh_offset));
while (verNeedNum > 0) {
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 24032f7..c1d61ce 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -38,7 +38,8 @@ src_TESTS = \
args-from-file.sh \
basic-flags.sh \
set-empty-rpath.sh \
- phdr-corruption.sh
+ phdr-corruption.sh \
+ replace-needed.sh
build_TESTS = \
$(no_rpath_arch_TESTS)
diff --git a/tests/replace-needed.sh b/tests/replace-needed.sh
new file mode 100755
index 0000000..0a5581c
--- /dev/null
+++ b/tests/replace-needed.sh
@@ -0,0 +1,21 @@
+#! /bin/sh -e
+SCRATCH=scratch/$(basename $0 .sh)
+
+rm -rf ${SCRATCH}
+mkdir -p ${SCRATCH}
+
+oldNeeded=$(../src/patchelf --print-needed big-dynstr)
+oldLibc=$(../src/patchelf --print-needed big-dynstr | grep -v 'foo\.so')
+../src/patchelf --output ${SCRATCH}/big-needed --replace-needed ${oldLibc} long_long_very_long_libc.so.6 --replace-needed libfoo.so lf.so big-dynstr
+
+if [ -z "$(../src/patchelf --print-needed ${SCRATCH}/big-needed | grep -Fx "long_long_very_long_libc.so.6")" ]; then
+ echo "library long_long_very_long_libc.so.6 not found as NEEDED"
+ ../src/patchelf --print-needed ${SCRATCH}/big-needed
+ exit 1
+fi
+
+if [ -z "$(../src/patchelf --print-needed ${SCRATCH}/big-needed | grep -Fx "lf.so")" ]; then
+ echo "library lf.so not found as NEEDED"
+ ../src/patchelf --print-needed ${SCRATCH}/big-needed
+ exit 1
+fi