This is the mail archive of the libc-alpha@sourceware.org mailing list for the glibc project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

[PATCH] Correct global-scope dlopen issues in static executables


Hi,

 We have two issues with dlopen issued from static executables when the 
global scope is accessed.

 First, an attempt to add a dynamic shared module to the global scope 
(i.e. with RTLD_GLOBAL requested) crashes like this:

Program received signal SIGSEGV, Segmentation fault.
add_to_global (new=0x49ef60) at dl-open.c:101
101           ns->_ns_global_scope_alloc

This is because at this point in static executables no global search list 
data structure has been initialised and as result the _ns_main_searchlist 
member of _dl_ns[LM_ID_BASE] (the initial namespace) is null.

 Second, an attempt to access the global scope itself (i.e. by using a 
null file name pointer) fails with an obscure error like:

/path/to/dir/: cannot read file data: Is a directory

(that can be retrieved with dlerror), where /path/to/dir/ is the first 
directory searched according to the dynamic load engine's shared module 
lookup policy.  This is because the null file name pointer is internally 
converted to an empty string, that is in turn looked for and then opened 
in the directories examined.  Of course that succeeds -- the directory 
itself is opened, but then the engine gets confused by the outcome and 
bails out.  The use of an empty string works in dynamic executables, 
because for them the dynamic loader has already prepared a link map 
referring to a file whose name is empty -- which, by means of how that 
code works, is the executable itself -- and that makes the loader abandon 
any file look-ups and use the link map already present.

 The two failure modes are illustrated by the two new programs included as 
new test suite cases below -- tststatic3 covers the first issue and 
tststatic4 covers the second issue described above, respectively.

 I have decided to make a new minimal global search list in static 
startup.  This list is used to access the global scope as where the 
pointer to a file name requested is null.  This guarantees that such 
dlopen calls succeed.  Of course, as static executables do not export 
dynamic symbols, any symbol lookup requests made with the handle returned 
are going to fail.

 However, once a dynamic shared module has been successfully loaded into 
the global scope, they are made available via any handles referring to the 
global scope, including ones previously obtained.  This is also covered by 
tststatic4.

 To further ensure correct operation, I have chosen to use the getpagesize 
call and compare the return value obtained from this call directly to one 
got via a call to a module loaded at the run time.  I chose that call 
deliberately, because it has interesting properties on platforms whose 
page size can only be determined at the run time and is passed to user 
startup by the kernel via the auxiliary vector.  This requires a manual 
setup in shared modules loaded dynamically from static binaries, the very 
operation being covered here.  Code that we have to handle this was not 
covered by any piece of our test suite until now, so that's additional 
value provided by these test cases.  The platforms affected are MIPS and 
IA-64.

 And the two final implementation notes.

 First I chose to use assertions in code that allocates the global search 
list, following similar code in the dynamic loader.  That may be 
suboptimal although unlikely to fail, but I found no previous art within 
our sources so as to how handle failures of this kind.  If that is not 
acceptable, then I'll gladly hear suggestions how to handle it otherwise.  

 Second, as _dl_static_init is now called whenever the global scope is 
accessed with dlopen, it has to return in that case right away, as there 
is nothing to reinitialise for the executable itself.  And otherwise the 
call to look up _dl_var_init would of course fail wreaking havoc.  I made 
the same change for both the MIPS and the IA-64 target.  Regrettably I 
have no way to test the latter target, but the two implementations of 
_dl_static_init closely mimic each other, so I believe once testing has 
been done for either target, the change to the other can be considered 
obviously correct.

 I have pushed the changes through regression testing for the MIPS/Linux 
target (where the problem was originally spotted), including the usual 
three ABIs that we support (o32, n64 and n32), as well as the i386/Linux 
and x86-64/Linux targets (to have coverage for "mainstream" non-ports 
targets).  There have been no regressions and the new tests passed.  They 
did score failures when applied on their own without the fix proper, 
ensuring proper coverage.

 Please apply.

