[PATCH v2] dlfcn: dlinfo (RTLD_DI_PHDR) should work for proxy link maps (bug 32060)

Florian Weimer fweimer@redhat.com
Tue Dec 3 12:29:12 GMT 2024


Previously, 0 was returned as the program header address.

To get a clean test run for the new dlfcn/tst-dlinfo-dlmopen test,
it is necessary to handle RTLD_DI_ORIGIN failures in dlinfo, too.

---
v2: Handle proxy link maps correctly for RTLD_DI_LINKMAP, RTLD_DI_LMID.
    Add new test dlfcn/tst-dlinfo-dlmopen.  Fix RTLD_DI_ORIGIN for
    link maps with unknown origin.  Use RTLD_DI_LINKMAP in
    dlfcn/tst-dlinfo-phdr per Adhemerval's suggestion.
 dlfcn/Makefile             |  1 +
 dlfcn/dlinfo.c             | 26 +++++++-----
 dlfcn/tst-dlinfo-dlmopen.c | 99 ++++++++++++++++++++++++++++++++++++++++++++++
 dlfcn/tst-dlinfo-phdr.c    | 73 +++++++++++++++++++++-------------
 4 files changed, 163 insertions(+), 36 deletions(-)

diff --git a/dlfcn/Makefile b/dlfcn/Makefile
index d473773dd4..dcc0fd2b70 100644
--- a/dlfcn/Makefile
+++ b/dlfcn/Makefile
@@ -124,6 +124,7 @@ tststatic4-ENV = $(tststatic-ENV)
 tststatic5-ENV = $(tststatic-ENV)
 
 tests-internal += \
+  tst-dlinfo-dlmopen \
   tst-dlinfo-phdr \
   # tests-internal
 
diff --git a/dlfcn/dlinfo.c b/dlfcn/dlinfo.c
index b0feb9362d..7d13b29822 100644
--- a/dlfcn/dlinfo.c
+++ b/dlfcn/dlinfo.c
@@ -49,41 +49,49 @@ dlinfo_doit (void *argsblock)
       break;
 
     case RTLD_DI_LMID:
+      /* Return the proxy namespace (if any).  */
       *(Lmid_t *) args->arg = l->l_ns;
       break;
 
     case RTLD_DI_LINKMAP:
+      /* Return the proxy link map (if any).  */
       *(struct link_map **) args->arg = l;
       break;
 
     case RTLD_DI_SERINFO:
-      _dl_rtld_di_serinfo (l, args->arg, false);
+      _dl_rtld_di_serinfo (l->l_real, args->arg, false);
       break;
     case RTLD_DI_SERINFOSIZE:
-      _dl_rtld_di_serinfo (l, args->arg, true);
+      _dl_rtld_di_serinfo (l->l_real, args->arg, true);
       break;
 
     case RTLD_DI_ORIGIN:
-      strcpy (args->arg, l->l_origin);
+      /* If the object was loaded by the kernel, origin information
+	 could be unavailable.  */
+      if (l->l_real->l_origin == NULL || l->l_real->l_origin == (void *) -1)
+	{
+	  args->result = -1;
+	  _dl_signal_error (0, NULL, NULL, N_("RTLD_DI_ORIGIN unavailable"));
+	}
+      strcpy (args->arg, l->l_real->l_origin);
       break;
 
     case RTLD_DI_TLS_MODID:
-      *(size_t *) args->arg = 0;
-      *(size_t *) args->arg = l->l_tls_modid;
+      *(size_t *) args->arg = l->l_real->l_tls_modid;
       break;
 
     case RTLD_DI_TLS_DATA:
       {
 	void *data = NULL;
-	if (l->l_tls_modid != 0)
-	  data = GLRO(dl_tls_get_addr_soft) (l);
+	if (l->l_real->l_tls_modid != 0)
+	  data = GLRO(dl_tls_get_addr_soft) (l->l_real);
 	*(void **) args->arg = data;
 	break;
       }
 
     case RTLD_DI_PHDR:
-      *(const ElfW(Phdr) **) args->arg = l->l_phdr;
-      args->result = l->l_phnum;
+      *(const ElfW(Phdr) **) args->arg = l->l_real->l_phdr;
+      args->result = l->l_real->l_phnum;
       break;
     }
 }
diff --git a/dlfcn/tst-dlinfo-dlmopen.c b/dlfcn/tst-dlinfo-dlmopen.c
new file mode 100644
index 0000000000..ef44005803
--- /dev/null
+++ b/dlfcn/tst-dlinfo-dlmopen.c
@@ -0,0 +1,99 @@
+/* Test for dlinfo with dlmopen.
+   Copyright (C) 2022-2024 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/>.  */
+
+/* This internal test assumes that dlopen handles are inplemented as
+   struct link_map pointers.  */
+
+#include <dlfcn.h>
+#include <gnu/lib-names.h>
+#include <link.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/auxv.h>
+#include <unistd.h>
+
+#include <support/check.h>
+#include <support/xdlfcn.h>
+
+/* Per-link-map checks.  */
+static void
+checks (struct link_map *l, Lmid_t expected_nsid)
+{
+  printf ("info: checking link map %p for \"%s\" [%ld]\n",
+          l, l->l_name, l->l_ns);
+
+  /* Cause dlerror () to return an error message.  */
+  dlsym (RTLD_DEFAULT, "does-not-exist");
+
+  /* Use the extension that link maps are valid dlopen handles.  */
+  struct link_map *l2 = NULL;
+  TEST_COMPARE (dlinfo (l, RTLD_DI_LINKMAP, &l2), 0);
+  /* This equality should hold even if l is a proxy link map.  */
+  TEST_VERIFY (l2 == l);
+  /* Verify that the error message has been cleared.  */
+  TEST_COMPARE_STRING (dlerror (), NULL);
+
+  /* Cause dlerror () to return an error message.  */
+  dlsym (RTLD_DEFAULT, "does-not-exist");
+
+  Lmid_t lmid = -1;
+  TEST_COMPARE (dlinfo (l, RTLD_DI_LMID, &lmid), 0);
+  /* Assume that namespaces are allocated sequentially.  */
+  TEST_COMPARE (lmid, expected_nsid);
+  /* Verify that the error message has been cleared.  */
+  TEST_COMPARE_STRING (dlerror (), NULL);
+
+  char origin[4096];          /* Cannot size properly (bug 24298).  */
+  origin[0] = '\0';
+  int result = dlinfo (l, RTLD_DI_ORIGIN, origin);
+  if (result != 0)
+    {
+      /* The RTLD_DI_ORIGIN request can fail, but should not crash.  */
+      TEST_COMPARE (result, -1);
+      TEST_VERIFY (dlerror () != NULL);
+    }
+  else
+    /* This should be an existing path.  */
+    TEST_COMPARE (access (origin, F_OK), 0);
+}
+
+static int
+do_test (void)
+{
+  /* Avoid a copy relocation.  */
+  struct r_debug *debug = xdlsym (RTLD_DEFAULT, "_r_debug");
+  struct link_map *l = (struct link_map *) debug->r_map;
+  TEST_VERIFY_EXIT (l != NULL);
+
+  for (; l != NULL; l = l->l_next)
+    checks (l, LM_ID_BASE);
+
+  /* The secondary namespace does not contain the main executable, and
+     dl_iterate_phdr does not cover it.  */
+  struct link_map *secondary_libc = xdlmopen (LM_ID_NEWLM, LIBC_SO, RTLD_NOW);
+  for (l = secondary_libc; l != NULL; l = l->l_next)
+    /* Assume that namespaces are allocated sequentially.  */
+    checks (l, LM_ID_BASE + 1);
+  TEST_VERIFY (secondary_libc->l_real == secondary_libc);
+  xdlclose (secondary_libc);
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/dlfcn/tst-dlinfo-phdr.c b/dlfcn/tst-dlinfo-phdr.c
index fdffb17724..9e61134210 100644
--- a/dlfcn/tst-dlinfo-phdr.c
+++ b/dlfcn/tst-dlinfo-phdr.c
@@ -17,6 +17,7 @@
    <https://www.gnu.org/licenses/>.  */
 
 #include <dlfcn.h>
+#include <gnu/lib-names.h>
 #include <link.h>
 #include <stdbool.h>
 #include <stdio.h>
@@ -54,6 +55,42 @@ dlip_callback (struct dl_phdr_info *dlpi, size_t size, void *closure)
   return 0;
 }
 
