[1/8] elf: Propagate the pointer guard to ld.so loaded via static dlopen (BZ 34196)

Message ID 20260603000656.3287796-2-adhemerval.zanella@linaro.org (mailing list archive)
State Superseded
Delegated to: DJ Delorie
Headers
Series Pointer guard hardening and consolidation |

Checks

Context Check Description
redhat-pt-bot/TryBot-apply_patch success Patch applied to master at the time it was sent
linaro-tcwg-bot/tcwg_glibc_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_glibc_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_glibc_check--master-aarch64 success Test passed
linaro-tcwg-bot/tcwg_glibc_check--master-arm success Test passed

Commit Message

Adhemerval Zanella Netto June 3, 2026, 12:04 a.m. UTC
  The static-dlopen does not initialize the pointer guard for ABIs that
define THREAD_SET_POINTER_GUARD.  Besides not properly guard the
pointer if a libc.so symbol is called, this can leads to setjmp
failures (a jmp_buf set up by the loaded ibc.so.6 cannot be restored
by the static program's __longjmp, and vice versa).

Seed the just-mapped loader's __pointer_chk_guard from the program's
__pointer_chk_guard_local in __rtld_static_init, next to the other
runtime values copied there.

Checked on aarch64-linux-gnu, x86_64-linux-gnu, and i686-linux-gnu.
---
 elf/Makefile                                  |  6 +++
 elf/rtld_static_init.c                        | 12 +++++
 elf/tst-ptrguard-static-dlopen-mod.c          | 29 +++++++++++
 elf/tst-ptrguard-static-dlopen.c              | 51 +++++++++++++++++++
 .../tst-ptrguard-static-dlopen.script         |  1 +
 5 files changed, 99 insertions(+)
 create mode 100644 elf/tst-ptrguard-static-dlopen-mod.c
 create mode 100644 elf/tst-ptrguard-static-dlopen.c
 create mode 100644 elf/tst-ptrguard-static-dlopen.root/tst-ptrguard-static-dlopen.script
  

Comments

DJ Delorie June 10, 2026, 1:35 a.m. UTC | #1
Adhemerval Zanella <adhemerval.zanella@linaro.org> writes:

> The static-dlopen does not initialize the pointer guard for ABIs that
> define THREAD_SET_POINTER_GUARD.  Besides not properly guard the
> pointer if a libc.so symbol is called, this can leads to setjmp

"can lead"

> failures (a jmp_buf set up by the loaded ibc.so.6 cannot be restored
> by the static program's __longjmp, and vice versa).

s/ibc/libc/

> diff --git a/elf/rtld_static_init.c b/elf/rtld_static_init.c
> index 04eb1c6fcc4..a428542e57e 100644
> --- a/elf/rtld_static_init.c
> +++ b/elf/rtld_static_init.c
> @@ -81,5 +81,17 @@ __rtld_static_init (struct link_map *map)
>    dl->_dl_find_object = _dl_find_object;
>    dl->_dl_readonly_area = _dl_readonly_area;
>  
> +#ifndef THREAD_SET_POINTER_GUARD
> +  extern uintptr_t __pointer_chk_guard_local attribute_hidden;
> +  const ElfW(Sym) *guard_sym
> +    = _dl_lookup_direct (map, "__pointer_chk_guard",
> +			 0x69f99cab, /* dl_new_hash output.  */

Missing _

> +			 "GLIBC_PRIVATE",
> +			 0x0963cf85); /* _dl_elf_hash output.  */
> +  if (guard_sym != NULL)

What do we do if it is NULL ?  Just ignore it?

> +    *(uintptr_t *) DL_SYMBOL_ADDRESS (map, guard_sym)
> +	= __pointer_chk_guard_local;

Do we need to check for DL_SYMBOL_ADDRESS() returning NULL?  The
definitions include such returns.

> diff --git a/elf/tst-ptrguard-static-dlopen-mod.c b/elf/tst-ptrguard-static-dlopen-mod.c
> @@ -0,0 +1,29 @@
> +#include <setjmp.h>
> +
> +static jmp_buf jb;
> +void (*do_longjmp) (jmp_buf);
> +
> +void
> +foo (void)
> +{
> +  if (setjmp (jb) == 0)
> +    do_longjmp (jb);
> +}

Ok.

> diff --git a/elf/tst-ptrguard-static-dlopen.c b/elf/tst-ptrguard-static-dlopen.c
> +/* A statically linked program dlopens a shared object; the object's setjmp
> +   uses the just-mapped libc.so's pointer guard while the longjmp below uses
> +   the program's guard.  Unless __rtld_static_init propagates the guard to
> +   the loaded loader the two differ, and the setjmp/longjmp round-trip jumps
> +   to a corrupt address and crashes.  */
> +
> +#include <setjmp.h>
> +#include <support/check.h>
> +#include <support/xdlfcn.h>
> +
> +static void
> +call_longjmp (jmp_buf jb)
> +{
> +  longjmp (jb, 1);
> +}
> +
> +static int
> +do_test (void)
> +{
> +  void *h = xdlopen ("tst-ptrguard-static-dlopen-mod.so", RTLD_NOW);
> +  void (*foo) (void) = xdlsym (h, "foo");
> +  void (**do_longjmp) (jmp_buf) = xdlsym (h, "do_longjmp");
> +  *do_longjmp = call_longjmp;
> +
> +  /* foo () sets the jump buffer and calls back into call_longjmp; a
> +     mismatched guard makes the return jump fault.  */
> +  foo ();
> +
> +  xdlclose (h);
> +  return 0;
> +}
> +
> +#include <support/test-driver.c>

Ok.
  
Adhemerval Zanella Netto June 10, 2026, 12:14 p.m. UTC | #2
On 09/06/26 22:35, DJ Delorie wrote:
> Adhemerval Zanella <adhemerval.zanella@linaro.org> writes:
> 
>> The static-dlopen does not initialize the pointer guard for ABIs that
>> define THREAD_SET_POINTER_GUARD.  Besides not properly guard the
>> pointer if a libc.so symbol is called, this can leads to setjmp
> 
> "can lead"

Ack.

> 
>> failures (a jmp_buf set up by the loaded ibc.so.6 cannot be restored
>> by the static program's __longjmp, and vice versa).
> 
> s/ibc/libc/

Ack.

> 
>> diff --git a/elf/rtld_static_init.c b/elf/rtld_static_init.c
>> index 04eb1c6fcc4..a428542e57e 100644
>> --- a/elf/rtld_static_init.c
>> +++ b/elf/rtld_static_init.c
>> @@ -81,5 +81,17 @@ __rtld_static_init (struct link_map *map)
>>    dl->_dl_find_object = _dl_find_object;
>>    dl->_dl_readonly_area = _dl_readonly_area;
>>  
>> +#ifndef THREAD_SET_POINTER_GUARD
>> +  extern uintptr_t __pointer_chk_guard_local attribute_hidden;
>> +  const ElfW(Sym) *guard_sym
>> +    = _dl_lookup_direct (map, "__pointer_chk_guard",
>> +			 0x69f99cab, /* dl_new_hash output.  */
> 
> Missing _	
> 

Ack.

>> +			 "GLIBC_PRIVATE",
>> +			 0x0963cf85); /* _dl_elf_hash output.  */
>> +  if (guard_sym != NULL)
> 
> What do we do if it is NULL ?  Just ignore it?

We should assert, as for _rtld_global_ro. I will fix it.

> 
>> +    *(uintptr_t *) DL_SYMBOL_ADDRESS (map, guard_sym)
>> +	= __pointer_chk_guard_local;
> 
> Do we need to check for DL_SYMBOL_ADDRESS() returning NULL?  The
> definitions include such returns.

I do not think we need for this specific case, __rtld_static_init already 
assumes that DL_SYMBOL_ADDRESS (map, "_rtld_global_ro") always works.

For a valid map and symbol reference, DL_SYMBOL_ADDRESS would return NULL
for two cases:

  1. l_addr == 0 and st_value == 0
  2. SHN_ABS symbol with st_value == 0

The 1. should not happen for the libc.so, and for 2. we need either explicit
set the symbol value to 0 (as elf/tst-absolute-zero-lib.lds does).

> 
>> diff --git a/elf/tst-ptrguard-static-dlopen-mod.c b/elf/tst-ptrguard-static-dlopen-mod.c
>> @@ -0,0 +1,29 @@
>> +#include <setjmp.h>
>> +
>> +static jmp_buf jb;
>> +void (*do_longjmp) (jmp_buf);
>> +
>> +void
>> +foo (void)
>> +{
>> +  if (setjmp (jb) == 0)
>> +    do_longjmp (jb);
>> +}
> 
> Ok.
> 
>> diff --git a/elf/tst-ptrguard-static-dlopen.c b/elf/tst-ptrguard-static-dlopen.c
>> +/* A statically linked program dlopens a shared object; the object's setjmp
>> +   uses the just-mapped libc.so's pointer guard while the longjmp below uses
>> +   the program's guard.  Unless __rtld_static_init propagates the guard to
>> +   the loaded loader the two differ, and the setjmp/longjmp round-trip jumps
>> +   to a corrupt address and crashes.  */
>> +
>> +#include <setjmp.h>
>> +#include <support/check.h>
>> +#include <support/xdlfcn.h>
>> +
>> +static void
>> +call_longjmp (jmp_buf jb)
>> +{
>> +  longjmp (jb, 1);
>> +}
>> +
>> +static int
>> +do_test (void)
>> +{
>> +  void *h = xdlopen ("tst-ptrguard-static-dlopen-mod.so", RTLD_NOW);
>> +  void (*foo) (void) = xdlsym (h, "foo");
>> +  void (**do_longjmp) (jmp_buf) = xdlsym (h, "do_longjmp");
>> +  *do_longjmp = call_longjmp;
>> +
>> +  /* foo () sets the jump buffer and calls back into call_longjmp; a
>> +     mismatched guard makes the return jump fault.  */
>> +  foo ();
>> +
>> +  xdlclose (h);
>> +  return 0;
>> +}
>> +
>> +#include <support/test-driver.c>
> 
> Ok.
>
  
Sachin Monga June 30, 2026, 6:15 p.m. UTC | #3
On 03/06/26 5:34 am, Adhemerval Zanella wrote:
> The static-dlopen does not initialize the pointer guard for ABIs that
> define THREAD_SET_POINTER_GUARD.  Besides not properly guard the
> pointer if a libc.so symbol is called, this can leads to setjmp
> failures (a jmp_buf set up by the loaded ibc.so.6 cannot be restored
> by the static program's __longjmp, and vice versa).
>
> Seed the just-mapped loader's __pointer_chk_guard from the program's
> __pointer_chk_guard_local in __rtld_static_init, next to the other
> runtime values copied there.
>
> Checked on aarch64-linux-gnu, x86_64-linux-gnu, and i686-linux-gnu.
> ---
>   elf/Makefile                                  |  6 +++
>   elf/rtld_static_init.c                        | 12 +++++
>   elf/tst-ptrguard-static-dlopen-mod.c          | 29 +++++++++++
>   elf/tst-ptrguard-static-dlopen.c              | 51 +++++++++++++++++++
>   .../tst-ptrguard-static-dlopen.script         |  1 +
>   5 files changed, 99 insertions(+)
>   create mode 100644 elf/tst-ptrguard-static-dlopen-mod.c
>   create mode 100644 elf/tst-ptrguard-static-dlopen.c
>   create mode 100644 elf/tst-ptrguard-static-dlopen.root/tst-ptrguard-static-dlopen.script
>
Hi Adhemerval

/tst-ptrguard-static-dlopen/ FAILs on powerpc64le.  It is the first test 
in both tests-static and tests-container, and the tests-container rule 
runs the (dynamically linked) support/test-container through
$(test-via-rtld-prefix), which is empty for tests-static.  The helper is 
thus bound to the system loader/libc instead of the build tree, and 
fails wherever the system glibc predates the build tree. On the system I 
was performing my execution, system installed glibc was 2.34 and hence I 
encountered this failure. :

   support/test-container: libc.so.6: version `GLIBC_2.38' not found 
before the test itself runs. Recent system glibc masks it, which is why 
it might have passed on other archs.

Do you think this assessment is correct or I am missing something here ?

Proposed Fix: add $(test-container-via-rtld-prefix) (= $(rtld-prefix), 
empty under build-hardcoded-path-in-tests) and use it in the 
tests-container rule.
Identical to $(test-via-rtld-prefix) for existing dynamic container 
tests; only static ones change.  I checked it on powerpc64le and x86_64.

Given the freeze on the 4th, OK to post this for the release?

Regards,
Sachin
  
Adhemerval Zanella Netto June 30, 2026, 7:56 p.m. UTC | #4
On 30/06/26 15:15, Sachin Monga wrote:
> 
> On 03/06/26 5:34 am, Adhemerval Zanella wrote:
>> The static-dlopen does not initialize the pointer guard for ABIs that
>> define THREAD_SET_POINTER_GUARD.  Besides not properly guard the
>> pointer if a libc.so symbol is called, this can leads to setjmp
>> failures (a jmp_buf set up by the loaded ibc.so.6 cannot be restored
>> by the static program's __longjmp, and vice versa).
>>
>> Seed the just-mapped loader's __pointer_chk_guard from the program's
>> __pointer_chk_guard_local in __rtld_static_init, next to the other
>> runtime values copied there.
>>
>> Checked on aarch64-linux-gnu, x86_64-linux-gnu, and i686-linux-gnu.
>> ---
>>  elf/Makefile                                  |  6 +++
>>  elf/rtld_static_init.c                        | 12 +++++
>>  elf/tst-ptrguard-static-dlopen-mod.c          | 29 +++++++++++
>>  elf/tst-ptrguard-static-dlopen.c              | 51 +++++++++++++++++++
>>  .../tst-ptrguard-static-dlopen.script         |  1 +
>>  5 files changed, 99 insertions(+)
>>  create mode 100644 elf/tst-ptrguard-static-dlopen-mod.c
>>  create mode 100644 elf/tst-ptrguard-static-dlopen.c
>>  create mode 100644 elf/tst-ptrguard-static-dlopen.root/tst-ptrguard-static-dlopen.script
>>
> Hi Adhemerval
> 
> /tst-ptrguard-static-dlopen/ FAILs on powerpc64le.  It is the first test in both tests-static and tests-container, and the tests-container rule runs the (dynamically linked) support/test-container through
> $(test-via-rtld-prefix), which is empty for tests-static.  The helper is thus bound to the system loader/libc instead of the build tree, and fails wherever the system glibc predates the build tree. On the system I was performing my execution, system installed glibc was 2.34 and hence I encountered this failure. :
> 
>   support/test-container: libc.so.6: version `GLIBC_2.38' not found before the test itself runs. Recent system glibc masks it, which is why it might have passed on other archs.
> 
> Do you think this assessment is correct or I am missing something here ?
> 
> Proposed Fix: add $(test-container-via-rtld-prefix) (= $(rtld-prefix), empty under build-hardcoded-path-in-tests) and use it in the tests-container rule.
> Identical to $(test-via-rtld-prefix) for existing dynamic container tests; only static ones change.  I checked it on powerpc64le and x86_64. 
> 
> Given the freeze on the 4th, OK to post this for the release?
Yeah, we need to always run the test-container with the built glibc. I think
I haven't see it because I always build with $(build-hardcoded-path-in-tests),
so test-container contains the required DT_RPATH.

If you have a working patch please send it, I think we should fix it for
2.44.
  

Patch

diff --git a/elf/Makefile b/elf/Makefile
index bdf9a786d54..a940b3045e5 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -270,6 +270,7 @@  tests-static-normal := \
   tst-env-setuid-static \
   tst-getauxval-static \
   tst-linkall-static \
+  tst-ptrguard-static-dlopen \
   tst-single_threaded-pthread-static \
   tst-single_threaded-static \
   tst-tls-allocation-failure-static \
@@ -576,6 +577,7 @@  tests-container += \
   tst-dlopen-tlsmodid-container \
   tst-pldd \
   tst-preload-pthread-libc \
+  tst-ptrguard-static-dlopen \
   tst-rootdir \
   # tests-container
 
@@ -1013,6 +1015,7 @@  modules-names += \
   tst-null-argv-lib \
   tst-p_alignmod-base \
   tst-p_alignmod3 \
+  tst-ptrguard-static-dlopen-mod \
   tst-recursive-tlsmallocmod \
   tst-recursive-tlsmod0 \
   tst-recursive-tlsmod1 \
@@ -3177,6 +3180,9 @@  $(objpfx)tst-tls21mod.so: $(tst-tls-many-dynamic-modules:%=$(objpfx)%.so)
 $(objpfx)tst-getauxval-static.out: $(objpfx)tst-auxvalmod.so
 tst-getauxval-static-ENV = LD_LIBRARY_PATH=$(objpfx):$(common-objpfx)
 
+$(objpfx)tst-ptrguard-static-dlopen.out: \
+  $(objpfx)tst-ptrguard-static-dlopen-mod.so
+
 $(objpfx)tst-dlmopen-gethostbyname.out: $(objpfx)tst-dlmopen-gethostbyname-mod.so
 
 $(objpfx)tst-ro-dynamic: $(objpfx)tst-ro-dynamic-mod.so
diff --git a/elf/rtld_static_init.c b/elf/rtld_static_init.c
index 04eb1c6fcc4..a428542e57e 100644
--- a/elf/rtld_static_init.c
+++ b/elf/rtld_static_init.c
@@ -81,5 +81,17 @@  __rtld_static_init (struct link_map *map)
   dl->_dl_find_object = _dl_find_object;
   dl->_dl_readonly_area = _dl_readonly_area;
 
+#ifndef THREAD_SET_POINTER_GUARD
+  extern uintptr_t __pointer_chk_guard_local attribute_hidden;
+  const ElfW(Sym) *guard_sym
+    = _dl_lookup_direct (map, "__pointer_chk_guard",
+			 0x69f99cab, /* dl_new_hash output.  */
+			 "GLIBC_PRIVATE",
+			 0x0963cf85); /* _dl_elf_hash output.  */
+  if (guard_sym != NULL)
+    *(uintptr_t *) DL_SYMBOL_ADDRESS (map, guard_sym)
+	= __pointer_chk_guard_local;
+#endif
+
   __rtld_static_init_arch (map, dl);
 }
diff --git a/elf/tst-ptrguard-static-dlopen-mod.c b/elf/tst-ptrguard-static-dlopen-mod.c
new file mode 100644
index 00000000000..1b98e24d038
--- /dev/null
+++ b/elf/tst-ptrguard-static-dlopen-mod.c
@@ -0,0 +1,29 @@ 
+/* Shared object for the static-dlopen pointer guard test.
+   Copyright (C) 2026 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 <setjmp.h>
+
+static jmp_buf jb;
+void (*do_longjmp) (jmp_buf);
+
+void
+foo (void)
+{
+  if (setjmp (jb) == 0)
+    do_longjmp (jb);
+}
diff --git a/elf/tst-ptrguard-static-dlopen.c b/elf/tst-ptrguard-static-dlopen.c
new file mode 100644
index 00000000000..5e4337b11cd
--- /dev/null
+++ b/elf/tst-ptrguard-static-dlopen.c
@@ -0,0 +1,51 @@ 
+/* Test that the pointer guard is propagated to ld.so via static dlopen.
+   Copyright (C) 2026 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/>.  */
+
+/* A statically linked program dlopens a shared object; the object's setjmp
+   uses the just-mapped libc.so's pointer guard while the longjmp below uses
+   the program's guard.  Unless __rtld_static_init propagates the guard to
+   the loaded loader the two differ, and the setjmp/longjmp round-trip jumps
+   to a corrupt address and crashes.  */
+
+#include <setjmp.h>
+#include <support/check.h>
+#include <support/xdlfcn.h>
+
+static void
+call_longjmp (jmp_buf jb)
+{
+  longjmp (jb, 1);
+}
+
+static int
+do_test (void)
+{
+  void *h = xdlopen ("tst-ptrguard-static-dlopen-mod.so", RTLD_NOW);
+  void (*foo) (void) = xdlsym (h, "foo");
+  void (**do_longjmp) (jmp_buf) = xdlsym (h, "do_longjmp");
+  *do_longjmp = call_longjmp;
+
+  /* foo () sets the jump buffer and calls back into call_longjmp; a
+     mismatched guard makes the return jump fault.  */
+  foo ();
+
+  xdlclose (h);
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/elf/tst-ptrguard-static-dlopen.root/tst-ptrguard-static-dlopen.script b/elf/tst-ptrguard-static-dlopen.root/tst-ptrguard-static-dlopen.script
new file mode 100644
index 00000000000..e4f49b34754
--- /dev/null
+++ b/elf/tst-ptrguard-static-dlopen.root/tst-ptrguard-static-dlopen.script
@@ -0,0 +1 @@ 
+cp $B/elf/tst-ptrguard-static-dlopen-mod.so $L/tst-ptrguard-static-dlopen-mod.so