diff mbox series

[v2,1/3] elf: Run constructors on cyclic recursive dlopen (bug 31986)

Message ID 3f27d400f91fbf02eabd188f002c3003dd6f994d.1723116962.git.fweimer@redhat.com
State New
Headers show
Series Fixes for recursive dlopen (bug 31986) | expand

Commit Message

Florian Weimer Aug. 8, 2024, 11:40 a.m. UTC
This is conceptually similar to the reported bug, but does not
depend on auditing.  The fix is simple: just complete execution
of the constructors.  This exposed the fact that the link map
for statically linked executables does not have l_init_called
set, even though constructors have run.
---
 elf/Makefile                 |  6 ++++
 elf/dl-open.c                |  8 +++++
 elf/dl-support.c             |  1 +
 elf/tst-dlopen-recurse.c     | 34 +++++++++++++++++++
 elf/tst-dlopen-recursemod1.c | 50 +++++++++++++++++++++++++++
 elf/tst-dlopen-recursemod2.c | 66 ++++++++++++++++++++++++++++++++++++
 6 files changed, 165 insertions(+)
 create mode 100644 elf/tst-dlopen-recurse.c
 create mode 100644 elf/tst-dlopen-recursemod1.c
 create mode 100644 elf/tst-dlopen-recursemod2.c

Comments

Adhemerval Zanella Netto Oct. 8, 2024, 6:59 p.m. UTC | #1
On 08/08/24 08:40, Florian Weimer wrote:
> This is conceptually similar to the reported bug, but does not
> depend on auditing.  The fix is simple: just complete execution
> of the constructors.  This exposed the fact that the link map
> for statically linked executables does not have l_init_called
> set, even though constructors have run.

LGTM, thanks.  Just a minor suggestion below.

Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>

