[glibc] elf: Scrub and reseed the AT_RANDOM bytes after deriving the guards (BZ 34197)
Adhemerval Zanella via Glibc-cvs <[email protected]> Tue, 16 Jun 2026 18:16:11 +0000 (GMT)
| Newsgroups | gmane.comp.lib.glibc.cvs |
|---|---|
| Message-ID | <[email protected]> |
https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=337e18d6617bb93a6c718818c4d77d000878dbb6 commit 337e18d6617bb93a6c718818c4d77d000878dbb6 Author: Adhemerval Zanella <[email protected]> Date: Fri Jun 12 13:10:16 2026 -0300 elf: Scrub and reseed the AT_RANDOM bytes after deriving the guards (BZ 34197) 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. Reviewed-by: DJ Delorie <[email protected]> Diff: --- csu/libc-start.c | 7 +++ elf/Makefile | 5 ++ elf/rtld.c | 9 +-- elf/tst-atrandom-scrub-static.c | 1 + elf/tst-atrandom-scrub.c | 89 ++++++++++++++++++++++++++++++ sysdeps/generic/dl-reseed-random.h | 34 ++++++++++++ sysdeps/unix/sysv/linux/dl-reseed-random.h | 43 +++++++++++++++ 7 files changed, 184 insertions(+), 4 deletions(-) diff --git a/csu/libc-start.c b/csu/libc-start.c index 28af53f7db..1614e932c2 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. */ @@ -301,6 +303,11 @@ LIBC_START_MAIN (int (*main) (int, char **, char ** MAIN_AUXVEC_DECL), __pointer_chk_guard_local = pointer_chk_guard; # endif + /* We do not need the _dl_random value anymore. Scrub the AT_RANDOM + bytes and clear the pointer; on targets with an entropy source, refill + the bytes with fresh random data. */ + _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 8a89ed3c60..6514428294 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 \ @@ -2420,6 +2422,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 12e1b4dd71..ebb5446681 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,10 @@ 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; + /* We do not need the _dl_random value anymore. Scrub the AT_RANDOM + bytes and clear the pointer; on targets with an entropy source, refill + the bytes with fresh random data. */ + _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 0000000000..b68362fbb3 --- /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 0000000000..a56641fa3f --- /dev/null +++ b/elf/tst-atrandom-scrub.c @@ -0,0 +1,89 @@ +/* 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 <stdbool.h> +#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"); + + /* Sanity check: the guards and AT_RANDOM must all have been populated + (not all-zero), otherwise we cannot tell scrub-and-reseed apart from + "never set up". */ + TEST_VERIFY (stack_guard != 0); + TEST_VERIFY (pointer_guard != 0); + bool random_all_zero = true; + for (int i = 0; i < 16; i++) + if (random[i] != 0) + { + random_all_zero = false; + break; + } + TEST_VERIFY (!random_all_zero); + + /* 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 0000000000..7e1a3c3be2 --- /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 0000000000..b0ceb5cc65 --- /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 */