summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIvan A. Melnikov <iv@altlinux.org>2019-08-22 15:30:11 +0400
committerIvan A. Melnikov <iv@altlinux.org>2021-08-11 17:49:41 +0400
commitb240bb8dcfb7bdce32df12d13c2829516a02ead7 (patch)
tree9869adba66b1239a8e930a021e1be6918392c7a6
parent374c92ad4e4d85684c35637ca70c8a7449d39397 (diff)
downloadpatchelf-b240bb8dcfb7bdce32df12d13c2829516a02ead7.tar.gz
Adjust DT_MIPS_RLD_MAP_REL dynamic section entry if present
`patchelf --set-rpath` corrupted executables on mips32el: the dynamic liker crushed with Segmentation fault when loading any executable with RPATH added that way. The problem was around the MIPS-specific mechanism of setting up the debug map pointer. When DT_MIPS_RLD_MAP_REL entry in the dynamic section is present, it holds the relative address of __RLD_MAP -- an offset relative to this dynamic section entry. Dynamic linker puts the pointer to the `r_debug` structure there. When patchelf updates the executable RPATH, it moves the .dynamic section both in the binary and in memory, while __RLD_MAP is not moved in memory, since it belongs to special .rld_map section that has type PROGBITS. So, the offset stored in DT_MIPS_RLD_MAP_REL entry is not valid anymore and should be updated. This commit adds the necessary update. In the corner case when DT_MIPS_RLD_MAP_REL is present, but .rld_map section is not, the dynamic loader writes the debug pointer to some arbitrary bytes in memory. To avoid crushes on otherwise "working" binaries, we set offset to zero so that the dynamic loader would just overwrite the dynamic section. Here we also import DT_MIPS_RLD_MAP_REL definition in elf.h form glibc commit a2057c984e4314c3740f04cf54e36c824e4c8f32. Refs: #82 Signed-off-by: Ivan A. Melnikov <iv@altlinux.org>
-rw-r--r--src/elf.h6
-rw-r--r--src/patchelf.cc21
-rw-r--r--tests/Makefile.am1
-rwxr-xr-xtests/set-rpath-rel-map.sh37
4 files changed, 62 insertions, 3 deletions
diff --git a/src/elf.h b/src/elf.h
index 576990d..ae1524a 100644
--- a/src/elf.h
+++ b/src/elf.h
@@ -1642,7 +1642,11 @@ typedef struct
PLT is writable. For a non-writable PLT, this is omitted or has a zero
value. */
#define DT_MIPS_RWPLT 0x70000034
-#define DT_MIPS_NUM 0x35
+/* An alternative description of the classic MIPS RLD_MAP that is usable
+ in a PIE as it stores a relative offset from the address of the tag
+ rather than an absolute address. */
+#define DT_MIPS_RLD_MAP_REL 0x70000035
+#define DT_MIPS_NUM 0x36
/* Legal values for DT_MIPS_FLAGS Elf32_Dyn entry. */
diff --git a/src/patchelf.cc b/src/patchelf.cc
index d02bc49..2cee798 100644
--- a/src/patchelf.cc
+++ b/src/patchelf.cc
@@ -1098,9 +1098,9 @@ void ElfFile<ElfFileParamNames>::rewriteHeaders(Elf_Addr phdrAddress)
(e.g., those produced by klibc's klcc). */
auto shdrDynamic = findSection2(".dynamic");
if (shdrDynamic) {
- auto dyn = (Elf_Dyn *)(contents + rdi(shdrDynamic->sh_offset));
+ auto dyn_table = (Elf_Dyn *) (contents + rdi(shdrDynamic->sh_offset));
unsigned int d_tag;
- for ( ; (d_tag = rdi(dyn->d_tag)) != DT_NULL; dyn++)
+ for (auto dyn = dyn_table; (d_tag = rdi(dyn->d_tag)) != DT_NULL; dyn++)
if (d_tag == DT_STRTAB)
dyn->d_un.d_ptr = findSection(".dynstr").sh_addr;
else if (d_tag == DT_STRSZ)
@@ -1142,6 +1142,23 @@ void ElfFile<ElfFileParamNames>::rewriteHeaders(Elf_Addr phdrAddress)
dyn->d_un.d_ptr = findSection(".gnu.version_r").sh_addr;
else if (d_tag == DT_VERSYM)
dyn->d_un.d_ptr = findSection(".gnu.version").sh_addr;
+ else if (d_tag == DT_MIPS_RLD_MAP_REL) {
+ /* the MIPS_RLD_MAP_REL tag stores the offset to the debug
+ pointer, relative to the address of the tag */
+ auto shdr = findSection2(".rld_map");
+ if (shdr) {
+ auto rld_map_addr = findSection(".rld_map").sh_addr;
+ auto dyn_offset = ((char*)dyn) - ((char*)dyn_table);
+ dyn->d_un.d_ptr = rld_map_addr + dyn_offset - shdrDynamic->sh_addr;
+ } else {
+ /* ELF file with DT_MIPS_RLD_MAP_REL but without .rld_map
+ is broken, and it's not our job to fix it; yet, we have
+ to find some location for dynamic loader to write the
+ debug pointer to; well, let's write it right here */
+ fprintf(stderr, "warning: DT_MIPS_RLD_MAP_REL entry is present, but .rld_map section is not\n");
+ dyn->d_un.d_ptr = 0;
+ }
+ }
}
diff --git a/tests/Makefile.am b/tests/Makefile.am
index 91a28ae..2196401 100644
--- a/tests/Makefile.am
+++ b/tests/Makefile.am
@@ -22,6 +22,7 @@ src_TESTS = \
plain-fail.sh plain-run.sh shrink-rpath.sh set-interpreter-short.sh \
set-interpreter-long.sh set-rpath.sh add-rpath.sh no-rpath.sh big-dynstr.sh \
set-rpath-library.sh soname.sh shrink-rpath-with-allowed-prefixes.sh \
+ set-rpath-rel-map.sh \
force-rpath.sh \
plain-needed.sh \
output-flag.sh \
diff --git a/tests/set-rpath-rel-map.sh b/tests/set-rpath-rel-map.sh
new file mode 100755
index 0000000..efc0943
--- /dev/null
+++ b/tests/set-rpath-rel-map.sh
@@ -0,0 +1,37 @@
+#! /bin/sh -e
+
+if ! objdump -p main | grep -q MIPS_RLD_MAP_REL; then
+ echo "No MIPS_RLD_MAP_REL dynamic section entry, skipping"
+ exit 0
+fi
+
+SCRATCH=scratch/$(basename $0 .sh)
+
+rm -rf ${SCRATCH}
+mkdir -p ${SCRATCH}
+mkdir -p ${SCRATCH}/libsA
+mkdir -p ${SCRATCH}/libsB
+
+cp main ${SCRATCH}/
+cp libfoo.so ${SCRATCH}/libsA/
+cp libbar.so ${SCRATCH}/libsB/
+
+# break the main executable by removing .rld_map section
+objcopy --remove-section .rld_map ${SCRATCH}/main
+
+oldRPath=$(../src/patchelf --print-rpath ${SCRATCH}/main)
+if test -z "$oldRPath"; then oldRPath="/oops"; fi
+../src/patchelf --force-rpath --set-rpath $oldRPath:$(pwd)/${SCRATCH}/libsA:$(pwd)/${SCRATCH}/libsB ${SCRATCH}/main
+
+if test "$(uname)" = FreeBSD; then
+ export LD_LIBRARY_PATH=$(pwd)/${SCRATCH}/libsB
+fi
+
+exitCode=0
+
+(cd ${SCRATCH} && ./main) || exitCode=$?
+
+if test "$exitCode" != 46; then
+ echo "bad exit code!"
+ exit 1
+fi