> ---
>  elf/Makefile                 |  6 ++++
>  elf/dl-open.c                |  8 +++++
>  elf/dl-support.c             |  1 +
>  elf/tst-dlopen-recurse.c     | 34 +++++++++++++++++++
>  elf/tst-dlopen-recursemod1.c | 50 +++++++++++++++++++++++++++
>  elf/tst-dlopen-recursemod2.c | 66 ++++++++++++++++++++++++++++++++++++
>  6 files changed, 165 insertions(+)
>  create mode 100644 elf/tst-dlopen-recurse.c
>  create mode 100644 elf/tst-dlopen-recursemod1.c
>  create mode 100644 elf/tst-dlopen-recursemod2.c
> 
> diff --git a/elf/Makefile b/elf/Makefile
> index 0792b57678..cc3685550d 100644
> --- a/elf/Makefile
> +++ b/elf/Makefile
> @@ -414,6 +414,7 @@ tests += \
>    tst-dlmopen1 \
>    tst-dlmopen3 \
>    tst-dlmopen4 \
> +  tst-dlopen-recurse \
>    tst-dlopen-self \
>    tst-dlopen-tlsmodid \
>    tst-dlopen-tlsreinit1 \
> @@ -864,6 +865,8 @@ modules-names += \
>    tst-dlmopen-twice-mod1 \
>    tst-dlmopen-twice-mod2 \
>    tst-dlmopen1mod \
> +  tst-dlopen-recursemod1 \
> +  tst-dlopen-recursemod2 \
>    tst-dlopen-tlsreinitmod1 \
>    tst-dlopen-tlsreinitmod2 \
>    tst-dlopen-tlsreinitmod3 \
> @@ -3155,3 +3158,6 @@ $(objpfx)tst-dlopen-tlsreinit3.out: $(objpfx)tst-auditmod1.so
>  tst-dlopen-tlsreinit3-ENV = LD_AUDIT=$(objpfx)tst-auditmod1.so
>  $(objpfx)tst-dlopen-tlsreinit4.out: $(objpfx)tst-auditmod1.so
>  tst-dlopen-tlsreinit4-ENV = LD_AUDIT=$(objpfx)tst-auditmod1.so
> +
> +$(objpfx)tst-dlopen-recurse.out: $(objpfx)tst-dlopen-recursemod1.so
> +$(objpfx)tst-dlopen-recursemod1.so: $(objpfx)tst-dlopen-recursemod2.so
> diff --git a/elf/dl-open.c b/elf/dl-open.c
> index 8b4704c09d..2c20aa1df9 100644
> --- a/elf/dl-open.c
> +++ b/elf/dl-open.c
> @@ -590,6 +590,14 @@ dl_open_worker_begin (void *a)
>          = _dl_debug_update (args->nsid)->r_state;
>        assert (r_state == RT_CONSISTENT);
>  
> +      /* Do not return without calling the (supposedly new) map's
> +	 constructor.  This case occurs if a dependency of a directly
> +	 opened map has a constructor that calls dlopen again on the
> +	 initially opened map.  The new map is initialized last, so
> +	 checking only it is enough.  */
> +      if (!new->l_init_called)
> +	_dl_catch_exception (NULL, call_dl_init, args);
> +
>        return;
>      }
>  
> diff --git a/elf/dl-support.c b/elf/dl-support.c
> index 451932dd03..94e8197c63 100644
> --- a/elf/dl-support.c
> +++ b/elf/dl-support.c
> @@ -99,6 +99,7 @@ static struct link_map _dl_main_map =
>      .l_used = 1,
>      .l_tls_offset = NO_TLS_OFFSET,
>      .l_serial = 1,
> +    .l_init_called = 1,
>    };
>  
>  /* Namespace information.  */
> diff --git a/elf/tst-dlopen-recurse.c b/elf/tst-dlopen-recurse.c
> new file mode 100644
> index 0000000000..c7fb379d37
> --- /dev/null
> +++ b/elf/tst-dlopen-recurse.c
> @@ -0,0 +1,34 @@
> +/* Test that recursive dlopen runs constructors before return (bug 31986).
> +   Copyright (C) 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/>.  */
> +
> +#include <stdio.h>
> +#include <support/check.h>
> +#include <support/xdlfcn.h>
> +
> +static int
> +do_test (void)
> +{
> +  void *handle = xdlopen ("tst-dlopen-recursemod1.so", RTLD_NOW);
> +  int *status = dlsym (handle, "recursemod1_status");

Maybe use xdlsym here.

> +  printf ("info: recursemod1_status == %d (from main)\n", *status);
> +  TEST_COMPARE (*status, 2);
> +  xdlclose (handle);
> +  return 0;
> +}
> +
> +#include <support/test-driver.c>
> diff --git a/elf/tst-dlopen-recursemod1.c b/elf/tst-dlopen-recursemod1.c
> new file mode 100644
> index 0000000000..5e0cc0eb8c
> --- /dev/null
> +++ b/elf/tst-dlopen-recursemod1.c
> @@ -0,0 +1,50 @@
> +/* Directly opened test module that gets recursively opened again.
> +   Copyright (C) 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/>.  */
> +
> +#include <stdio.h>
> +#include <stdlib.h>
> +#include <support/xdlfcn.h>
> +
> +int recursemod1_status;
> +
> +/* Force linking against st-dlopen-recursemod2.so.  Also allows
> +   checking for relocation.  */
> +extern int recursemod2_status;
> +int *force_recursemod2_reference = &recursemod2_status;
> +
> +static void __attribute__ ((constructor))
> +init (void)
> +{
> +  ++recursemod1_status;
> +  printf ("info: tst-dlopen-recursemod1.so constructor called (status %d)\n",
> +          recursemod1_status);
> +}
> +
> +static void __attribute__ ((destructor))
> +fini (void)
> +{
> +  /* The recursemod1_status variable was incremented in the
> +     tst-dlopen-recursemod2.so constructor.  */
> +  printf ("info: tst-dlopen-recursemod1.so destructor called (status %d)\n",
> +          recursemod1_status);
> +  if (recursemod1_status != 2)
> +    {
> +      puts ("error: recursemod1_status == 2 expected");
> +      exit (1);
> +    }
> +}
> diff --git a/elf/tst-dlopen-recursemod2.c b/elf/tst-dlopen-recursemod2.c
> new file mode 100644
> index 0000000000..edd2f2526b
> --- /dev/null
> +++ b/elf/tst-dlopen-recursemod2.c
> @@ -0,0 +1,66 @@
> +/* Indirectly opened module that recursively opens the directly opened module.
> +   Copyright (C) 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/>.  */
> +
> +#include <dlfcn.h>
> +#include <stdio.h>
> +#include <stdlib.h>
> +
> +int recursemod2_status;
> +
> +static void __attribute__ ((constructor))
> +init (void)
> +{
> +  ++recursemod2_status;
> +  printf ("info: tst-dlopen-recursemod2.so constructor called (status %d)\n",
> +          recursemod2_status);
> +  void *handle = dlopen ("tst-dlopen-recursemod1.so", RTLD_NOW);
> +  if (handle == NULL)
> +    {
> +      printf ("error: dlopen: %s\n", dlerror ());
> +      exit (1);
> +    }
> +  int *status = dlsym (handle, "recursemod1_status");
> +  if (status == NULL)
> +    {
> +      printf ("error: dlsym: %s\n", dlerror ());
> +      exit (1);
> +    }
> +  printf ("info: recursemod1_status == %d\n", *status);
> +  if (*status != 1)
> +    {
> +      puts ("error: recursemod1_status == 1 expected");
> +      exit (1);
> +    }
> +  ++*status;
> +  printf ("info: recursemod1_status == %d\n", *status);
> +
> +  int **mod2_status = dlsym (handle, "force_recursemod2_reference");
> +  if (mod2_status == NULL || *mod2_status != &recursemod2_status)
> +    {
> +      puts ("error: invalid recursemod2_status address in"
> +            " tst-dlopen-recursemod1.so");
> +      exit (1);
> +    }
> +}
> +
> +static void __attribute__ ((destructor))
> +fini (void)
> +{
> +  printf  ("info: tst-dlopen-recursemod2.so destructor called (status %d)\n",
> +           recursemod2_status);
> +}
Andreas Schwab Oct. 28, 2024, 1:58 p.m. UTC | #2
On Aug 08 2024, Florian Weimer wrote:

> This is conceptually similar to the reported bug, but does not
> depend on auditing.  The fix is simple: just complete execution
> of the constructors.  This exposed the fact that the link map
> for statically linked executables does not have l_init_called
> set, even though constructors have run.

This breaks https://github.com/NVIDIA/libglvnd.  It has a constructor
__glDispatchOnLoadInit in libGLdispatch.so.0 (a dependency of
libEGL.so.1 and libGLX.so.0), which calls dlopen(NULL) to get a handle
of libc for looking up the pthread functions to initialize
__glvndPthreadFuncs.  That dlopen call now runs the constructors of the
remaining librararies, including libEGL.so.1 (__eglInit) and libGLX.so.0
(__glXIinit), which depend on __glvndPthreadFuncs being initialized.
Florian Weimer Oct. 28, 2024, 2:28 p.m. UTC | #3
* Andreas Schwab:

> On Aug 08 2024, Florian Weimer wrote:
>
>> This is conceptually similar to the reported bug, but does not
>> depend on auditing.  The fix is simple: just complete execution
>> of the constructors.  This exposed the fact that the link map
>> for statically linked executables does not have l_init_called
>> set, even though constructors have run.
>
> This breaks https://github.com/NVIDIA/libglvnd.  It has a constructor
> __glDispatchOnLoadInit in libGLdispatch.so.0 (a dependency of
> libEGL.so.1 and libGLX.so.0), which calls dlopen(NULL) to get a handle
> of libc for looking up the pthread functions to initialize
> __glvndPthreadFuncs.  That dlopen call now runs the constructors of the
> remaining librararies, including libEGL.so.1 (__eglInit) and libGLX.so.0
> (__glXIinit), which depend on __glvndPthreadFuncs being initialized.