2013-01-16  Maciej W. Rozycki  <macro@codesourcery.com>

	ChangeLog:
	* csu/libc-start.c (LIBC_START_MAIN) [!SHARED]: Prepare a global 
	search list.
	* dlfcn/modstatic3.c: New file.
	* dlfcn/tststatic3.c: New file.
	* dlfcn/tststatic4.c: New file.
	* dlfcn/Makefile (tests): Add tststatic3 and tststatic4.
	(tests-static): Likewise.
	(modules-names): Add modstatic3.
	(tststatic3-ENV, tststatic4-ENV): New variables.
	($(objpfx)tststatic3, $(objpfx)tststatic3.out): New dependencies.
	($(objpfx)tststatic4, $(objpfx)tststatic4.out): Likewise.

	ports/ChangeLog.ia64:
	* sysdeps/unix/sysv/linux/ia64/dl-static.c (_dl_static_init):
	Exit right away if opening self.

	ports/ChangeLog.mips:
	* sysdeps/unix/sysv/linux/mips/dl-static.c (_dl_static_init):
	Exit right away if opening self.

  Maciej

glibc-static-dlopen.diff
Index: glibc-fsf-trunk-quilt/csu/libc-start.c
===================================================================
--- glibc-fsf-trunk-quilt.orig/csu/libc-start.c	2013-01-16 00:04:04.000000000 +0000
+++ glibc-fsf-trunk-quilt/csu/libc-start.c	2013-01-16 00:23:14.096521716 +0000
@@ -15,6 +15,7 @@
    License along with the GNU C Library; if not, see
    <http://www.gnu.org/licenses/>.  */
 
+#include <assert.h>
 #include <stdlib.h>
 #include <stdio.h>
 #include <unistd.h>