+/* Set by basic_checks.   */
+static const ElfW(Phdr) *phdr;
+static int phnum;
+
+/* Basic checks that can be used regardless of namespace.  */
+static void
+basic_checks (struct link_map *l)
+{
+  printf ("info: checking link map %p (%p) for \"%s\"\n",
+          l, l->l_phdr, l->l_name);
+
+  /* Cause dlerror () to return an error message.  */
+  dlsym (RTLD_DEFAULT, "does-not-exist");
+
+  /* Use the extension that link maps are valid dlopen handles.  */
+  phnum = dlinfo (l, RTLD_DI_PHDR, &phdr);
+  TEST_VERIFY (phnum >= 0);
+  /* Verify that the error message has been cleared.  */
+  TEST_COMPARE_STRING (dlerror (), NULL);
+
+  TEST_VERIFY (phdr == l->l_real->l_phdr);
+  TEST_COMPARE (phnum, l->l_real->l_phnum);
+
+  /* Check that we can find PT_DYNAMIC among the array.  */
+  {
+    bool dynamic_found = false;
+    for (int i = 0; i < phnum; ++i)
+      if (phdr[i].p_type == PT_DYNAMIC)
+        {
+          dynamic_found = true;
+          TEST_COMPARE ((ElfW(Addr)) l->l_ld, l->l_addr + phdr[i].p_vaddr);
+        }
+    TEST_VERIFY (dynamic_found);
+  }
+}
+
 static int
 do_test (void)
 {
@@ -64,33 +101,7 @@ do_test (void)
 
   do
     {
-      printf ("info: checking link map %p (%p) for \"%s\"\n",
-              l, l->l_phdr, l->l_name);
-
-      /* Cause dlerror () to return an error message.  */
-      dlsym (RTLD_DEFAULT, "does-not-exist");
-
-      /* Use the extension that link maps are valid dlopen handles.  */
-      const ElfW(Phdr) *phdr;
-      int phnum = dlinfo (l, RTLD_DI_PHDR, &phdr);
-      TEST_VERIFY (phnum >= 0);
-      /* Verify that the error message has been cleared.  */
-      TEST_COMPARE_STRING (dlerror (), NULL);
-
-      TEST_VERIFY (phdr == l->l_phdr);
-      TEST_COMPARE (phnum, l->l_phnum);
-
-      /* Check that we can find PT_DYNAMIC among the array.  */
-      {
-        bool dynamic_found = false;
-        for (int i = 0; i < phnum; ++i)
-          if (phdr[i].p_type == PT_DYNAMIC)
-            {
-              dynamic_found = true;
-              TEST_COMPARE ((ElfW(Addr)) l->l_ld, l->l_addr + phdr[i].p_vaddr);
-            }
-        TEST_VERIFY (dynamic_found);
-      }
+      basic_checks (l);
 
       /* Check that dl_iterate_phdr finds the link map with the same
          program headers.  */
@@ -119,6 +130,14 @@ do_test (void)
     }
   while (l != NULL);
 
+  /* The secondary namespace does not contain the main executable, and
+     dl_iterate_phdr does not cover it.  */
+  struct link_map *secondary_libc = xdlmopen (LM_ID_NEWLM, LIBC_SO, RTLD_NOW);
+  TEST_COMPARE (dlinfo (secondary_libc, RTLD_DI_LINKMAP, &l), 0);
+  for (; l != NULL; l = l->l_next)
+    basic_checks (l);
+  xdlclose (secondary_libc);
+
   return 0;
 }
 

base-commit: f43eb2cf30fdff39bda1c2018246d4badabbc576



More information about the Libc-alpha mailing list