Indeed, I've just pushed the revert I posted earlier.  I've also posted
a patch which adds the libglvnd usage as a test case:

  [PATCH 2/2] elf: Test dlopen (NULL, RTLD_LAZY) from an ELF constructor
  <https://inbox.sourceware.org/libc-alpha/9cae3d01f7110eed03256a03903a805b5f6120e5.1729950708.git.fweimer@redhat.com/T/#u>

Thanks,
Florian
diff mbox series

Patch

diff --git a/elf/Makefile b/elf/Makefile
index 0792b57678..cc3685550d 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -414,6 +414,7 @@  tests += \
   tst-dlmopen1 \
   tst-dlmopen3 \
   tst-dlmopen4 \
+  tst-dlopen-recurse \
   tst-dlopen-self \
   tst-dlopen-tlsmodid \
   tst-dlopen-tlsreinit1 \
@@ -864,6 +865,8 @@  modules-names += \
   tst-dlmopen-twice-mod1 \
   tst-dlmopen-twice-mod2 \
   tst-dlmopen1mod \
+  tst-dlopen-recursemod1 \
+  tst-dlopen-recursemod2 \
   tst-dlopen-tlsreinitmod1 \
   tst-dlopen-tlsreinitmod2 \
   tst-dlopen-tlsreinitmod3 \
@@ -3155,3 +3158,6 @@  $(objpfx)tst-dlopen-tlsreinit3.out: $(objpfx)tst-auditmod1.so
 tst-dlopen-tlsreinit3-ENV = LD_AUDIT=$(objpfx)tst-auditmod1.so
 $(objpfx)tst-dlopen-tlsreinit4.out: $(objpfx)tst-auditmod1.so
 tst-dlopen-tlsreinit4-ENV = LD_AUDIT=$(objpfx)tst-auditmod1.so
+
+$(objpfx)tst-dlopen-recurse.out: $(objpfx)tst-dlopen-recursemod1.so
+$(objpfx)tst-dlopen-recursemod1.so: $(objpfx)tst-dlopen-recursemod2.so
diff --git a/elf/dl-open.c b/elf/dl-open.c
index 8b4704c09d..2c20aa1df9 100644
--- a/elf/dl-open.c
+++ b/elf/dl-open.c
@@ -590,6 +590,14 @@  dl_open_worker_begin (void *a)
         = _dl_debug_update (args->nsid)->r_state;
       assert (r_state == RT_CONSISTENT);
 
+      /* Do not return without calling the (supposedly new) map's
+	 constructor.  This case occurs if a dependency of a directly
+	 opened map has a constructor that calls dlopen again on the
+	 initially opened map.  The new map is initialized last, so
+	 checking only it is enough.  */
+      if (!new->l_init_called)
+	_dl_catch_exception (NULL, call_dl_init, args);
+
       return;
     }
 
diff --git a/elf/dl-support.c b/elf/dl-support.c
index 451932dd03..94e8197c63 100644
--- a/elf/dl-support.c
+++ b/elf/dl-support.c
@@ -99,6 +99,7 @@  static struct link_map _dl_main_map =
     .l_used = 1,
     .l_tls_offset = NO_TLS_OFFSET,
     .l_serial = 1,
+    .l_init_called = 1,
   };
 
 /* Namespace information.  */
