[8/8] elf: Scrub and reseed the AT_RANDOM bytes after deriving the guards (BZ 34197)

Message ID 20260603000656.3287796-9-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
redhat-pt-bot/TryBot-32bit fail Patch caused testsuite regressions
linaro-tcwg-bot/tcwg_glibc_check--master-arm success Test passed

Commit Message

Adhemerval Zanella Netto June 3, 2026, 12:04 a.m. UTC
  Once the pointer and stack guards have been derived from AT_RANDOM, scrub
the bytes and refill them with new random data unrelated to the guards.
On Linux, it uses getrandom syscall (as for tcache_key_initialize), and
fallback to zero the memory if the syscall is not avaiable.

This keeps AT_RANDOM useful to applications while ensuring those bytes no
longer reveal the guards.

The work is done by _dl_reseed_random, called once the guards are in place
and before any ELF constructor can observe AT_RANDOM: in security_init for
the dynamic loader and in __libc_start_main for statically linked programs.

Checked on aarch64-linux-gnu, x86_64-linux-gnu, and i686-linux-gnu.
---
 csu/libc-start.c                           |  4 ++
 elf/Makefile                               |  5 ++
 elf/rtld.c                                 |  6 +-
 elf/tst-atrandom-scrub-static.c            |  1 +
 elf/tst-atrandom-scrub.c                   | 74 ++++++++++++++++++++++
 sysdeps/generic/dl-reseed-random.h         | 34 ++++++++++
 sysdeps/unix/sysv/linux/dl-reseed-random.h | 43 +++++++++++++
 7 files changed, 163 insertions(+), 4 deletions(-)
 create mode 100644 elf/tst-atrandom-scrub-static.c
 create mode 100644 elf/tst-atrandom-scrub.c
 create mode 100644 sysdeps/generic/dl-reseed-random.h
 create mode 100644 sysdeps/unix/sysv/linux/dl-reseed-random.h
  

Comments

DJ Delorie June 10, 2026, 10:05 p.m. UTC | #1
Adhemerval Zanella <adhemerval.zanella@linaro.org> writes:
> Once the pointer and stack guards have been derived from AT_RANDOM, scrub
> the bytes and refill them with new random data unrelated to the guards.
> On Linux, it uses getrandom syscall (as for tcache_key_initialize), and
> fallback to zero the memory if the syscall is not avaiable.
>
> This keeps AT_RANDOM useful to applications while ensuring those bytes no
> longer reveal the guards.
>
> The work is done by _dl_reseed_random, called once the guards are in place
> and before any ELF constructor can observe AT_RANDOM: in security_init for
> the dynamic loader and in __libc_start_main for statically linked programs.

> diff --git a/csu/libc-start.c b/csu/libc-start.c
> +  _dl_reseed_random (&_dl_random);

Ok.

> diff --git a/elf/rtld.c b/elf/rtld.c
> -  /* We do not need the _dl_random value anymore.  The less
> -     information we leave behind, the better, so clear the
> -     variable.  */

I see no reason to remove this comment; it's still valid.  If anything,
it should be expanded to include the new logic.  The other caller has no
comment, though...

> -  _dl_random = NULL;
> +  _dl_reseed_random (&_dl_random);

can _dl_reseed_random ever be called twice in the same program?  This
second one happens when we load audit modules, but you don't test that
case, and I wonder if calling it twice might result in NULL guards, or
mismatched ones...

> diff --git a/elf/tst-atrandom-scrub.c b/elf/tst-atrandom-scrub.c

