From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: (qmail 21076 invoked by alias); 16 Jan 2013 18:13:12 -0000 Received: (qmail 21051 invoked by uid 22791); 16 Jan 2013 18:13:10 -0000 X-SWARE-Spam-Status: No, hits=-3.7 required=5.0 tests=AWL,BAYES_00,KHOP_RCVD_UNTRUST,RCVD_IN_HOSTKARMA_W,RCVD_IN_HOSTKARMA_WL X-Spam-Check-By: sourceware.org Received: from relay1.mentorg.com (HELO relay1.mentorg.com) (192.94.38.131) by sourceware.org (qpsmtpd/0.43rc1) with ESMTP; Wed, 16 Jan 2013 18:13:03 +0000 Received: from svr-orw-exc-10.mgc.mentorg.com ([147.34.98.58]) by relay1.mentorg.com with esmtp id 1TvXTz-000122-4Q from Maciej_Rozycki@mentor.com ; Wed, 16 Jan 2013 10:13:03 -0800 Received: from SVR-IES-FEM-01.mgc.mentorg.com ([137.202.0.104]) by SVR-ORW-EXC-10.mgc.mentorg.com with Microsoft SMTPSVC(6.0.3790.4675); Wed, 16 Jan 2013 10:13:02 -0800 Received: from [172.30.3.183] (137.202.0.76) by SVR-IES-FEM-01.mgc.mentorg.com (137.202.0.104) with Microsoft SMTP Server id 14.1.289.1; Wed, 16 Jan 2013 18:13:00 +0000 Date: Wed, 16 Jan 2013 18:13:00 -0000 From: "Maciej W. Rozycki" To: , Subject: [PATCH] Correct global-scope dlopen issues in static executables Message-ID: User-Agent: Alpine 1.10 (DEB 962 2008-03-14) MIME-Version: 1.0 Content-Type: text/plain; charset="US-ASCII" Mailing-List: contact libc-ports-help@sourceware.org; run by ezmlm Precedence: bulk List-Id: List-Subscribe: List-Post: List-Help: , Sender: libc-ports-owner@sourceware.org X-SW-Source: 2013-01/txt/msg00031.txt.bz2 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 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 . */ +#include #include #include #include @@ -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 + . */ + +#include + +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 + . */ + +#include +#include +#include +#include + +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 + . */ + +#include +#include +#include +#include + +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,