[v5,16/22] elf: Add main application on main_map l_name

Message ID 20211109183347.2943786-17-adhemerval.zanella@linaro.org
State Superseded
Headers
Series Multiple rtld-audit fixes |

Checks

Context Check Description
dj/TryBot-apply_patch success Patch applied to master at the time it was sent

Commit Message

Adhemerval Zanella Nov. 9, 2021, 6:33 p.m. UTC
  For the la_objopen() the path listed in link_map->l_name for the main
application binary is empty, even though dladdr() is able to recover the
correct path.

By setting the expected application name on l_name, it allows to
simplify the code to handle the special case and make the API that
return such information (la_objopen(), dladdr() and dlinfo() to have
similar semantic.

Checked on x86_64-linux-gnu.
---
 dlfcn/Makefile          |  4 ++-
 dlfcn/tst-dladdr-self.c | 55 +++++++++++++++++++++++++++++++++++++++++
 elf/dl-addr.c           |  5 ----
 elf/dl-dst.h            |  2 +-
 elf/dl-init.c           |  3 +--
 elf/dl-misc.c           |  1 +
 elf/rtld.c              |  6 +----
 elf/tst-audit22.c       |  4 +++
 elf/tst-auditmod22.c    |  8 ++++++
 gmon/gmon.c             | 10 ++++----
 10 files changed, 79 insertions(+), 19 deletions(-)
 create mode 100644 dlfcn/tst-dladdr-self.c
  

Comments

Florian Weimer Nov. 11, 2021, 12:39 p.m. UTC | #1
* Adhemerval Zanella:

> For the la_objopen() the path listed in link_map->l_name for the main
> application binary is empty, even though dladdr() is able to recover the
> correct path.
>
> By setting the expected application name on l_name, it allows to
> simplify the code to handle the special case and make the API that
> return such information (la_objopen(), dladdr() and dlinfo() to have
> similar semantic.

_dl_argv[0] is not a path name.

After “bash -c 'exec -a not-a-real-path cat'”, we have this:

(gdb) print _dl_argv[0]
$1 = 0x7ffc1feaf3de "not-a-real-path"

I think dladdr is wrong to use this as a path.

I think in practice l_name == "" means that the link map is for the main
executable.  This seems particularly relevant given that the public part
of the link map does not expose lt_executable.

Thanks,
Florian
  
Adhemerval Zanella Nov. 12, 2021, 7:30 p.m. UTC | #2
On 11/11/2021 09:39, Florian Weimer wrote:
> * Adhemerval Zanella:
> 
>> For the la_objopen() the path listed in link_map->l_name for the main
>> application binary is empty, even though dladdr() is able to recover the
>> correct path.
>>
>> By setting the expected application name on l_name, it allows to
>> simplify the code to handle the special case and make the API that
>> return such information (la_objopen(), dladdr() and dlinfo() to have
>> similar semantic.
> 
> _dl_argv[0] is not a path name.
> 
> After “bash -c 'exec -a not-a-real-path cat'”, we have this:
> 
> (gdb) print _dl_argv[0]
> $1 = 0x7ffc1feaf3de "not-a-real-path"

This is what program_invocation_name/__progname  (initialized at __init_misc())
assumes.  I think it is a fair assumption, since by convention the first string 
in argv on execve() is the filename associated with the file being executed.

Also, if users really want something different to use as the program name
they can use argv[0] which will be aligned in the various APIs.

> 
> I think dladdr is wrong to use this as a path.

I don't think so, glibc itself uses it in multiples places to get the 
program name and also exports program_invocation_name/program_invocation_short_name
as GNU extensions.  Another option would to read /proc/self/cmdline, but I don'
think this would be really an improvement (and it is not easily override by
the users).

> 
> I think in practice l_name == "" means that the link map is for the main
> executable.  This seems particularly relevant given that the public part
> of the link map does not expose lt_executable.

This is one reason I consider to not expose this in the link_map, however I
still think to align la_objopen(), dladdr() and dlinfo() seems to be a better
QoI than asking users to check program_invocation_name.
  

Patch

diff --git a/dlfcn/Makefile b/dlfcn/Makefile
index 6bbfbb8344..bceb2b2885 100644
--- a/dlfcn/Makefile
+++ b/dlfcn/Makefile
@@ -51,7 +51,7 @@  endif
 ifeq (yes,$(build-shared))
 tests = glrefmain failtest tst-dladdr default errmsg1 tstcxaatexit \
 	bug-dlopen1 bug-dlsym1 tst-dlinfo bug-atexit1 bug-atexit2 \
-	bug-atexit3 tstatexit bug-dl-leaf tst-rec-dlopen
+	bug-atexit3 tstatexit bug-dl-leaf tst-rec-dlopen tst-dladdr-self
 endif
 modules-names = glreflib1 glreflib2 glreflib3 failtestmod defaultmod1 \
 		defaultmod2 errmsg1mod modatexit modcxaatexit \
@@ -143,3 +143,5 @@  $(objpfx)bug-dl-leaf.out: $(objpfx)bug-dl-leaf-lib-cb.so
 $(objpfx)bug-dl-leaf-lib-cb.so: $(objpfx)bug-dl-leaf-lib.so
 
 $(objpfx)tst-rec-dlopen.out: $(objpfx)moddummy1.so $(objpfx)moddummy2.so
+
+LDFLAGS-tst-dladdr-self = -rdynamic
diff --git a/dlfcn/tst-dladdr-self.c b/dlfcn/tst-dladdr-self.c
new file mode 100644
index 0000000000..0eddaf1cd7
--- /dev/null
+++ b/dlfcn/tst-dladdr-self.c
@@ -0,0 +1,55 @@ 
+/* Check dladdr with the reference to own exectuable.
+   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/>.  */
+
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <support/check.h>
+#include <support/xdlfcn.h>
+
+void
+test_symbol (void)
+{
+}
+
+static int
+do_test (void)
+{
+  void *handle = xdlopen (NULL, RTLD_NOW);
+  int (*sym)(void) = xdlsym (handle, "test_symbol");
+
+  Dl_info info = { 0 };
+  {
+    int r = dladdr (sym, &info);
+    TEST_VERIFY_EXIT (r != 0);
+  }
+
+  {
+    const char *p = strrchr (info.dli_fname, '/');
+    const char *dli_name = p == NULL ? info.dli_fname : p + 1;
+
+    TEST_COMPARE_STRING (dli_name, "tst-dladdr-self");
+  }
+
+  TEST_COMPARE_STRING (info.dli_sname, "test_symbol");
+  TEST_COMPARE ((uintptr_t) info.dli_saddr, (uintptr_t) test_symbol);
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/elf/dl-addr.c b/elf/dl-addr.c
index 3226880d48..c2c93c63ad 100644
--- a/elf/dl-addr.c
+++ b/elf/dl-addr.c
@@ -30,11 +30,6 @@  determine_info (const ElfW(Addr) addr, struct link_map *match, Dl_info *info,
   info->dli_fname = match->l_name;
   info->dli_fbase = (void *) match->l_map_start;
 
-  /* If this is the main program the information is incomplete.  */
-  if (__builtin_expect (match->l_name[0], 'a') == '\0'
-      && match->l_type == lt_executable)
-    info->dli_fname = _dl_argv[0];
-
   const ElfW(Sym) *symtab
     = (const ElfW(Sym) *) D_PTR (match, l_info[DT_SYMTAB]);
   const char *strtab = (const char *) D_PTR (match, l_info[DT_STRTAB]);
diff --git a/elf/dl-dst.h b/elf/dl-dst.h
index 34bbaf65dc..a1e9af5a06 100644
--- a/elf/dl-dst.h
+++ b/elf/dl-dst.h
@@ -44,7 +44,7 @@ 
 	   auditing, in ld.so.  */					      \
 	if ((l)->l_origin == NULL)					      \
 	  {								      \
-	    assert ((l)->l_name[0] == '\0' || IS_RTLD (l));		      \
+	    assert ((l)->l_type == lt_executable || IS_RTLD (l));	      \
 	    (l)->l_origin = _dl_get_origin ();				      \
 	    dst_len = ((l)->l_origin && (l)->l_origin != (char *) -1	      \
 			  ? strlen ((l)->l_origin) : 0);		      \
diff --git a/elf/dl-init.c b/elf/dl-init.c
index f924d26642..9fa8403290 100644
--- a/elf/dl-init.c
+++ b/elf/dl-init.c
@@ -39,8 +39,7 @@  call_init (struct link_map *l, int argc, char **argv, char **env)
   l->l_init_called = 1;
 
   /* Check for object which constructors we do not run here.  */
-  if (__builtin_expect (l->l_name[0], 'a') == '\0'
-      && l->l_type == lt_executable)
+  if (l->l_type == lt_executable)
     return;
 
   /* Print a debug message if wanted.  */
diff --git a/elf/dl-misc.c b/elf/dl-misc.c
index b256d792c6..b62bbcb8b4 100644
--- a/elf/dl-misc.c
+++ b/elf/dl-misc.c
@@ -338,6 +338,7 @@  rtld_hidden_def (_dl_fatal_printf)
 int
 _dl_name_match_p (const char *name, const struct link_map *map)
 {
+  /* Filter out the main executable.  */
   if (strcmp (name, map->l_name) == 0)
     return 1;
 
diff --git a/elf/rtld.c b/elf/rtld.c
index 55b4eb91f2..29a37f51d3 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -1361,11 +1361,6 @@  dl_main (const ElfW(Phdr) *phdr,
 
       phdr = main_map->l_phdr;
       phnum = main_map->l_phnum;
-      /* We overwrite here a pointer to a malloc()ed string.  But since
-	 the malloc() implementation used at this point is the dummy
-	 implementations which has no real free() function it does not
-	 makes sense to free the old string first.  */
-      main_map->l_name = (char *) "";
       *user_entry = main_map->l_entry;
 
       /* Set bit indicating this is the main program map.  */
@@ -1431,6 +1426,7 @@  dl_main (const ElfW(Phdr) *phdr,
 	 information for the program.  */
     }
 
+  main_map->l_name = _dl_argv[0];
   main_map->l_map_end = 0;
   main_map->l_text_end = 0;
   /* Perhaps the executable has no PT_LOAD header entries at all.  */
diff --git a/elf/tst-audit22.c b/elf/tst-audit22.c
index c1b301c4cc..7718e3e959 100644
--- a/elf/tst-audit22.c
+++ b/elf/tst-audit22.c
@@ -94,6 +94,7 @@  do_test (int argc, char *argv[])
 
   unsigned long vdso_process = 0;
   unsigned long vdso_audit = 0;
+  bool mainapp_found = false;
 
   FILE *out = fmemopen (result.err.buffer, result.err.length, "r");
   TEST_VERIFY (out != NULL);
@@ -105,9 +106,12 @@  do_test (int argc, char *argv[])
 	vdso_process = parse_address (buffer + strlen ("vdso: "));
       else if (startswith (buffer, "vdso found: "))
 	vdso_audit = parse_address (buffer + strlen ("vdso found: "));
+      else if (startswith (buffer, "mainapp found"))
+	mainapp_found = true;
     }
 
   TEST_COMPARE (vdso_process, vdso_audit);
+  TEST_COMPARE (mainapp_found, true);
 
   free (buffer);
   xfclose (out);
diff --git a/elf/tst-auditmod22.c b/elf/tst-auditmod22.c
index 02275bed57..a4921f35de 100644
--- a/elf/tst-auditmod22.c
+++ b/elf/tst-auditmod22.c
@@ -19,6 +19,7 @@ 
 #include <link.h>
 #include <inttypes.h>
 #include <stdio.h>
+#include <string.h>
 #include <sys/auxv.h>
 
 unsigned int
@@ -30,6 +31,13 @@  la_version (unsigned int version)
 unsigned int
 la_objopen (struct link_map *map, Lmid_t lmid, uintptr_t *cookie)
 {
+  {
+    const char *p = strrchr (map->l_name, '/');
+    const char *l_name = p == NULL ? map->l_name : p + 1;
+    if (strcmp (l_name, "tst-audit22") == 0)
+      fprintf (stderr, "mainapp found\n");
+  }
+
   if (map->l_addr == getauxval (AT_SYSINFO_EHDR))
     fprintf (stderr, "vdso found: %" PRIxPTR "\n", (uintptr_t) map->l_addr);
 
diff --git a/gmon/gmon.c b/gmon/gmon.c
index dee64803ad..cb8327b8e4 100644
--- a/gmon/gmon.c
+++ b/gmon/gmon.c
@@ -48,16 +48,16 @@ 
 
 #ifdef PIC
 # include <link.h>
+# include <ldsodefs.h>
 
 static int
 callback (struct dl_phdr_info *info, size_t size, void *data)
 {
-  if (info->dlpi_name[0] == '\0')
+  if (info->dlpi_name == _dl_argv[0])
     {
-      /* The link map for the executable is created by calling
-	 _dl_new_object with "" as filename.  dl_iterate_phdr
-	 calls the callback function with filename from the
-	 link map as dlpi_name.  */
+      /* The link map l_name for the executable is set to the _dl_argv[0]
+	 after argument processing.  dl_iterate_phdr() calls the callback
+	 function with filename from the link map as dlpi_name.  */
       u_long *load_address = data;
       *load_address = (u_long) info->dlpi_addr;
       return 1;