> +/* The loader (security_init) and the static startup code (__libc_start_main)
> +   derive the stack and pointer guards from the AT_RANDOM bytes, scrub those
> +   bytes, and refill them with fresh entropy unrelated to the guards.  The
> +   AT_RANDOM entry is kept, so getauxval (AT_RANDOM) keeps returning 16 random
> +   bytes, but they no longer reveal the guards.  Check that neither guard can
> +   be reconstructed from AT_RANDOM and that no auxiliary vector entry holds a
> +   guard value.  */
> +
> +#include <stdint.h>
> +#include <stdio.h>
> +#include <string.h>
> +#include <sys/auxv.h>
> +
> +#include <stackguard-macros.h>
> +#include <tls.h>
> +#include <support/check.h>
> +
> +static int
> +do_test (void)
> +{
> +  uintptr_t stack_guard = STACK_CHK_GUARD;
> +  uintptr_t pointer_guard = POINTER_CHK_GUARD;
> +
> +  unsigned char *random = (unsigned char *) getauxval (AT_RANDOM);
> +  if (random == NULL)
> +    FAIL_UNSUPPORTED ("the kernel did not provide AT_RANDOM");
> +
> +  printf ("debug: stack guard   = %0*jx\n",
> +          (int) (2 * sizeof (uintptr_t)), (uintmax_t) stack_guard);
> +  printf ("debug: pointer guard = %0*jx\n",
> +          (int) (2 * sizeof (uintptr_t)), (uintmax_t) pointer_guard);
> +  printf ("debug: AT_RANDOM     = ");
> +  for (int i = 0; i < 16; i++)
> +    printf ("%02x", random[i]);
> +  printf ("\n");

Ok.

> +  /* Reconstruct the guards from the (reseeded) AT_RANDOM bytes the way the
> +     loader does and check that they no longer match the live guards.  */
> +  uintptr_t recovered_stack;
> +  memcpy (&recovered_stack, random, sizeof (recovered_stack));
> +#if __BYTE_ORDER == __LITTLE_ENDIAN
> +  recovered_stack &= ~(uintptr_t) 0xff;
> +#else
> +  recovered_stack &= ~((uintptr_t) 0xff << (8 * (sizeof (recovered_stack) - 1)));
> +#endif
> +  TEST_VERIFY (recovered_stack != stack_guard);
> +
> +  uintptr_t recovered_pointer;
> +  memcpy (&recovered_pointer, random + sizeof (uintptr_t),
> +          sizeof (recovered_pointer));
> +  TEST_VERIFY (recovered_pointer != pointer_guard);

Ok.

Should be verify that none of (stack_guard, pointer_guard, AT_RANDOM)
are all zeros?  Even assuming that the kernel can always give us more
random bits, we should ensure that they're being filled properly.

> diff --git a/sysdeps/generic/dl-reseed-random.h b/sysdeps/generic/dl-reseed-random.h
> +#include <string.h>
> +
> +static inline void __attribute__ ((always_inline))
> +_dl_reseed_random (void **dl_random)
> +{
> +  if (*dl_random == NULL)
> +    return;
> +  memset (*dl_random, '\0', 16);
> +  __asm__ __volatile__ ("" : : "r" (*dl_random) : "memory");
> +  *dl_random = NULL;
> +}
> +

Ok.

> diff --git a/sysdeps/unix/sysv/linux/dl-reseed-random.h b/sysdeps/unix/sysv/linux/dl-reseed-random.h
> +#include <string.h>
> +#include <not-cancel.h>
> +#include <sys/random.h>
> +
> +/* The stack and pointer guards have been derived from the 16 AT_RANDOM
> +   bytes pointed to by DL_RANDOM.  Scrub them first, so the guards cannot be
> +   recovered even if the refill below fails, then refill them with fresh
> +   entropy unrelated to the guards so that getauxval (AT_RANDOM) keeps
> +   returning random bytes.  */
> +static inline void __attribute__ ((always_inline))
> +_dl_reseed_random (void **dl_random)
> +{
> +  if (*dl_random == NULL)
> +    return;
> +  memset (*dl_random, '\0', 16);
> +  __asm__ __volatile__ ("" : : "r" (*dl_random) : "memory");
> +
> +  __getrandom_nocancel_nostatus_direct (*dl_random, 16, GRND_NONBLOCK);
> +  _dl_random = NULL;

Why access _dl_random directly here, and not &dl_random like the generic case?

IMHO This looks too much like a typo.
  
Adhemerval Zanella Netto June 11, 2026, 7:57 p.m. UTC | #2
On 10/06/26 19:05, DJ Delorie wrote:
> Adhemerval Zanella <adhemerval.zanella@linaro.org> writes:
>> Once the pointer and stack guards have been derived from AT_RANDOM, scrub
>> the bytes and refill them with new random data unrelated to the guards.
>> On Linux, it uses getrandom syscall (as for tcache_key_initialize), and
>> fallback to zero the memory if the syscall is not avaiable.
>>
>> This keeps AT_RANDOM useful to applications while ensuring those bytes no
>> longer reveal the guards.
>>
>> The work is done by _dl_reseed_random, called once the guards are in place
>> and before any ELF constructor can observe AT_RANDOM: in security_init for
>> the dynamic loader and in __libc_start_main for statically linked programs.
> 
>> diff --git a/csu/libc-start.c b/csu/libc-start.c
>> +  _dl_reseed_random (&_dl_random);
> 
> Ok.
> 
>> diff --git a/elf/rtld.c b/elf/rtld.c
>> -  /* We do not need the _dl_random value anymore.  The less
>> -     information we leave behind, the better, so clear the
>> -     variable.  */
> 
> I see no reason to remove this comment; it's still valid.  If anything,
> it should be expanded to include the new logic.  The other caller has no
> comment, though...

Ack, I will keep and add a similar one on the other caller.

> 
>> -  _dl_random = NULL;
>> +  _dl_reseed_random (&_dl_random);
> 
> can _dl_reseed_random ever be called twice in the same program?  This
> second one happens when we load audit modules, but you don't test that
> case, and I wonder if calling it twice might result in NULL guards, or
> mismatched ones...

The security_init cannot be called twice: in dl_main, need_security_init is
set 'false' after the early audit-path call. The later call around line 2020
is gated on need_security_init, and the __libc_start_main's call is only for
!SHARED.

But I think even if a double call were ever introduced, the function should be
safe. The if (*dl_random == NULL) return; early-return makes the second call a 
no-op.

> 
>> diff --git a/elf/tst-atrandom-scrub.c b/elf/tst-atrandom-scrub.c
> 
>> +/* The loader (security_init) and the static startup code (__libc_start_main)
>> +   derive the stack and pointer guards from the AT_RANDOM bytes, scrub those
>> +   bytes, and refill them with fresh entropy unrelated to the guards.  The
>> +   AT_RANDOM entry is kept, so getauxval (AT_RANDOM) keeps returning 16 random
>> +   bytes, but they no longer reveal the guards.  Check that neither guard can
>> +   be reconstructed from AT_RANDOM and that no auxiliary vector entry holds a
>> +   guard value.  */
>> +
>> +#include <stdint.h>
>> +#include <stdio.h>
>> +#include <string.h>
>> +#include <sys/auxv.h>
>> +
>> +#include <stackguard-macros.h>
>> +#include <tls.h>
>> +#include <support/check.h>
>> +
>> +static int
>> +do_test (void)
>> +{
>> +  uintptr_t stack_guard = STACK_CHK_GUARD;
>> +  uintptr_t pointer_guard = POINTER_CHK_GUARD;
>> +
>> +  unsigned char *random = (unsigned char *) getauxval (AT_RANDOM);
>> +  if (random == NULL)
>> +    FAIL_UNSUPPORTED ("the kernel did not provide AT_RANDOM");
>> +
>> +  printf ("debug: stack guard   = %0*jx\n",
>> +          (int) (2 * sizeof (uintptr_t)), (uintmax_t) stack_guard);
>> +  printf ("debug: pointer guard = %0*jx\n",
>> +          (int) (2 * sizeof (uintptr_t)), (uintmax_t) pointer_guard);
>> +  printf ("debug: AT_RANDOM     = ");
>> +  for (int i = 0; i < 16; i++)
>> +    printf ("%02x", random[i]);
>> +  printf ("\n");
> 
> Ok.
> 
>> +  /* Reconstruct the guards from the (reseeded) AT_RANDOM bytes the way the
>> +     loader does and check that they no longer match the live guards.  */
>> +  uintptr_t recovered_stack;
>> +  memcpy (&recovered_stack, random, sizeof (recovered_stack));
>> +#if __BYTE_ORDER == __LITTLE_ENDIAN
>> +  recovered_stack &= ~(uintptr_t) 0xff;
>> +#else
>> +  recovered_stack &= ~((uintptr_t) 0xff << (8 * (sizeof (recovered_stack) - 1)));
>> +#endif
>> +  TEST_VERIFY (recovered_stack != stack_guard);
>> +
>> +  uintptr_t recovered_pointer;
>> +  memcpy (&recovered_pointer, random + sizeof (uintptr_t),
>> +          sizeof (recovered_pointer));
>> +  TEST_VERIFY (recovered_pointer != pointer_guard);
> 
> Ok.
> 
> Should be verify that none of (stack_guard, pointer_guard, AT_RANDOM)
> are all zeros?  Even assuming that the kernel can always give us more
> random bits, we should ensure that they're being filled properly.
> 
>> diff --git a/sysdeps/generic/dl-reseed-random.h b/sysdeps/generic/dl-reseed-random.h
>> +#include <string.h>
>> +
>> +static inline void __attribute__ ((always_inline))
>> +_dl_reseed_random (void **dl_random)
>> +{
>> +  if (*dl_random == NULL)
>> +    return;
>> +  memset (*dl_random, '\0', 16);
>> +  __asm__ __volatile__ ("" : : "r" (*dl_random) : "memory");
>> +  *dl_random = NULL;
>> +}
>> +
> 
> Ok.
> 
>> diff --git a/sysdeps/unix/sysv/linux/dl-reseed-random.h b/sysdeps/unix/sysv/linux/dl-reseed-random.h
>> +#include <string.h>
>> +#include <not-cancel.h>
>> +#include <sys/random.h>
>> +
>> +/* The stack and pointer guards have been derived from the 16 AT_RANDOM
>> +   bytes pointed to by DL_RANDOM.  Scrub them first, so the guards cannot be
>> +   recovered even if the refill below fails, then refill them with fresh
>> +   entropy unrelated to the guards so that getauxval (AT_RANDOM) keeps
>> +   returning random bytes.  */
>> +static inline void __attribute__ ((always_inline))
>> +_dl_reseed_random (void **dl_random)
>> +{
>> +  if (*dl_random == NULL)
>> +    return;
>> +  memset (*dl_random, '\0', 16);
>> +  __asm__ __volatile__ ("" : : "r" (*dl_random) : "memory");
>> +
>> +  __getrandom_nocancel_nostatus_direct (*dl_random, 16, GRND_NONBLOCK);
>> +  _dl_random = NULL;
> 
> Why access _dl_random directly here, and not &dl_random like the generic case?
> 
> IMHO This looks too much like a typo.
> 

It is a typo, it should be "*dl_random = NULL'.
  
DJ Delorie June 11, 2026, 8:25 p.m. UTC | #3
Adhemerval Zanella Netto <adhemerval.zanella@linaro.org> writes:
>>> -  _dl_random = NULL;
>>> +  _dl_reseed_random (&_dl_random);
>> 
>> can _dl_reseed_random ever be called twice in the same program?  This
>> second one happens when we load audit modules, but you don't test that
>> case, and I wonder if calling it twice might result in NULL guards, or
>> mismatched ones...
>
> The security_init cannot be called twice: in dl_main, need_security_init is
> set 'false' after the early audit-path call. The later call around line 2020
> is gated on need_security_init, and the __libc_start_main's call is only for
> !SHARED.
>
> But I think even if a double call were ever introduced, the function should be
> safe. The if (*dl_random == NULL) return; early-return makes the second call a 
> no-op.

I was more worried about the call in csu/libc-start.c and the call in
elf/rtld.c both affecting the same process.
  
Adhemerval Zanella Netto June 11, 2026, 8:36 p.m. UTC | #4
On 11/06/26 17:25, DJ Delorie wrote:
> 
> Adhemerval Zanella Netto <adhemerval.zanella@linaro.org> writes:
>>>> -  _dl_random = NULL;
>>>> +  _dl_reseed_random (&_dl_random);
>>>
>>> can _dl_reseed_random ever be called twice in the same program?  This
>>> second one happens when we load audit modules, but you don't test that
>>> case, and I wonder if calling it twice might result in NULL guards, or
>>> mismatched ones...
>>
>> The security_init cannot be called twice: in dl_main, need_security_init is
>> set 'false' after the early audit-path call. The later call around line 2020
>> is gated on need_security_init, and the __libc_start_main's call is only for
>> !SHARED.
>>
>> But I think even if a double call were ever introduced, the function should be
>> safe. The if (*dl_random == NULL) return; early-return makes the second call a 
>> no-op.
> 
> I was more worried about the call in csu/libc-start.c and the call in
> elf/rtld.c both affecting the same process.
> 

Right, the csu/libc-start.c is only for static objects.  The static-dlopen edge
case also does trigger the elf/rtld.c initialization, and that's why we have
__rtld_static_init to handle the requires missing pieces.
  

Patch

diff --git a/csu/libc-start.c b/csu/libc-start.c
index 03d770ef157..66ef4e3ffc4 100644
--- a/csu/libc-start.c
+++ b/csu/libc-start.c
@@ -44,6 +44,8 @@  extern void __libc_init_first (int argc, char **argv, char **envp);
 #include <tls.h>
 #ifndef SHARED
 # include <dl-osinfo.h>
+# include <dl-reseed-random.h>
+# include <dl-symbol-redir-ifunc.h>
 # ifndef THREAD_SET_STACK_GUARD
 /* Only exported for architectures that don't store the stack guard canary
    in thread local area.  */
@@ -300,6 +302,8 @@  LIBC_START_MAIN (int (*main) (int, char **, char ** MAIN_AUXVEC_DECL),
   __pointer_chk_guard_local = pointer_chk_guard;
 # endif
 
+  _dl_reseed_random (&_dl_random);
+
   /* Now that the TCB, canary, and pointer guard are in place, run the
      deferred IFUNC relocations.  For non-PIE static binaries this is
      ARCH_SETUP_IREL (apply_irel); for static-pie it is the IRELATIVE
diff --git a/elf/Makefile b/elf/Makefile
index a940b3045e5..75e86dadc4f 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -279,6 +279,7 @@  tests-static-normal := \
   # tests-static-normal
 
 tests-static-internal := \
+  tst-atrandom-scrub-static \
   tst-dl-printf-static \
   tst-dl_find_object-static \
   tst-env-setuid-tunables \
@@ -548,6 +549,7 @@  tests-internal += \
   neededtest2 \
   neededtest3 \
   neededtest4 \
+  tst-atrandom-scrub \
   tst-audit19a \
   tst-create_format1 \
   tst-dl-hwcaps_split \
@@ -2410,6 +2412,9 @@  tst-ptrguard1-ARGS = --command "$(host-test-program-cmd) --child"
 CFLAGS-tst-ptrguard1-static.c += -DPTRGUARD_LOCAL
 tst-ptrguard1-static-ARGS = --command "$(objpfx)tst-ptrguard1-static --child"
 
+# Likewise, the static pointer guard lives in __pointer_chk_guard_local.
+CFLAGS-tst-atrandom-scrub-static.c += -DPTRGUARD_LOCAL
+
 $(objpfx)tst-leaks1-mem.out: $(objpfx)tst-leaks1.out
 	$(common-objpfx)malloc/mtrace $(objpfx)tst-leaks1.mtrace > $@; \
 	$(evaluate-test)
diff --git a/elf/rtld.c b/elf/rtld.c
index 12e1b4dd71f..4d27b2fccc8 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -35,6 +35,7 @@ 
 #include <unsecvars.h>
 #include <dl-cache.h>
 #include <dl-osinfo.h>
+#include <dl-reseed-random.h>
 #include <dl-prop.h>
 #include <dl-vdso.h>
 #include <dl-vdso-setup.h>
@@ -832,10 +833,7 @@  security_init (void)
 #endif
   __pointer_chk_guard_local = pointer_chk_guard;
 
-  /* We do not need the _dl_random value anymore.  The less
-     information we leave behind, the better, so clear the
-     variable.  */
-  _dl_random = NULL;
+  _dl_reseed_random (&_dl_random);
 }
 
 #include <setup-vdso.h>
diff --git a/elf/tst-atrandom-scrub-static.c b/elf/tst-atrandom-scrub-static.c
new file mode 100644
index 00000000000..b68362fbb3c
--- /dev/null
+++ b/elf/tst-atrandom-scrub-static.c
@@ -0,0 +1 @@ 
+#include "tst-atrandom-scrub.c"
diff --git a/elf/tst-atrandom-scrub.c b/elf/tst-atrandom-scrub.c
new file mode 100644
index 00000000000..1fabfa63dcb
--- /dev/null
+++ b/elf/tst-atrandom-scrub.c
@@ -0,0 +1,74 @@ 
+/* Verify the AT_RANDOM bytes do not reveal the guards after startup.
+   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/>.  */
+
+/* The loader (security_init) and the static startup code (__libc_start_main)
+   derive the stack and pointer guards from the AT_RANDOM bytes, scrub those
+   bytes, and refill them with fresh entropy unrelated to the guards.  The
+   AT_RANDOM entry is kept, so getauxval (AT_RANDOM) keeps returning 16 random
+   bytes, but they no longer reveal the guards.  Check that neither guard can
+   be reconstructed from AT_RANDOM and that no auxiliary vector entry holds a
+   guard value.  */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/auxv.h>
+
+#include <stackguard-macros.h>
+#include <tls.h>
+#include <support/check.h>
+
+static int
+do_test (void)
+{
+  uintptr_t stack_guard = STACK_CHK_GUARD;
+  uintptr_t pointer_guard = POINTER_CHK_GUARD;
+
+  unsigned char *random = (unsigned char *) getauxval (AT_RANDOM);
+  if (random == NULL)
+    FAIL_UNSUPPORTED ("the kernel did not provide AT_RANDOM");
+
+  printf ("debug: stack guard   = %0*jx\n",
+          (int) (2 * sizeof (uintptr_t)), (uintmax_t) stack_guard);
+  printf ("debug: pointer guard = %0*jx\n",
+          (int) (2 * sizeof (uintptr_t)), (uintmax_t) pointer_guard);
+  printf ("debug: AT_RANDOM     = ");
+  for (int i = 0; i < 16; i++)
+    printf ("%02x", random[i]);
+  printf ("\n");
+
+  /* Reconstruct the guards from the (reseeded) AT_RANDOM bytes the way the
+     loader does and check that they no longer match the live guards.  */
+  uintptr_t recovered_stack;
+  memcpy (&recovered_stack, random, sizeof (recovered_stack));
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+  recovered_stack &= ~(uintptr_t) 0xff;
+#else
+  recovered_stack &= ~((uintptr_t) 0xff << (8 * (sizeof (recovered_stack) - 1)));
+#endif
+  TEST_VERIFY (recovered_stack != stack_guard);
+
+  uintptr_t recovered_pointer;
+  memcpy (&recovered_pointer, random + sizeof (uintptr_t),
+          sizeof (recovered_pointer));
+  TEST_VERIFY (recovered_pointer != pointer_guard);
+
+  return 0;
+}
+
+#include <support/test-driver.c>
diff --git a/sysdeps/generic/dl-reseed-random.h b/sysdeps/generic/dl-reseed-random.h
new file mode 100644
index 00000000000..7e1a3c3be21
--- /dev/null
+++ b/sysdeps/generic/dl-reseed-random.h
@@ -0,0 +1,34 @@ 
+/* Scrub and reseed the kernel-provided random bytes.  Generic version.
+   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/>.  */
+
+#ifndef _DL_RESEED_RANDOM_H
+#define _DL_RESEED_RANDOM_H
+
+#include <string.h>
+
+static inline void __attribute__ ((always_inline))
+_dl_reseed_random (void **dl_random)
+{
+  if (*dl_random == NULL)
+    return;
+  memset (*dl_random, '\0', 16);
+  __asm__ __volatile__ ("" : : "r" (*dl_random) : "memory");
+  *dl_random = NULL;
+}
+
+#endif /* _DL_RESEED_RANDOM_H */
diff --git a/sysdeps/unix/sysv/linux/dl-reseed-random.h b/sysdeps/unix/sysv/linux/dl-reseed-random.h
new file mode 100644
index 00000000000..f954e48b7f1
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/dl-reseed-random.h
@@ -0,0 +1,43 @@ 
+/* Scrub and reseed the AT_RANDOM bytes.  Linux version.
+   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/>.  */
+
+#ifndef _DL_RESEED_RANDOM_H
+#define _DL_RESEED_RANDOM_H
+
+#include <string.h>
+#include <not-cancel.h>
+#include <sys/random.h>
+
+/* The stack and pointer guards have been derived from the 16 AT_RANDOM
+   bytes pointed to by DL_RANDOM.  Scrub them first, so the guards cannot be
+   recovered even if the refill below fails, then refill them with fresh
+   entropy unrelated to the guards so that getauxval (AT_RANDOM) keeps
+   returning random bytes.  */
+static inline void __attribute__ ((always_inline))
+_dl_reseed_random (void **dl_random)
+{
+  if (*dl_random == NULL)
+    return;
+  memset (*dl_random, '\0', 16);
+  __asm__ __volatile__ ("" : : "r" (*dl_random) : "memory");
+
+  __getrandom_nocancel_nostatus_direct (*dl_random, 16, GRND_NONBLOCK);
+  _dl_random = NULL;
+}
+
+#endif /* _DL_RESEED_RANDOM_H */