diff --git a/elf/tst-dlopen-recurse.c b/elf/tst-dlopen-recurse.c
new file mode 100644
index 0000000000..c7fb379d37
--- /dev/null
+++ b/elf/tst-dlopen-recurse.c
@@ -0,0 +1,34 @@ 
+/* Test that recursive dlopen runs constructors before return (bug 31986).
+   Copyright (C) 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/>.  */
+
+#include <stdio.h>
+#include <support/check.h>
+#include <support/xdlfcn.h>
+
+static int
+do_test (void)
+{
+  void *handle = xdlopen ("tst-dlopen-recursemod1.so", RTLD_NOW);
+  int *status = dlsym (handle, "recursemod1_status");
+  printf ("info: recursemod1_status == %d (from main)\n", *status);
+  TEST_COMPARE (*status, 2);
+  xdlclose (handle);
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/elf/tst-dlopen-recursemod1.c b/elf/tst-dlopen-recursemod1.c
new file mode 100644
index 0000000000..5e0cc0eb8c
--- /dev/null
+++ b/elf/tst-dlopen-recursemod1.c
@@ -0,0 +1,50 @@ 
+/* Directly opened test module that gets recursively opened again.
+   Copyright (C) 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/>.  */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <support/xdlfcn.h>
+
+int recursemod1_status;
+
+/* Force linking against st-dlopen-recursemod2.so.  Also allows
+   checking for relocation.  */
+extern int recursemod2_status;
+int *force_recursemod2_reference = &recursemod2_status;
+
+static void __attribute__ ((constructor))
+init (void)
+{
+  ++recursemod1_status;
+  printf ("info: tst-dlopen-recursemod1.so constructor called (status %d)\n",
+          recursemod1_status);
+}
+
+static void __attribute__ ((destructor))
+fini (void)
+{
+  /* The recursemod1_status variable was incremented in the
+     tst-dlopen-recursemod2.so constructor.  */
+  printf ("info: tst-dlopen-recursemod1.so destructor called (status %d)\n",
+          recursemod1_status);
+  if (recursemod1_status != 2)
+    {
+      puts ("error: recursemod1_status == 2 expected");
+      exit (1);
+    }
+}
diff --git a/elf/tst-dlopen-recursemod2.c b/elf/tst-dlopen-recursemod2.c
new file mode 100644
index 0000000000..edd2f2526b
--- /dev/null
+++ b/elf/tst-dlopen-recursemod2.c
@@ -0,0 +1,66 @@ 
+/* Indirectly opened module that recursively opens the directly opened module.
+   Copyright (C) 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/>.  */
+
+#include <dlfcn.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+int recursemod2_status;
+
+static void __attribute__ ((constructor))
+init (void)
+{
+  ++recursemod2_status;
+  printf ("info: tst-dlopen-recursemod2.so constructor called (status %d)\n",
+          recursemod2_status);
+  void *handle = dlopen ("tst-dlopen-recursemod1.so", RTLD_NOW);
+  if (handle == NULL)
+    {
+      printf ("error: dlopen: %s\n", dlerror ());
+      exit (1);
+    }
+  int *status = dlsym (handle, "recursemod1_status");
+  if (status == NULL)
+    {
+      printf ("error: dlsym: %s\n", dlerror ());
+      exit (1);
+    }
+  printf ("info: recursemod1_status == %d\n", *status);
+  if (*status != 1)
+    {
+      puts ("error: recursemod1_status == 1 expected");
+      exit (1);
+    }
+  ++*status;
+  printf ("info: recursemod1_status == %d\n", *status);
+
+  int **mod2_status = dlsym (handle, "force_recursemod2_reference");
+  if (mod2_status == NULL || *mod2_status != &recursemod2_status)
+    {
+      puts ("error: invalid recursemod2_status address in"
+            " tst-dlopen-recursemod1.so");
+      exit (1);
+    }
+}
+
+static void __attribute__ ((destructor))
+fini (void)
+{
+  printf  ("info: tst-dlopen-recursemod2.so destructor called (status %d)\n",
+           recursemod2_status);
+}