[PATCH] elf: Support multiple PT_GNU_RELRO segments
Adhemerval Zanella <[email protected]>
| Newsgroups | gmane.comp.lib.glibc.alpha |
|---|---|
| Message-ID | <[email protected]> |
When binaries become extremely large, PC-relative references to a single GOT can exceed the +/- 2GB limit. To resolve this, we'd like to generate multiple GOTs, which would require multiple PT_GNU_RELRO segments. This change modifies RELRO protection by removing cached fields (l_relro_addr and l_relro_size) and instead iterating over all program headers to protect every PT_GNU_RELRO segment discovered. The new tests tst-relro-multi and tst-relro-multi-static carry two placeholder writable PT_NOTE segments, placed by a linker script and converted to PT_GNU_RELRO by the new --note-to-relro option of scripts/tst-elf-edit.py. Co-authored-by: Justin Rivera <[email protected]> --- elf/Makefile | 32 +++++++++++++++++++++++++ elf/dl-load.c | 5 ---- elf/dl-readonly-area.c | 31 ++++++++++++++---------- elf/dl-reloc.c | 28 ++++++++++------------ elf/dl-support.c | 5 ---- elf/rtld.c | 15 ------------ elf/tst-relro-multi-notes.S | 46 ++++++++++++++++++++++++++++++++++++ elf/tst-relro-multi-static.c | 19 +++++++++++++++ elf/tst-relro-multi.c | 43 +++++++++++++++++++++++++++++++++ elf/tst-relro-multi.lds | 24 +++++++++++++++++++ elf/tst-relro-symbols.py | 36 ++++++++++++++++------------ include/link.h | 4 ---- scripts/tst-elf-edit.py | 39 ++++++++++++++++++++++++++++++ sysdeps/generic/ldsodefs.h | 19 +++++++++++++++ 14 files changed, 273 insertions(+), 73 deletions(-) create mode 100644 elf/tst-relro-multi-notes.S create mode 100644 elf/tst-relro-multi-static.c create mode 100644 elf/tst-relro-multi.c create mode 100644 elf/tst-relro-multi.lds diff --git a/elf/Makefile b/elf/Makefile index 8b063e1bbae..a6c4afc6265 100644 --- a/elf/Makefile +++ b/elf/Makefile @@ -1530,6 +1530,38 @@ tests-special += $(objpfx)tst-tls-debug-recursive.out tests-special += $(objpfx)tst-dl-debug-exclude.out endif +test-srcs += tst-relro-multi +tests-static += tst-relro-multi-static +extra-test-objs += tst-relro-multi-notes.o +LDFLAGS-tst-relro-multi = -Wl,-z,now -Wl,-T,$(..)elf/tst-relro-multi.lds +LDFLAGS-tst-relro-multi-static = -Wl,-T,$(..)elf/tst-relro-multi.lds +$(objpfx)tst-relro-multi: +nolink-deps += tst-relro-multi.lds +$(objpfx)tst-relro-multi: $(objpfx)tst-relro-multi-notes.o tst-relro-multi.lds +$(objpfx)tst-relro-multi-static: +nolink-deps += tst-relro-multi.lds +$(objpfx)tst-relro-multi-static: $(objpfx)tst-relro-multi-notes.o \ + tst-relro-multi.lds + +$(objpfx)tst-relro-multi-patched $(objpfx)tst-relro-multi-static-patched: \ + %-patched: % $(..)scripts/tst-elf-edit.py + cp $< [email protected] + $(PYTHON) $(..)scripts/tst-elf-edit.py --note-to-relro 2 [email protected] + mv [email protected] $@ + +$(objpfx)tst-relro-multi-patched.out: $(objpfx)tst-relro-multi-patched \ + $(objpfx)ld.so + $(run-program-prefix) $< > $@ 2>&1; \ + $(evaluate-test) + +$(objpfx)tst-relro-multi-static-patched.out: \ + $(objpfx)tst-relro-multi-static-patched + $(test-wrapper) $< > $@ 2>&1; \ + $(evaluate-test) + +ifeq ($(run-built-tests),yes) +tests-special += $(objpfx)tst-relro-multi-patched.out +tests-special += $(objpfx)tst-relro-multi-static-patched.out +endif + # The test requires shared _and_ PIE because the executable # unit test driver must be able to link with the shared object # that is going to eventually go into an installed DSO. diff --git a/elf/dl-load.c b/elf/dl-load.c index 95404adae94..e76d149f1f6 100644 --- a/elf/dl-load.c +++ b/elf/dl-load.c @@ -1091,11 +1091,6 @@ _dl_map_object_scan_phdrs (struct dl_pt_load_iterator *it, case PT_GNU_STACK: *stack_flagsp = pf_to_prot (ph->p_flags); break; - - case PT_GNU_RELRO: - l->l_relro_addr = ph->p_vaddr; - l->l_relro_size = ph->p_memsz; - break; } } diff --git a/elf/dl-readonly-area.c b/elf/dl-readonly-area.c index 833f4559049..9a945031314 100644 --- a/elf/dl-readonly-area.c +++ b/elf/dl-readonly-area.c @@ -17,23 +17,28 @@ <https://www.gnu.org/licenses/>. */ #include <ldsodefs.h> +#include <sys/param.h> -static bool +static enum dl_readonly_area_error_type check_relro (const struct link_map *l, uintptr_t start, uintptr_t end) { - if (l->l_relro_addr != 0) - { - uintptr_t relro_start = ALIGN_DOWN (l->l_addr + l->l_relro_addr, - GLRO(dl_pagesize)); - uintptr_t relro_end = ALIGN_DOWN (l->l_addr + l->l_relro_addr - + l->l_relro_size, - GLRO(dl_pagesize)); - /* RELRO is caved out from a RW segment, so the next range is either - RW or nonexistent. */ - return relro_start <= start && end <= relro_end - ? dl_readonly_area_rdonly : dl_readonly_area_writable; + /* The range may span multiple PT_GNU_RELRO segments whose ranges are + adjacent, so accumulate the covered bytes. */ + size_t size = end - start; + for (const ElfW(Phdr) *ph = l->l_phdr; ph < &l->l_phdr[l->l_phnum]; ++ph) + if (ph->p_type == PT_GNU_RELRO) + { + struct dl_relro_range relro = _dl_relro_range (l, ph); + uintptr_t from = MAX (relro.start, start); + uintptr_t to = MIN (relro.end, end); + if (from < to) + size -= to - from; + if (size == 0) + return dl_readonly_area_rdonly; + } - } + /* RELRO is caved out from a RW segment, so any range outside of + a RELRO segment is either RW or nonexistent. */ return dl_readonly_area_writable; } diff --git a/elf/dl-reloc.c b/elf/dl-reloc.c index 191d39cbbd9..e72f8c5a606 100644 --- a/elf/dl-reloc.c +++ b/elf/dl-reloc.c @@ -406,23 +406,19 @@ _dl_relocate_object (struct link_map *l, struct r_scope_elem *scope[], void _dl_protect_relro (struct link_map *l) { - if (l->l_relro_size == 0) - return; - - ElfW(Addr) start = ALIGN_DOWN((l->l_addr - + l->l_relro_addr), - GLRO(dl_pagesize)); - ElfW(Addr) end = ALIGN_DOWN((l->l_addr - + l->l_relro_addr - + l->l_relro_size), - GLRO(dl_pagesize)); - if (start != end - && __mprotect ((void *) start, end - start, PROT_READ) < 0) - { - static const char errstring[] = N_("\ + for (const ElfW(Phdr) *ph = l->l_phdr; ph < &l->l_phdr[l->l_phnum]; ++ph) + if (ph->p_type == PT_GNU_RELRO) + { + struct dl_relro_range range = _dl_relro_range (l, ph); + if (range.start != range.end + && __mprotect ((void *) range.start, range.end - range.start, + PROT_READ) < 0) + { + static const char errstring[] = N_("\ cannot apply additional memory protection after relocation"); - _dl_signal_error (errno, l->l_name, NULL, errstring); - } + _dl_signal_error (errno, l->l_name, NULL, errstring); + } + } } void diff --git a/elf/dl-support.c b/elf/dl-support.c index b57fd74670b..041b89da797 100644 --- a/elf/dl-support.c +++ b/elf/dl-support.c @@ -327,11 +327,6 @@ _dl_non_dynamic_init (void) case PT_GNU_STACK: _dl_stack_prot_flags = pf_to_prot (ph->p_flags); break; - - case PT_GNU_RELRO: - _dl_main_map.l_relro_addr = ph->p_vaddr; - _dl_main_map.l_relro_size = ph->p_memsz; - break; } _dl_handle_execstack_tunable (); diff --git a/elf/rtld.c b/elf/rtld.c index fc053df8586..b9d0047a686 100644 --- a/elf/rtld.c +++ b/elf/rtld.c @@ -1198,11 +1198,6 @@ rtld_setup_main_map (struct link_map *main_map) case PT_GNU_STACK: GL(dl_stack_prot_flags) = pf_to_prot (ph->p_flags); break; - - case PT_GNU_RELRO: - main_map->l_relro_addr = ph->p_vaddr; - main_map->l_relro_size = ph->p_memsz; - break; } _dl_executable_postprocess (main_map, phdr, phnum); @@ -1270,16 +1265,6 @@ rtld_setup_phdr (void) & ~(GLRO(dl_pagesize) - 1)); } } - - /* PT_GNU_RELRO is usually the last phdr. */ - size_t cnt = rtld_ehdr->e_phnum; - while (cnt-- > 0) - if (rtld_phdr[cnt].p_type == PT_GNU_RELRO) - { - _dl_rtld_map.l_relro_addr = rtld_phdr[cnt].p_vaddr; - _dl_rtld_map.l_relro_size = rtld_phdr[cnt].p_memsz; - break; - } } /* Adjusts the contents of the stack and related globals for the user diff --git a/elf/tst-relro-multi-notes.S b/elf/tst-relro-multi-notes.S new file mode 100644 index 00000000000..c66ad8e0baf --- /dev/null +++ b/elf/tst-relro-multi-notes.S @@ -0,0 +1,46 @@ +/* Data definitions for the multiple PT_GNU_RELRO 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/>. */ + +/* Assembly is used because compilers emit SHT_PROGBITS for + __attribute__ ((section)) definitions and gas might warns when its + name-based heuristic retypes .note.* sections; and note sections + must not have the SHF_WRITE flag for the same reason. */ + + .section .note.a, "a", %note + .balign 8 + .globl relro_a + .type relro_a, %object +relro_a: + .dc.a 0xAAAA + .size relro_a, .-relro_a + + .section .gap, "aw", %progbits + .balign 8 + .globl gap_d + .type gap_d, %object +gap_d: + .dc.a 0xDDDD + .size gap_d, .-gap_d + + .section .note.b, "a", %note + .balign 8 + .globl relro_b + .type relro_b, %object +relro_b: + .dc.a 0xBBBB + .size relro_b, .-relro_b diff --git a/elf/tst-relro-multi-static.c b/elf/tst-relro-multi-static.c new file mode 100644 index 00000000000..8f795c84bbc --- /dev/null +++ b/elf/tst-relro-multi-static.c @@ -0,0 +1,19 @@ +/* Multiple PT_GNU_RELRO test, static 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/>. */ + +#include "tst-relro-multi.c" diff --git a/elf/tst-relro-multi.c b/elf/tst-relro-multi.c new file mode 100644 index 00000000000..be8feb3252a --- /dev/null +++ b/elf/tst-relro-multi.c @@ -0,0 +1,43 @@ +/* Multiple PT_GNU_RELRO 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 <support/check.h> +#include <support/check_mem_access.h> + +/* The tst-elf-edit.py --note-to-relro convert relro_a and relro_b + to page-padded PT_GNU_RELRO seguments. */ +extern unsigned long int relro_a; +extern unsigned long int gap_d; +extern unsigned long int relro_b; + +static int +do_test (void) +{ + /* Both RELRO regions must be readable but not writable. */ + TEST_COMPARE (check_mem_access (&relro_a, false), true); + TEST_COMPARE (check_mem_access (&relro_a, true), false); + TEST_COMPARE (check_mem_access (&relro_b, false), true); + TEST_COMPARE (check_mem_access (&relro_b, true), false); + + /* The gap between the two RELRO regions must remain writable. */ + TEST_COMPARE (check_mem_access (&gap_d, true), true); + + return 0; +} + +#include <support/test-driver.c> diff --git a/elf/tst-relro-multi.lds b/elf/tst-relro-multi.lds new file mode 100644 index 00000000000..eeb6af15d3c --- /dev/null +++ b/elf/tst-relro-multi.lds @@ -0,0 +1,24 @@ +/* Multiple PT_GNU_RELRO 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/>. */ +SECTIONS { + . = ALIGN(CONSTANT(MAXPAGESIZE)); + .note.a : { *(.note.a) . = ALIGN(CONSTANT(MAXPAGESIZE)); } + .gap : { *(.gap) . = ALIGN(CONSTANT(MAXPAGESIZE)); } + .note.b : { *(.note.b) . = ALIGN(CONSTANT(MAXPAGESIZE)); } +} +INSERT AFTER .data; diff --git a/elf/tst-relro-symbols.py b/elf/tst-relro-symbols.py index ffbe9958fed..7633a8dfb84 100644 --- a/elf/tst-relro-symbols.py +++ b/elf/tst-relro-symbols.py @@ -32,25 +32,31 @@ sys.path.append(os.path.join( import glibcelf -def find_relro(path: str, img: glibcelf.Image) -> (int, int): - """Discover the address range of the PT_GNU_RELRO segment.""" +def find_relro(path: str, img: glibcelf.Image) -> list: + """Discover the address ranges of the PT_GNU_RELRO segments.""" + regions = [] for phdr in img.phdrs(): if phdr.p_type == glibcelf.Pt.PT_GNU_RELRO: # The computation is not entirely accurate because # _dl_protect_relro in elf/dl-reloc.c rounds both the # start end and downwards using the run-time page size. - return phdr.p_vaddr, phdr.p_vaddr + phdr.p_memsz - sys.stdout.write('{}: error: no PT_GNU_RELRO segment\n'.format(path)) - sys.exit(1) + regions.append((phdr.p_vaddr, phdr.p_vaddr + phdr.p_memsz)) + if not regions: + sys.stdout.write('{}: error: no PT_GNU_RELRO segment\n'.format(path)) + sys.exit(1) + return regions -def check_in_relro(kind, relro_begin, relro_end, name, start, size, error): - """Check if a section or symbol falls within in the RELRO segment.""" +def check_in_relro(kind, relro_regions, name, start, size, error): + """Check if a section or symbol falls within in any RELRO segment.""" end = start + size - 1 - if not (relro_begin <= start < end < relro_end): - error( - '{} {!r} of size {} at 0x{:x} is not in RELRO range [0x{:x}, 0x{:x})'.format( - kind, name.decode('UTF-8'), start, size, - relro_begin, relro_end)) + for relro_begin, relro_end in relro_regions: + if relro_begin <= start <= end < relro_end: + return + error( + '{} {!r} of size {} at 0x{:x} is not in any RELRO range: {}'.format( + kind, name.decode('UTF-8'), size, start, + ', '.join('[0x{:x}, 0x{:x})'.format(*region) + for region in relro_regions))) def get_parser(): """Return an argument parser for this script.""" @@ -78,7 +84,7 @@ def main(argv): symbols_found = set() # Discover the extent of the RELRO segment. - relro_begin, relro_end = find_relro(opts.object, img) + relro_regions = find_relro(opts.object, img) symbol_table_found = False errors = False @@ -109,13 +115,13 @@ def main(argv): sym.st_name.decode('UTF-8'))) continue - check_in_relro('symbol', relro_begin, relro_end, + check_in_relro('symbol', relro_regions, sym.st_name, sym.st_value, sym.st_size, error) continue # SHT_SYMTAB if shdr.sh_name == b'.data.rel.ro' \ or shdr.sh_name.startswith(b'.data.rel.ro.'): - check_in_relro('section', relro_begin, relro_end, + check_in_relro('section', relro_regions, shdr.sh_name, shdr.sh_addr, shdr.sh_size, error) continue diff --git a/include/link.h b/include/link.h index 8f851d2212d..04274b490ea 100644 --- a/include/link.h +++ b/include/link.h @@ -340,10 +340,6 @@ struct link_map lock. See also: CONCURRENCY NOTES in cxa_thread_atexit_impl.c. */ size_t l_tls_dtor_count; - /* Information used to change permission after the relocations are - done. */ - ElfW(Addr) l_relro_addr; - size_t l_relro_size; unsigned long long int l_serial; }; diff --git a/scripts/tst-elf-edit.py b/scripts/tst-elf-edit.py index 07fa7e90f55..4c5e73e2f0e 100644 --- a/scripts/tst-elf-edit.py +++ b/scripts/tst-elf-edit.py @@ -47,7 +47,11 @@ ET_EXEC=2 ET_DYN=3 PT_LOAD=1 +PT_NOTE=4 PT_TLS=7 +PT_GNU_RELRO=0x6474e552 + +PF_W=2 def elf_types_fmts(e_ident): endian = '<' if e_ident[EI_DATA] == ELFDATA2LSB else '>' @@ -156,6 +160,34 @@ def elf_edit_maximize_tls_size(phdr, elfclass): else: phdr.p_memsz = 1 << 63 +def elf_edit_note_to_relro(f, e_ident, ehdr, expected): + phdrs = [] + for i in range(0, ehdr.e_phnum): + phdr = Elf_Phdr(e_ident) + f.seek(ehdr.e_phoff + i * phdr.len) + phdr.read(f) + phdrs.append(phdr) + + wr_loads = [(p.p_vaddr, p.p_vaddr + p.p_memsz) for p in phdrs + if p.p_type == PT_LOAD and (p.p_flags & PF_W) != 0] + + converted = 0 + for i, phdr in enumerate(phdrs): + if phdr.p_type != PT_NOTE: + continue + if not any(lo <= phdr.p_vaddr < hi for lo, hi in wr_loads): + continue + phdr.p_type = PT_GNU_RELRO + # Match the alignment the linker uses for PT_GNU_RELRO. + phdr.p_align = 1 + f.seek(ehdr.e_phoff + i * phdr.len) + phdr.write(f) + converted += 1 + + if converted != expected: + error('{}: converted {} PT_NOTE segment(s), expected {}'.format( + f.name, converted, expected)) + def elf_edit(f, opts): ei_nident_fmt = 'c' * EI_NIDENT ei_nident_len = struct.calcsize(ei_nident_fmt) @@ -184,6 +216,10 @@ def elf_edit(f, opts): if ehdr.e_type not in (ET_EXEC, ET_DYN): error('{}: not an executable or shared library'.format(f.name)) + if opts.note_to_relro is not None: + elf_edit_note_to_relro(f, e_ident, ehdr, opts.note_to_relro) + return + phdr = Elf_Phdr(e_ident) maximize_tls_size_done = False for i in range(0, ehdr.e_phnum): @@ -210,6 +246,9 @@ def get_parser(): help='How to set the LOAD alignment') parser.add_argument('--maximize-tls-size', action='store_true', help='Set maximum PT_TLS size') + parser.add_argument('--note-to-relro', type=int, metavar='COUNT', + help='Convert COUNT PT_NOTE segments in writable ' + 'PT_LOAD segments to PT_GNU_RELRO') parser.add_argument('output', help='ELF file to edit') return parser diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h index 305ca6e0df2..0d5f2a2c96d 100644 --- a/sysdeps/generic/ldsodefs.h +++ b/sysdeps/generic/ldsodefs.h @@ -32,6 +32,7 @@ #include <dlfcn.h> #include <fpu_control.h> #include <sys/mman.h> +#include <libc-pointer-arith.h> #include <link.h> #include <dl-lookupcfg.h> #include <dl-sysdep.h> @@ -1037,6 +1038,24 @@ void _dl_relocate_object_no_relro (struct link_map *map, /* Protect PT_GNU_RELRO area. */ extern void _dl_protect_relro (struct link_map *map) attribute_hidden; +struct dl_relro_range +{ + ElfW(Addr) start; + ElfW(Addr) end; +}; + +/* Compute the range for the PT_GNU_RELRO segment PH of map L. */ +static inline struct dl_relro_range +_dl_relro_range (const struct link_map *l, const ElfW(Phdr) *ph) +{ + return (struct dl_relro_range) + { + .start = ALIGN_DOWN (l->l_addr + ph->p_vaddr, GLRO(dl_pagesize)), + .end = ALIGN_DOWN (l->l_addr + ph->p_vaddr + ph->p_memsz, + GLRO(dl_pagesize)), + }; +} + /* Call _dl_signal_error with a message about an unhandled reloc type. TYPE is the result of ELFW(R_TYPE) (r_info), i.e. an R_<CPU>_* value. PLT is nonzero if this was a PLT reloc; it just affects the message. */ -- 2.53.0