RFC: ld.so: Add DT_FLAGS_2 and DF_2_GNU_IFUNC [BZ #20019]

Message ID 20180525202634.GA23760@intel.com
State New, archived
Headers

Commit Message

Lu, Hongjiu May 25, 2018, 8:26 p.m. UTC
  As shown in

https://sourceware.org/bugzilla/show_bug.cgi?id=20019

it is possible to create a shared object which references an IFUNC
function defined in another shared object.  With non-lazy binding at
run-time, usage of such a shared object can lead to segfault.  The
issue is the shared object, which provides the IFUNC function, isn't
in DT_NEEDED and dynamic linker tries to resolve the reference to the
IFUNC function defined in the unrelocated shared object with non-lazy
binding.  Currently, the glibc dynamic linker issues a warning:

[hjl@gnu-cfl-1 pr20019]$ ./main-dynamic
./main-dynamic: Relink `./libbar.so' with `/export/build/gnu/glibc/build-x86_64-
linux/libc.so.6' for IFUNC symbol `memmove'
Segmentation fault
[hjl@gnu-cfl-1 pr20019]$

But it doesn't prevent segfault.  This can happen more often with
LD_PRELOAD of shared objects with IFUNC symbols on executables and
shared objects with DT_BIND_NOW.

This patch updates ld.so to check the new DT_FLAGS_2 and DF_2_GNU_IFUNC,
proposed at

https://sourceware.org/ml/binutils/2018-05/msg00264.html

which indicates that a shared object has IFUNC symbols and relocate it
first.

I also renamed l_feature_1 in link_map to l_flags_2 since it is unused.

	[BZ #20019]
	* elf/Makefile (tests): Add tst-reloc1.
	(test-xfail-tst-reloc1): New.
	(modules-names): Add tst-relocmod1a and tst-relocmod1b.
	($(objpfx)tst-reloc1): New.
	($(objpfx)tst-relocmod1b.so): Likewise.
	($(objpfx)tst-relocmod1a.so): Likewise.
	* elf/elf.h (DT_FLAGS_2): New macro.
	(DF_2_GNU_IFUNC) : New.
	* elf/get-dynamic-info.h (elf_get_dynamic_info): Also get
	DT_FLAGS_2.
	* elf/rtld.c (dl_main): Relocate shared objects with IFUNC
	symbols first.
	* elf/tst-reloc1.c: New file.
	* elf/tst-relocmod1a.c: Likewise.
	* elf/tst-relocmod1b.c: Likewise.
	* include/link.h (link_map): Rename l_feature_1 to l_flags_2.
---
 elf/Makefile           | 14 ++++++++++++--
 elf/elf.h              |  6 ++++++
 elf/get-dynamic-info.h |  2 ++
 elf/rtld.c             | 21 +++++++++++++++++++++
 elf/tst-reloc1.c       | 39 +++++++++++++++++++++++++++++++++++++++
 elf/tst-relocmod1a.c   | 23 +++++++++++++++++++++++
 elf/tst-relocmod1b.c   | 25 +++++++++++++++++++++++++
 include/link.h         |  2 +-
 8 files changed, 129 insertions(+), 3 deletions(-)
 create mode 100644 elf/tst-reloc1.c
 create mode 100644 elf/tst-relocmod1a.c
 create mode 100644 elf/tst-relocmod1b.c
  

Comments

Florian Weimer May 25, 2018, 9:52 p.m. UTC | #1
On 05/25/2018 10:26 PM, H.J. Lu wrote:
> +$(objpfx)tst-relocmod1b.so: $(objpfx)tst-relocmod1b.os \
> +			    $(objpfx)tst-relocmod1a.so
> +	$(LINK.o) -nostdlib -nostartfiles -shared -o $@ -Wl,-z,now \
> +		  $(filter-out $(shlib-lds),$^)

Does this still link against libc.so.6?  If not, then this is the bug.

> diff --git a/elf/tst-relocmod1a.c b/elf/tst-relocmod1a.c
> new file mode 100644
> index 0000000000..be9f8832b8
> --- /dev/null
> +++ b/elf/tst-relocmod1a.c
> @@ -0,0 +1,23 @@
> +/* Shared module to test for relocation over with IFUNC symbols.
> +   Copyright (C) 2018 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
> +<http://www.gnu.org/licenses/>.  */
> +
> +void
> +bar (char *dst, const char *src, unsigned int size)
> +{
> +  __builtin_memmove (dst, src, size);
> +}

What happens if you add an IFUNC resolver here?  Doesn't the problem 
return as before?

Thanks,
Florian
  
H.J. Lu May 25, 2018, 10:01 p.m. UTC | #2
On Fri, May 25, 2018 at 2:52 PM, Florian Weimer <fweimer@redhat.com> wrote:
> On 05/25/2018 10:26 PM, H.J. Lu wrote:
>>
>> +$(objpfx)tst-relocmod1b.so: $(objpfx)tst-relocmod1b.os \
>> +                           $(objpfx)tst-relocmod1a.so
>> +       $(LINK.o) -nostdlib -nostartfiles -shared -o $@ -Wl,-z,now \
>> +                 $(filter-out $(shlib-lds),$^)
>
>
> Does this still link against libc.so.6?  If not, then this is the bug.

It is not linked against libc.so.6 and it is done on purpose.

>
>> diff --git a/elf/tst-relocmod1a.c b/elf/tst-relocmod1a.c
>> new file mode 100644
>> index 0000000000..be9f8832b8
>> --- /dev/null
>> +++ b/elf/tst-relocmod1a.c
>> @@ -0,0 +1,23 @@
>> +/* Shared module to test for relocation over with IFUNC symbols.
>> +   Copyright (C) 2018 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
>> +<http://www.gnu.org/licenses/>.  */
>> +
>> +void
>> +bar (char *dst, const char *src, unsigned int size)
>> +{
>> +  __builtin_memmove (dst, src, size);
>> +}
>
>
> What happens if you add an IFUNC resolver here?  Doesn't the problem return
> as before?

https://sourceware.org/bugzilla/show_bug.cgi?id=23240
  
Florian Weimer May 25, 2018, 10:05 p.m. UTC | #3
On 05/26/2018 12:01 AM, H.J. Lu wrote:
> On Fri, May 25, 2018 at 2:52 PM, Florian Weimer <fweimer@redhat.com> wrote:
>> On 05/25/2018 10:26 PM, H.J. Lu wrote:
>>>
>>> +$(objpfx)tst-relocmod1b.so: $(objpfx)tst-relocmod1b.os \
>>> +                           $(objpfx)tst-relocmod1a.so
>>> +       $(LINK.o) -nostdlib -nostartfiles -shared -o $@ -Wl,-z,now \
>>> +                 $(filter-out $(shlib-lds),$^)
>>
>>
>> Does this still link against libc.so.6?  If not, then this is the bug.
> 
> It is not linked against libc.so.6 and it is done on purpose.

But this makes the test case invalid.

Florian
  
Carlos O'Donell May 28, 2018, 4:56 p.m. UTC | #4
On 05/25/2018 06:01 PM, H.J. Lu wrote:
> On Fri, May 25, 2018 at 2:52 PM, Florian Weimer <fweimer@redhat.com> wrote:
>> On 05/25/2018 10:26 PM, H.J. Lu wrote:
>>>
>>> +$(objpfx)tst-relocmod1b.so: $(objpfx)tst-relocmod1b.os \
>>> +                           $(objpfx)tst-relocmod1a.so
>>> +       $(LINK.o) -nostdlib -nostartfiles -shared -o $@ -Wl,-z,now \
>>> +                 $(filter-out $(shlib-lds),$^)
>>
>>
>> Does this still link against libc.so.6?  If not, then this is the bug.
> 
> It is not linked against libc.so.6 and it is done on purpose.

That's OK, but then you *cannot* call libc.so.6 functions, since you have
no dependency against the library, symbol versioning won't work correctly,
and the dynamic loader will not order initialization correctly (as you note).

Cheers,
Carlos.
  
H.J. Lu May 28, 2018, 7:52 p.m. UTC | #5
On Mon, May 28, 2018 at 9:56 AM, Carlos O'Donell <carlos@redhat.com> wrote:
> On 05/25/2018 06:01 PM, H.J. Lu wrote:
>> On Fri, May 25, 2018 at 2:52 PM, Florian Weimer <fweimer@redhat.com> wrote:
>>> On 05/25/2018 10:26 PM, H.J. Lu wrote:
>>>>
>>>> +$(objpfx)tst-relocmod1b.so: $(objpfx)tst-relocmod1b.os \
>>>> +                           $(objpfx)tst-relocmod1a.so
>>>> +       $(LINK.o) -nostdlib -nostartfiles -shared -o $@ -Wl,-z,now \
>>>> +                 $(filter-out $(shlib-lds),$^)
>>>
>>>
>>> Does this still link against libc.so.6?  If not, then this is the bug.
>>
>> It is not linked against libc.so.6 and it is done on purpose.
>
> That's OK, but then you *cannot* call libc.so.6 functions, since you have
> no dependency against the library, symbol versioning won't work correctly,
> and the dynamic loader will not order initialization correctly (as you note).
>

I withdrew this.
  

Patch

diff --git a/elf/Makefile b/elf/Makefile
index 2dcd2b88e0..7d78e28c9e 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -186,8 +186,9 @@  tests += restest1 preloadtest loadfail multiload origtest resolvfail \
 	 tst-tlsalign tst-tlsalign-extern tst-nodelete-opened \
 	 tst-nodelete2 tst-audit11 tst-audit12 tst-dlsym-error tst-noload \
 	 tst-latepthread tst-tls-manydynamic tst-nodelete-dlclose \
-	 tst-debug1 tst-main1 tst-absolute-sym tst-big-note
+	 tst-debug1 tst-main1 tst-absolute-sym tst-big-note tst-reloc1
 #	 reldep9
+test-xfail-tst-reloc1 = yes
 tests-internal += loadtest unload unload2 circleload1 \
 	 neededtest neededtest2 neededtest3 neededtest4 \
 	 tst-tls3 tst-tls6 tst-tls7 tst-tls8 tst-dlmopen2 \
@@ -273,7 +274,7 @@  modules-names = testobj1 testobj2 testobj3 testobj4 testobj5 testobj6 \
 		tst-latepthreadmod $(tst-tls-many-dynamic-modules) \
 		tst-nodelete-dlclose-dso tst-nodelete-dlclose-plugin \
 		tst-main1mod tst-libc_dlvsym-dso tst-absolute-sym-lib \
-		tst-big-note-lib
+		tst-big-note-lib tst-relocmod1a tst-relocmod1b
 
 ifeq (yes,$(have-mtls-dialect-gnu2))
 tests += tst-gnu2-tls1
@@ -1454,3 +1455,12 @@  tst-libc_dlvsym-static-ENV = \
 $(objpfx)tst-libc_dlvsym-static.out: $(objpfx)tst-libc_dlvsym-dso.so
 
 $(objpfx)tst-big-note: $(objpfx)tst-big-note-lib.so
+
+$(objpfx)tst-reloc1: $(objpfx)tst-relocmod1b.so
+$(objpfx)tst-relocmod1b.so: $(objpfx)tst-relocmod1b.os \
+			    $(objpfx)tst-relocmod1a.so
+	$(LINK.o) -nostdlib -nostartfiles -shared -o $@ -Wl,-z,now \
+		  $(filter-out $(shlib-lds),$^)
+$(objpfx)tst-relocmod1a.so: $(objpfx)tst-relocmod1a.os
+	$(LINK.o) -nostdlib -nostartfiles -shared -o $@ -Wl,-z,now \
+		  $(filter-out $(shlib-lds),$^)
diff --git a/elf/elf.h b/elf/elf.h
index a5b2811ce0..3e32522532 100644
--- a/elf/elf.h
+++ b/elf/elf.h
@@ -925,6 +925,8 @@  typedef struct
    GNU extension.  */
 #define DT_VERSYM	0x6ffffff0
 
+#define DT_FLAGS_2	0x6ffffff1	/* State flags, see DF_2_* below.  */
+
 #define DT_RELACOUNT	0x6ffffff9
 #define DT_RELCOUNT	0x6ffffffa
 
@@ -984,6 +986,10 @@  typedef struct
 #define	DF_1_STUB	0x04000000
 #define	DF_1_PIE	0x08000000
 
+/* State flags selectable in the `d_un.d_val' element of the DT_FLAGS_2
+   entry in the dynamic section.  */
+#define DF_2_GNU_IFUNC	0x00000001	/* Object has GNU_IFUNC symbols */
+
 /* Flags for the feature selection in DT_FEATURE_1.  */
 #define DTF_1_PARINIT	0x00000001
 #define DTF_1_CONFEXP	0x00000002
diff --git a/elf/get-dynamic-info.h b/elf/get-dynamic-info.h
index 4b1ea7c407..1cea375c40 100644
--- a/elf/get-dynamic-info.h
+++ b/elf/get-dynamic-info.h
@@ -178,6 +178,8 @@  elf_get_dynamic_info (struct link_map *l, ElfW(Dyn) *temp)
       if (l->l_flags_1 & DF_1_NOW)
 	info[DT_BIND_NOW] = info[VERSYMIDX (DT_FLAGS_1)];
     }
+  if (info[VERSYMIDX (DT_FLAGS_2)] != NULL)
+    l->l_flags_2 = info[VERSYMIDX (DT_FLAGS_2)]->d_un.d_val;
   if (info[DT_RUNPATH] != NULL)
     /* If both RUNPATH and RPATH are given, the latter is ignored.  */
     info[DT_RPATH] = NULL;
diff --git a/elf/rtld.c b/elf/rtld.c
index e7681ebb1f..91fb91ef7f 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -2183,6 +2183,27 @@  ERROR: ld.so: object '%s' cannot be loaded as audit interface: %s; ignored.\n",
 	  /* Also allocated with the fake malloc().  */
 	  l->l_free_initfini = 0;
 
+	  /* Relocate shared object with IFUNC symbols first.  */
+	  if (!l->l_name[0] || !(l->l_flags_2 & DF_2_GNU_IFUNC))
+	    continue;
+
+	  if (l != &GL(dl_rtld_map))
+	    _dl_relocate_object (l, l->l_scope, GLRO(dl_lazy) ? RTLD_LAZY : 0,
+				 consider_profiling);
+
+	  /* Add object to slot information data if necessasy.  */
+	  if (l->l_tls_blocksize != 0 && tls_init_tp_called)
+	    _dl_add_to_slotinfo (l);
+	}
+
+      i = main_map->l_searchlist.r_nlist;
+      while (i-- > 0)
+	{
+	  struct link_map *l = main_map->l_initfini[i];
+
+	  if (l->l_relocated)
+	    continue;
+
 	  if (l != &GL(dl_rtld_map))
 	    _dl_relocate_object (l, l->l_scope, GLRO(dl_lazy) ? RTLD_LAZY : 0,
 				 consider_profiling);
diff --git a/elf/tst-reloc1.c b/elf/tst-reloc1.c
new file mode 100644
index 0000000000..5867a638f4
--- /dev/null
+++ b/elf/tst-reloc1.c
@@ -0,0 +1,39 @@ 
+/* Test for relocation over with IFUNC symbols.
+   Copyright (C) 2018 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
+   <http://www.gnu.org/licenses/>.  */
+
+#include <support/check.h>
+#include <support/support.h>
+#include <support/test-driver.h>
+
+extern void foo (char *, const char *, unsigned int);
+
+static int
+do_test (void)
+{
+  char dst[50];
+  const char src[] =
+    {
+      "This is a test"
+    };
+  foo (dst, src, sizeof (src));
+  if (__builtin_memcmp (dst, src, sizeof (src)) != 0)
+    __builtin_abort ();
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/elf/tst-relocmod1a.c b/elf/tst-relocmod1a.c
new file mode 100644
index 0000000000..be9f8832b8
--- /dev/null
+++ b/elf/tst-relocmod1a.c
@@ -0,0 +1,23 @@ 
+/* Shared module to test for relocation over with IFUNC symbols.
+   Copyright (C) 2018 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
+   <http://www.gnu.org/licenses/>.  */
+
+void
+bar (char *dst, const char *src, unsigned int size)
+{
+  __builtin_memmove (dst, src, size);
+}
diff --git a/elf/tst-relocmod1b.c b/elf/tst-relocmod1b.c
new file mode 100644
index 0000000000..0424dc8b36
--- /dev/null
+++ b/elf/tst-relocmod1b.c
@@ -0,0 +1,25 @@ 
+/* Shared module to test for relocation over with IFUNC symbols.
+   Copyright (C) 2018 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
+   <http://www.gnu.org/licenses/>.  */
+
+extern void bar (char *dst, const char *src, unsigned int);
+
+void
+foo (char *dst, const char *src, unsigned int size)
+{
+  bar (dst, src, size);
+}
diff --git a/include/link.h b/include/link.h
index 5924594548..6acf4e0493 100644
--- a/include/link.h
+++ b/include/link.h
@@ -264,8 +264,8 @@  struct link_map
     unsigned int l_used;
 
     /* Various flag words.  */
-    ElfW(Word) l_feature_1;
     ElfW(Word) l_flags_1;
+    ElfW(Word) l_flags_2;
     ElfW(Word) l_flags;
 
     /* Temporarily used in `dl_close'.  */