@@ -177,6 +178,26 @@ LIBC_START_MAIN (int (*main) (int, char 
      we need to setup errno.  */
   __pthread_initialize_minimal ();
 
+  /* Create a dummy link_map for the executable, used by dlopen to
+     access the global scope.  We don't export any symbols ourselves,
+     so this can be minimal.  */
+  struct link_map **new_global;
+  struct link_map *main_map;
+
+  main_map = _dl_new_object ("", "", lt_executable, NULL, 0, LM_ID_BASE);
+  assert (main_map != NULL);
+
+  _dl_add_to_namespace_list (main_map, LM_ID_BASE);
+  assert (main_map == GL(dl_ns)[LM_ID_BASE]._ns_loaded);
+  GL(dl_nns) = 1;
+
+  new_global = malloc (sizeof (struct link_map **));
+  assert (new_global != NULL);
+  *new_global = main_map;
+  main_map->l_searchlist.r_list = new_global;
+  main_map->l_searchlist.r_nlist = 1;
+  GL(dl_ns)[LM_ID_BASE]._ns_main_searchlist = &main_map->l_searchlist;
+
   /* Set up the stack checker's canary.  */
   uintptr_t stack_chk_guard = _dl_setup_stack_chk_guard (_dl_random);
 # ifdef THREAD_SET_STACK_GUARD
Index: glibc-fsf-trunk-quilt/dlfcn/Makefile
===================================================================
--- glibc-fsf-trunk-quilt.orig/dlfcn/Makefile	2013-01-16 00:04:04.000000000 +0000
+++ glibc-fsf-trunk-quilt/dlfcn/Makefile	2013-01-16 03:31:52.096586114 +0000
@@ -47,11 +47,13 @@ glreflib2.so-no-z-defs = yes
 errmsg1mod.so-no-z-defs = yes
 
 ifeq (yes,$(build-shared))
-tests += tststatic tststatic2
-tests-static += tststatic tststatic2
-modules-names += modstatic modstatic2
+tests += tststatic tststatic2 tststatic3 tststatic4
+tests-static += tststatic tststatic2 tststatic3 tststatic4
+modules-names += modstatic modstatic2 modstatic3
 tststatic-ENV = LD_LIBRARY_PATH=$(objpfx):$(common-objpfx):$(common-objpfx)elf
 tststatic2-ENV = LD_LIBRARY_PATH=$(objpfx):$(common-objpfx):$(common-objpfx)elf
+tststatic3-ENV = LD_LIBRARY_PATH=$(objpfx):$(common-objpfx):$(common-objpfx)elf
+tststatic4-ENV = LD_LIBRARY_PATH=$(objpfx):$(common-objpfx):$(common-objpfx)elf
 endif
 
 extra-test-objs += $(modules-names:=.os)
@@ -104,6 +106,12 @@ $(objpfx)tststatic2.out: $(objpfx)tststa
 
 $(objpfx)modstatic2.so: $(libdl)
 
+$(objpfx)tststatic3: $(objpfx)libdl.a
+$(objpfx)tststatic3.out: $(objpfx)tststatic3 $(objpfx)modstatic3.so
+
+$(objpfx)tststatic4: $(objpfx)libdl.a
+$(objpfx)tststatic4.out: $(objpfx)tststatic4 $(objpfx)modstatic3.so
+
 $(objpfx)bug-dlopen1: $(libdl)
 
 $(objpfx)bug-dlsym1: $(libdl) $(objpfx)bug-dlsym1-lib2.so
Index: glibc-fsf-trunk-quilt/dlfcn/modstatic3.c
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ glibc-fsf-trunk-quilt/dlfcn/modstatic3.c	2013-01-16 00:23:14.096521716 +0000
@@ -0,0 +1,24 @@
+/* Copyright (C) 2013 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 <unistd.h>
+
+int
+my_getpagesize (void)
+{
+  return getpagesize ();
+}
Index: glibc-fsf-trunk-quilt/dlfcn/tststatic3.c
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ glibc-fsf-trunk-quilt/dlfcn/tststatic3.c	2013-01-16 01:25:30.917783162 +0000
@@ -0,0 +1,59 @@
+/* Copyright (C) 2013 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 <dlfcn.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <unistd.h>
+
+static int
+do_test (void)
+{
+  int pagesize = getpagesize ();
+  int (*my_getpagesize) (void);
+  int my_pagesize;
+  void *handle;
+
+  handle = dlopen ("modstatic3.so", RTLD_LAZY | RTLD_GLOBAL);
+  if (handle == NULL)
+    {
+      printf ("%s\n", dlerror ());
+      return 1;
+    }
+
+  my_getpagesize = dlsym (handle, "my_getpagesize");
+  if (my_getpagesize == NULL)
+    {
+      printf ("%s\n", dlerror ());
+      return 1;
+    }
+
+  my_pagesize = my_getpagesize ();
+  if (my_pagesize != pagesize)
+    {
+      printf ("Got %i, expected %i\n", my_pagesize, pagesize);
+      return 1;
+    }
+
+  my_getpagesize = NULL;
+  dlclose (handle);
+
+  return 0;
+}
+
+#define TEST_FUNCTION do_test ()
+#include "../test-skeleton.c"
Index: glibc-fsf-trunk-quilt/dlfcn/tststatic4.c
===================================================================
--- /dev/null	1970-01-01 00:00:00.000000000 +0000
+++ glibc-fsf-trunk-quilt/dlfcn/tststatic4.c	2013-01-16 03:44:31.237651366 +0000
@@ -0,0 +1,120 @@
+/* Copyright (C) 2013 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 <dlfcn.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <unistd.h>
+
+static int
+do_test (void)
+{
+  int (*my_initial_getpagesize) (void);
+  int (*my_global_getpagesize) (void);
+  int (*my_local_getpagesize) (void);
+  int pagesize = getpagesize ();
+  int my_initial_pagesize;
+  int my_global_pagesize;
+  int my_local_pagesize;
+  void *global_handle;
+  void *local_handle;
+  void *initial_handle;
+
+  initial_handle = dlopen (NULL, RTLD_LAZY | RTLD_GLOBAL);
+  if (initial_handle == NULL)
+    {
+      printf ("%s\n", dlerror ());
+      return 1;
+    }
+
+  my_initial_getpagesize = dlsym (initial_handle, "my_getpagesize");
+  if (my_initial_getpagesize != NULL)
+    {
+      printf ("Got %p, expected NULL\n", my_initial_getpagesize);
+      return 1;
+    }
+
+  global_handle = dlopen ("modstatic3.so", RTLD_LAZY | RTLD_GLOBAL);
+  if (global_handle == NULL)
+    {
+      printf ("%s\n", dlerror ());
+      return 1;
+    }
+
+  my_global_getpagesize = dlsym (global_handle, "my_getpagesize");
+  if (my_global_getpagesize == NULL)
+    {
+      printf ("%s\n", dlerror ());
+      return 1;
+    }
+
+  local_handle = dlopen (NULL, RTLD_LAZY | RTLD_LOCAL);
+  if (local_handle == NULL)
+    {
+      printf ("%s\n", dlerror ());
+      return 1;
+    }
+
+  my_local_getpagesize = dlsym (local_handle, "my_getpagesize");
+  if (my_local_getpagesize == NULL)
+    {
+      printf ("%s\n", dlerror ());
+      return 1;
+    }
+
+  my_initial_getpagesize = dlsym (initial_handle, "my_getpagesize");
+  if (my_initial_getpagesize == NULL)
+    {
+      printf ("%s\n", dlerror ());
+      return 1;
+    }
+
+  my_initial_pagesize = my_initial_getpagesize ();
+  if (my_initial_pagesize != pagesize)
+    {
+      printf ("Got %i, expected %i\n", my_initial_pagesize, pagesize);
+      return 1;
+    }
+
+  my_global_pagesize = my_global_getpagesize ();
+  if (my_global_pagesize != pagesize)
+    {
+      printf ("Got %i, expected %i\n", my_global_pagesize, pagesize);
+      return 1;
+    }
+
+  my_local_pagesize = my_local_getpagesize ();
+  if (my_local_pagesize != pagesize)
+    {
+      printf ("Got %i, expected %i\n", my_local_pagesize, pagesize);
+      return 1;
+    }
+
+  my_local_getpagesize = NULL;
+  dlclose (local_handle);
+
+  my_global_getpagesize = NULL;
+  dlclose (global_handle);
+
+  my_initial_getpagesize = NULL;
+  dlclose (initial_handle);
+
+  return 0;
+}
+
+#define TEST_FUNCTION do_test ()
+#include "../test-skeleton.c"
Index: glibc-fsf-trunk-quilt/ports/sysdeps/unix/sysv/linux/ia64/dl-static.c
===================================================================
--- glibc-fsf-trunk-quilt.orig/ports/sysdeps/unix/sysv/linux/ia64/dl-static.c	2013-01-16 00:04:04.000000000 +0000
+++ glibc-fsf-trunk-quilt/ports/sysdeps/unix/sysv/linux/ia64/dl-static.c	2013-01-16 00:23:14.117747200 +0000
@@ -52,6 +52,10 @@ _dl_static_init (struct link_map *map)
   lookup_t loadbase;
   void (*f) (void *[]);
 
+  /* Nothing to do if opening self.  */
+  if (__builtin_expect (map->l_name[0] == '\0', 0))
+    return;
+
   __libc_lock_lock_recursive (_dl_static_lock);
 
   loadbase = _dl_lookup_symbol_x ("_dl_var_init", map, &ref,
Index: glibc-fsf-trunk-quilt/ports/sysdeps/unix/sysv/linux/mips/dl-static.c
===================================================================
--- glibc-fsf-trunk-quilt.orig/ports/sysdeps/unix/sysv/linux/mips/dl-static.c	2013-01-16 00:04:04.000000000 +0000
+++ glibc-fsf-trunk-quilt/ports/sysdeps/unix/sysv/linux/mips/dl-static.c	2013-01-16 00:23:14.137773938 +0000
@@ -64,6 +64,10 @@ _dl_static_init (struct link_map *l)
   void (*f) (void *[]);
   size_t i;
 
+  /* Nothing to do if opening self.  */
+  if (__builtin_expect (l->l_name[0] == '\0', 0))
+    return;
+
   __libc_lock_lock_recursive (_dl_static_lock);
 
   loadbase = _dl_lookup_symbol_x ("_dl_var_init", l, &ref, l->l_local_scope,


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]