[glibc/azanella/pointer-guard-hardening] elf: Clear ld.so's pointer guard copy after libc relocation
Adhemerval Zanella via Glibc-cvs <[email protected]> Fri, 29 May 2026 20:25:26 +0000 (GMT)
| Newsgroups | gmane.comp.lib.glibc.cvs |
|---|---|
| Message-ID | <[email protected]> |
https://sourceware.org/git/gitweb.cgi?p=glibc.git;h=e44bbd943b0e77896e4ea16054a326490f8136ed commit e44bbd943b0e77896e4ea16054a326490f8136ed Author: Adhemerval Zanella <[email protected]> Date: Fri May 29 17:21:59 2026 -0300 elf: Clear ld.so's pointer guard copy after libc relocation When the pointer guard is kept in the thread descriptor, ld.so still mangles its own setjmp/longjmp jmp_bufs with the global __pointer_chk_guard_local, because the dynamic linker uses setjmp before the TCB is initialized. That leaves a second copy of the process-wide pointer guard at a fixed address inside ld.so (information leakage). Once libc.so has been relocated, switch the dynamic linker's exception handling to the libc setjmp/longjmp (which read the guard from the TCB) and clear __pointer_chk_guard_local. This mirrors the minimal-malloc to libc-malloc handoff: __sigsetjmp/__longjmp are reached through RELRO function pointers via inline wrappers in <rtld-setjmp.h>, so dl-catch.c is unchanged, and the lookup reuses dl-minimal.c's libc-symbol binding. The handoff is opt-in per target via RTLD_USE_LIBC_SETJMP, defined in pointer_guard.h (only enable on x86_64 for now). Checked on x86_64-linux-gnu. Diff: --- elf/dl-minimal.c | 58 +++++++++++++++++----- elf/rtld.c | 13 +++++ include/rtld-setjmp.h | 66 ++++++++++++++++++++++++++ include/setjmp.h | 5 ++ sysdeps/unix/sysv/linux/x86_64/pointer_guard.h | 5 +- 5 files changed, 135 insertions(+), 12 deletions(-) diff --git a/elf/dl-minimal.c b/elf/dl-minimal.c index 591d49e3f5..cca42160a7 100644 --- a/elf/dl-minimal.c +++ b/elf/dl-minimal.c @@ -23,6 +23,7 @@ #include <dl-sym-post.h> #include <_itoa.h> #include <dl-minimal-malloc.h> +#include <rtld-setjmp.h> #include <stdio.h> #include <unistd.h> #include <errno.h> @@ -54,12 +55,13 @@ __rtld_malloc_is_complete (void) return __rtld_malloc != &__minimal_malloc; } -/* Lookup NAME at VERSION in the scope of MATCH. */ +/* Lookup NAME in the scope of MAIN_MAP at VERSION, or at the default + (current) version if VERSION is NULL. Used to bind the rtld + function-pointer handoffs to the real libc.so implementations. */ static void * -lookup_malloc_symbol (struct link_map *main_map, const char *name, - struct r_found_version *version) +lookup_libc_symbol (struct link_map *main_map, const char *name, + struct r_found_version *version) { - const ElfW(Sym) *ref = NULL; lookup_t result = _dl_lookup_symbol_x (name, main_map, &ref, main_map->l_scope, @@ -88,21 +90,55 @@ __rtld_malloc_init_real (struct link_map *main_map) version.hash = _dl_elf_hash (version.name); version.filename = NULL; - void *new_calloc = lookup_malloc_symbol (main_map, "calloc", &version); - void *new_free = lookup_malloc_symbol (main_map, "free", &version); - void *new_malloc = lookup_malloc_symbol (main_map, "malloc", &version); - void *new_realloc = lookup_malloc_symbol (main_map, "realloc", &version); + void *new_calloc = lookup_libc_symbol (main_map, "calloc", &version); + void *new_free = lookup_libc_symbol (main_map, "free", &version); + void *new_malloc = lookup_libc_symbol (main_map, "malloc", &version); + void *new_realloc = lookup_libc_symbol (main_map, "realloc", &version); /* Update the pointers in one go, so that any internal allocations - performed by lookup_malloc_symbol see a consistent - implementation. */ + performed by lookup_libc_symbol see a consistent implementation. */ __rtld_calloc = new_calloc; __rtld_free = new_free; __rtld_malloc = new_malloc; __rtld_realloc = new_realloc; } - +#ifdef RTLD_USE_LIBC_SETJMP +__typeof (__sigsetjmp) *__rtld_setjmp attribute_relro; +__rtld_longjmp_t *__rtld_longjmp attribute_relro; + +/* Bind a distinct name to the real (asm) symbol so it is called directly. */ +extern __typeof (__longjmp) __rtld_real_longjmp + __asm__ (__SYMBOL_PREFIX "__longjmp") attribute_hidden; + +/* ld.so provides __longjmp (operating on the inner __jmp_buf) but not the + _longjmp wrapper used for the libc.so implementation, so adapt it. */ +static void __attribute__ ((__noreturn__)) +rtld_local_longjmp (struct __jmp_buf_tag env[1], int val) +{ + __rtld_real_longjmp (env[0].__jmpbuf, val); + __builtin_unreachable (); +} + +void +__rtld_setjmp_init_stubs (void) +{ + __rtld_setjmp = &__sigsetjmp; + __rtld_longjmp = &rtld_local_longjmp; +} + +void +__rtld_setjmp_init_real (struct link_map *main_map) +{ + void *new_setjmp = lookup_libc_symbol (main_map, "__sigsetjmp", NULL); + void *new_longjmp = lookup_libc_symbol (main_map, "_longjmp", NULL); + + __rtld_setjmp = new_setjmp; + __rtld_longjmp = new_longjmp; +} +#endif /* RTLD_USE_LIBC_SETJMP */ + + /* Avoid signal frobnication in setjmp/longjmp. Keeps things smaller. */ #include <setjmp.h> diff --git a/elf/rtld.c b/elf/rtld.c index 12e1b4dd71..1980ca74f3 100644 --- a/elf/rtld.c +++ b/elf/rtld.c @@ -52,6 +52,7 @@ #include <dl-find_object.h> #include <dl-audit-check.h> #include <dl-call_tls_init_tp.h> +#include <rtld-setjmp.h> #include <assert.h> @@ -452,6 +453,9 @@ _dl_start_final (void *arg, struct dl_start_final_info *info) ElfW(Addr) start_addr; __rtld_malloc_init_stubs (); +#ifdef RTLD_USE_LIBC_SETJMP + __rtld_setjmp_init_stubs (); +#endif /* Do not use an initializer for these members because it would interfere with __rtld_static_init. */ @@ -2351,6 +2355,15 @@ dl_main (const ElfW(Phdr) *phdr, __rtld_mutex_init (); __rtld_malloc_init_real (main_map); +#ifdef RTLD_USE_LIBC_SETJMP + /* Switch the dynamic linker's exception-handling setjmp/longjmp to the + libc.so implementations, which mangle jmp_bufs with the pointer guard + in the thread descriptor. After this the ld.so-local copy is no + longer used, so clear it. */ + __rtld_setjmp_init_real (main_map); + __pointer_chk_guard_local = 0; +#endif + /* Update copy-relocated _r_debug if necessary. */ _dl_debug_post_relocate (main_map); } diff --git a/include/rtld-setjmp.h b/include/rtld-setjmp.h new file mode 100644 index 0000000000..e39797e986 --- /dev/null +++ b/include/rtld-setjmp.h @@ -0,0 +1,66 @@ +/* Redirection of setjmp/longjmp inside the dynamic linker. + 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/>. */ + +/* Some targets mangle ld.so's own setjmp/longjmp jmp_bufs with the global + __pointer_chk_guard_local, because the dynamic linker's first uses of + setjmp happen before the TCB is set up (see pointer_guard.h and the + analysis of dl_main), while libc.so and the application mangle with the + copy in the thread descriptor. On such targets __pointer_chk_guard_local + is a second copy of the process-wide pointer guard. */ + +#ifndef _RTLD_SETJMP_H +#define _RTLD_SETJMP_H + +#if IS_IN (rtld) +# include <pointer_guard.h> /* RTLD_USE_LIBC_SETJMP */ +#endif + +#ifdef RTLD_USE_LIBC_SETJMP + +# include <setjmp.h> + +extern __typeof (__sigsetjmp) *__rtld_setjmp attribute_hidden; +typedef void __rtld_longjmp_t (struct __jmp_buf_tag __env[1], int __val); +extern __rtld_longjmp_t *__rtld_longjmp attribute_hidden; + +__extern_inline int __attribute__ ((__returns_twice__)) +__sigsetjmp (struct __jmp_buf_tag __env[1], int __savemask) +{ + return __rtld_setjmp (__env, __savemask); +} + +__extern_inline void __attribute__ ((__noreturn__)) +__longjmp (__jmp_buf __env, int __val) +{ + __rtld_longjmp ((struct __jmp_buf_tag *) (void *) __env, __val); + __builtin_unreachable (); +} + +/* Activate the ld.so-local setjmp/longjmp. Called after the first + self-relocation, before any use of the exception handling. */ +void __rtld_setjmp_init_stubs (void) attribute_hidden; + +/* Switch to the libc.so setjmp/longjmp. Called while the RELRO variables + are still writable, after libc.so has been relocated. MAIN_MAP is the + link map of the executable. */ +struct link_map; +void __rtld_setjmp_init_real (struct link_map *main_map) attribute_hidden; + +#endif /* RTLD_USE_LIBC_SETJMP */ + +#endif /* _RTLD_SETJMP_H */ diff --git a/include/setjmp.h b/include/setjmp.h index 4997d0d7d2..2f4f126ca5 100644 --- a/include/setjmp.h +++ b/include/setjmp.h @@ -39,6 +39,11 @@ libc_hidden_proto (__sigsetjmp) extern __typeof (__sigsetjmp) __sigsetjmp attribute_hidden; # endif +/* In the dynamic linker, redirect __sigsetjmp/__longjmp through the + setjmp/longjmp handoff pointers (analogous to how <stdlib.h> pulls in the + malloc redirection from <rtld-malloc.h>). */ +# include <rtld-setjmp.h> + #endif #endif diff --git a/sysdeps/unix/sysv/linux/x86_64/pointer_guard.h b/sysdeps/unix/sysv/linux/x86_64/pointer_guard.h index 4f7abc64f4..525f7cf0f7 100644 --- a/sysdeps/unix/sysv/linux/x86_64/pointer_guard.h +++ b/sysdeps/unix/sysv/linux/x86_64/pointer_guard.h @@ -24,7 +24,10 @@ #if IS_IN (rtld) /* We cannot use the thread descriptor because in ld.so we use setjmp - earlier than the descriptor is initialized. */ + earlier than the descriptor is initialized. Also, enable the use of + libc function after libc relocation to avoid the need to duplicate the + __pointer_chk_guard value on __pointer_chk_guard_local. */ +# define RTLD_USE_LIBC_SETJMP 1 # ifdef __ASSEMBLER__ # define PTR_MANGLE(reg) xor __pointer_chk_guard_local(%rip), reg; \ rol $2*LP_SIZE+1, reg