unofficial mirror of libc-alpha@sourceware.org
 help / color / mirror / Atom feed
From: Siddhesh Poyarekar via Libc-alpha <libc-alpha@sourceware.org>
To: libc-alpha@sourceware.org
Cc: fweimer@redhat.com
Subject: [PATCH v2] ld.so: Handle read-only dynamic section gracefully [BZ #28340]
Date: Wed, 15 Sep 2021 07:06:53 +0530	[thread overview]
Message-ID: <20210915013653.1802776-1-siddhesh@sourceware.org> (raw)

ld.so on x86_64 (and possibly for all other architectures that don't
have a read-only dynamic section by default but I haven't checked on
all of them), when invoked with a DSO that has a read-only .dynamic
section, crashes when trying to update gotplt, symtab, etc. entries
with the load address.  This crash happens even during verification,
i.e. when it is invoked with --verify or with LD_TRACE_LOADED_OBJECTS.

This is seen with vdso64.so from the Linux kernel on x86_64 for
example.

Handle this case more gracefully by bailing out early when a read-only
PT_DYNAMIC segment is encountered and l_addr is non-zero, indicating
the need for fixing up entries in .dynamic.  A test case has also been
added to verify that BZ #28340 has been fixed.
---
Changes from v1:
- Assume that all programs with a .dynamic section need a fixup if
  l_addr != 0.  Drop elf_dynamic_info_needs_fixup()  to that effect.
- Rename tst-ro-dynamic.map to tst-ro-dynamic-mod.map for consistency.

 elf/Makefile               | 11 +++++++++--
 elf/dl-load.c              | 14 ++++++++++++++
 elf/rtld.c                 | 12 ++++++++++++
 elf/tst-ro-dynamic-mod.c   |  1 +
 elf/tst-ro-dynamic-mod.map | 13 +++++++++++++
 elf/tst-ro-dynamic.sh      | 36 ++++++++++++++++++++++++++++++++++++
 6 files changed, 85 insertions(+), 2 deletions(-)
 create mode 100644 elf/tst-ro-dynamic-mod.c
 create mode 100644 elf/tst-ro-dynamic-mod.map
 create mode 100644 elf/tst-ro-dynamic.sh

diff --git a/elf/Makefile b/elf/Makefile
index 9f3fadc37e..406bb3ad23 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -223,7 +223,7 @@ tests += restest1 preloadtest loadfail multiload origtest resolvfail \
 	 tst-tls-ie tst-tls-ie-dlmopen argv0test \
 	 tst-glibc-hwcaps tst-glibc-hwcaps-prepend tst-glibc-hwcaps-mask \
 	 tst-tls20 tst-tls21 tst-dlmopen-dlerror tst-dlmopen-gethostbyname \
-	 tst-dl-is_dso
+	 tst-dl-is_dso tst-ro-dynamic
 #	 reldep9
 tests-internal += loadtest unload unload2 circleload1 \
 	 neededtest neededtest2 neededtest3 neededtest4 \
@@ -359,7 +359,7 @@ modules-names = testobj1 testobj2 testobj3 testobj4 testobj5 testobj6 \
 		libmarkermod4-1 libmarkermod4-2 libmarkermod4-3 libmarkermod4-4 \
 		tst-tls20mod-bad tst-tls21mod tst-dlmopen-dlerror-mod \
 		tst-auxvalmod \
-		tst-dlmopen-gethostbyname-mod \
+		tst-dlmopen-gethostbyname-mod tst-ro-dynamic-mod \
 
 # Most modules build with _ISOMAC defined, but those filtered out
 # depend on internal headers.
@@ -1908,3 +1908,10 @@ $(objpfx)tst-getauxval-static.out: $(objpfx)tst-auxvalmod.so
 tst-getauxval-static-ENV = LD_LIBRARY_PATH=$(objpfx):$(common-objpfx)
 
 $(objpfx)tst-dlmopen-gethostbyname.out: $(objpfx)tst-dlmopen-gethostbyname-mod.so
+
+LDFLAGS-tst-ro-dynamic-mod = -Wl,--version-script=tst-ro-dynamic-mod.map
+$(objpfx)tst-ro-dynamic-mod.so: tst-ro-dynamic-mod.map
+$(objpfx)tst-ro-dynamic.out: tst-ro-dynamic.sh $(objpfx)tst-ro-dynamic-mod.so \
+			     $(objpfx)ld.so
+	$(SHELL) $^ '$(test-wrapper-env)' '$(run_program_env)' > $@; \
+	$(evaluate-test)
diff --git a/elf/dl-load.c b/elf/dl-load.c
index 650e4edc35..c33da0467e 100644
--- a/elf/dl-load.c
+++ b/elf/dl-load.c
@@ -1124,6 +1124,9 @@ _dl_map_object_from_fd (const char *name, const char *origname, int fd,
     * executable.  Other platforms default to a nonexecutable stack and don't
     * need PT_GNU_STACK to do so.  */
    uint_fast16_t stack_flags = DEFAULT_STACK_PERMS;
+#ifndef DL_RO_DYN_SECTION
+  bool readonly_dynamic = false;
+#endif
 
   {
     /* Scan the program header table, collecting its load commands.  */
@@ -1149,6 +1152,9 @@ _dl_map_object_from_fd (const char *name, const char *origname, int fd,
 		 such a segment to avoid a crash later.  */
 	      l->l_ld = (void *) ph->p_vaddr;
 	      l->l_ldnum = ph->p_memsz / sizeof (ElfW(Dyn));
+#ifndef DL_RO_DYN_SECTION
+	      readonly_dynamic = (ph->p_flags & PF_W) == 0;
+#endif
 	    }
 	  break;
 
@@ -1292,6 +1298,14 @@ _dl_map_object_from_fd (const char *name, const char *origname, int fd,
   else
     l->l_ld = (ElfW(Dyn) *) ((ElfW(Addr)) l->l_ld + l->l_addr);
 
+#ifndef DL_RO_DYN_SECTION
+  if (readonly_dynamic && l->l_addr != 0)
+    {
+      errstring = N_("dynamic section of object file not writable");
+      goto lose;
+    }
+#endif
+
   elf_get_dynamic_info (l, NULL);
 
   /* Make sure we are not dlopen'ing an object that has the
diff --git a/elf/rtld.c b/elf/rtld.c
index 878e6480f4..d8149f2bca 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -1456,6 +1456,10 @@ dl_main (const ElfW(Phdr) *phdr,
   /* And it was opened directly.  */
   ++main_map->l_direct_opencount;
 
+#ifndef DL_RO_DYN_SECTION
+  bool readonly_dynamic = false;
+#endif
+
   /* Scan the program header table for the dynamic section.  */
   for (ph = phdr; ph < &phdr[phnum]; ++ph)
     switch (ph->p_type)
@@ -1468,6 +1472,9 @@ dl_main (const ElfW(Phdr) *phdr,
 	/* This tells us where to find the dynamic section,
 	   which tells us everything we need to do.  */
 	main_map->l_ld = (void *) main_map->l_addr + ph->p_vaddr;
+#ifndef DL_RO_DYN_SECTION
+	readonly_dynamic = (ph->p_flags & PF_W) == 0;
+#endif
 	break;
       case PT_INTERP:
 	/* This "interpreter segment" was used by the program loader to
@@ -1612,6 +1619,11 @@ dl_main (const ElfW(Phdr) *phdr,
 
   if (! rtld_is_main)
     {
+#ifndef DL_RO_DYN_SECTION
+      if (readonly_dynamic && main_map->l_addr != 0)
+	_dl_fatal_printf ("dynamic section of executable is not writable\n");
+#endif
+
       /* Extract the contents of the dynamic section for easy access.  */
       elf_get_dynamic_info (main_map, NULL);
 
diff --git a/elf/tst-ro-dynamic-mod.c b/elf/tst-ro-dynamic-mod.c
new file mode 100644
index 0000000000..2bc87d0c3c
--- /dev/null
+++ b/elf/tst-ro-dynamic-mod.c
@@ -0,0 +1 @@
+int foo = 0;
diff --git a/elf/tst-ro-dynamic-mod.map b/elf/tst-ro-dynamic-mod.map
new file mode 100644
index 0000000000..dac92e0b94
--- /dev/null
+++ b/elf/tst-ro-dynamic-mod.map
@@ -0,0 +1,13 @@
+SECTIONS
+{
+  .data : { *(.data) } :text
+  .bss : { *(.bss) } :text
+  .dynamic : { *(.dynamic) } :text :dynamic
+  .text : { *(.text) } :text
+}
+
+PHDRS
+{
+  text PT_LOAD FLAGS(5) FILEHDR PHDRS;
+  dynamic PT_DYNAMIC FLAGS(4);
+}
diff --git a/elf/tst-ro-dynamic.sh b/elf/tst-ro-dynamic.sh
new file mode 100644
index 0000000000..3d7144e0e2
--- /dev/null
+++ b/elf/tst-ro-dynamic.sh
@@ -0,0 +1,36 @@
+#!/bin/sh
+# Ensure ld.so does not crash when verifying DSO with read-only dynamic
+# section.
+# Copyright (C) 2021 Free Software Foundation, Inc.
+# This file is part of the GNU C Library.
+#
+# The GNU C Library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+#
+# The GNU C Library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public
+# License along with the GNU C Library; if not, see
+# <https://www.gnu.org/licenses/>.
+
+dso=$1
+rtld=$2
+test_wrapper_env=$3
+run_program_env=$4
+
+${test_wrapper_env} \
+${run_program_env} \
+$rtld --verify ${dso}
+
+ret=$?
+
+# ld.so should fail gracefully by returning 1.
+if [ $ret -ne 1 ]; then
+  echo "ld.so returned $ret"
+  exit 1
+fi
-- 
2.31.1


             reply	other threads:[~2021-09-15  1:37 UTC|newest]

Thread overview: 5+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2021-09-15  1:36 Siddhesh Poyarekar via Libc-alpha [this message]
2021-09-15 10:40 ` [PATCH v2] ld.so: Handle read-only dynamic section gracefully [BZ #28340] Florian Weimer via Libc-alpha
2021-09-15 12:10   ` Siddhesh Poyarekar via Libc-alpha
2021-09-15 12:18     ` Florian Weimer via Libc-alpha
2021-09-15 13:04       ` Siddhesh Poyarekar via Libc-alpha

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.gnu.org/software/libc/involved.html

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20210915013653.1802776-1-siddhesh@sourceware.org \
    --to=libc-alpha@sourceware.org \
    --cc=fweimer@redhat.com \
    --cc=siddhesh@sourceware.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).