Re: [PATCH v3] elf: Support multiple PT_GNU_RELRO segments
Adhemerval Zanella Netto <[email protected]>
| Newsgroups | gmane.comp.lib.glibc.alpha |
|---|---|
| Organization | Linaro |
| Message-ID | <[email protected]> |
On 30/07/26 18:19, Justin Rivera wrote: > 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. > elf/tst-relro-symbols.py is also updated to validate symbols against a > list of RELRO regions. > > Tested against elf/tst-relro-symbols.py and the glibc test suite, no > regression observed. Added an additional test for coverage of mutli > RELRO behavior. Required a script to patch dummy PT_NOTE segments into > PT_GNU_RELRO. > > Signed-off-by: Justin Rivera <[email protected]> Some comments below. Due to the large number of required changes, I implemented them all on a branch [1]. I also checked them on multiple ABIs, mostly on qemu-user, but I will take a look at some hardware with some different page sizes (powerpc for instance). [1] https://sourceware.org/git/?p=glibc.git;a=shortlog;h=refs/heads/azanella/multiple-relro > --- > elf/Makefile | 15 ++++++ > elf/dl-load.c | 5 -- > elf/dl-readonly-area.c | 23 ++++----- > elf/dl-reloc.c | 31 ++++++------ > elf/dl-support.c | 5 -- > elf/rtld.c | 15 ------ > elf/tst-relro-multi.c | 29 +++++++++++ > elf/tst-relro-multi.lds | 24 +++++++++ > elf/tst-relro-symbols.py | 34 +++++++------ > include/link.h | 4 -- > scripts/tst-relrofy.py | 102 +++++++++++++++++++++++++++++++++++++++ > 11 files changed, 216 insertions(+), 71 deletions(-) > create mode 100644 elf/tst-relro-multi.c > create mode 100644 elf/tst-relro-multi.lds > create mode 100755 scripts/tst-relrofy.py > > diff --git a/elf/Makefile b/elf/Makefile > index 94c5b7e6ed..344f04a787 100644 > --- a/elf/Makefile > +++ b/elf/Makefile > @@ -700,6 +700,21 @@ $(objpfx)tst-relro-libc.out: tst-relro-symbols.py $(..)/scripts/glibcelf.py \ > --required=__io_vtables \ > > $@ 2>&1; $(evaluate-test) > > +test-srcs += tst-relro-multi > +LDFLAGS-tst-relro-multi = -Wl,-z,now -Wl,-T,$(..)elf/tst-relro-multi.lds The linker script should be added as a test requisite. > + > +ifeq ($(run-built-tests),yes) > +tests-special += $(objpfx)tst-relro-multi-patched.out > + > +$(objpfx)tst-relro-multi-patched: $(objpfx)tst-relro-multi $(..)scripts/tst-relrofy.py > + cp $< $@ I think it should move to a temporary to avoid a stale up to date for the case of an error in the tst-relrofy.py (for instance, when make check is run after a subsequent failure). > + $(PYTHON) $(..)scripts/tst-relrofy.py $@ 1 > + > +$(objpfx)tst-relro-multi-patched.out: $(objpfx)tst-relro-multi-patched > + $(run-program-prefix) $< > $@ 2>&1; \ > + $(evaluate-test) > +endif > + We also need a static tests for this features, since it exercises a different code path. > ifeq ($(run-built-tests),yes) > tests-special += $(objpfx)tst-valgrind-smoke.out > endif > diff --git a/elf/dl-load.c b/elf/dl-load.c > index 95404adae9..e76d149f1f 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 833f455904..7883d6c7bf 100644 > --- a/elf/dl-readonly-area.c > +++ b/elf/dl-readonly-area.c > @@ -21,19 +21,20 @@ > static bool > 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, > + for (const ElfW(Phdr) *ph = l->l_phdr; ph < &l->l_phdr[l->l_phnum]; ++ph) > + if (ph->p_type == PT_GNU_RELRO) > + { > + uintptr_t relro_start = ALIGN_DOWN (l->l_addr + ph->p_vaddr, > + GLRO(dl_pagesize)); > + uintptr_t relro_end = ALIGN_DOWN (l->l_addr + ph->p_vaddr > + + ph->p_memsz, > 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; > + if (relro_start <= start && end <= relro_end) > + return dl_readonly_area_rdonly; > + } > This tests each PT_GNU_RELRO segment in isolation, but the protection this should apply is the union of the per-segment page-rounded ranges. For instance, with a 4k page size and two segments [0x2000, 0x3c00) and [0x3d00, 0x4800), the rounded ranges become [0x2000, 0x3000) and [0x3000, 0x4000), so 0x2000..0x3fff is contiguously read-only. A fortified format string at [0x2fe0, 0x3020) is fully protected, yet it is contained in neither rounded range, so check_relro reports it writable and the %n check aborts a valid program. Although this might not be arguability created by the compiler, this check should be done defensively. There is also a type confusion, where the function should return 'bool', but the patch changes to return dl_readonly_area_error_type. It would be better to do something like (assuming RELRO segments don't overlap): struct dl_relro_range { ElfW(Addr) start; ElfW(Addr) end; }; 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)), }; } static enum dl_readonly_area_error_type check_relro (const struct link_map *l, uintptr_t start, uintptr_t end) { 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; } return dl_readonly_area_writable; } And adjust check_relro callers accordingly. > - } > + /* 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 15a6a4cffe..f4003bfee5 100644 > --- a/elf/dl-reloc.c > +++ b/elf/dl-reloc.c > @@ -348,23 +348,22 @@ _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_("\ > + const ElfW(Phdr) *ph; > + for (ph = l->l_phdr; ph < &l->l_phdr[l->l_phnum]; ++ph) > + if (ph->p_type == PT_GNU_RELRO) > + { > + ElfW(Addr) start = ALIGN_DOWN (l->l_addr + ph->p_vaddr, > + GLRO(dl_pagesize)); > + ElfW(Addr) end = ALIGN_DOWN (l->l_addr + ph->p_vaddr + ph->p_memsz, > + GLRO(dl_pagesize)); > + if (start != end > + && __mprotect ((void *) start, end - 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 b57fd74670..041b89da79 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 fc053df858..b9d0047a68 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.c b/elf/tst-relro-multi.c > new file mode 100644 > index 0000000000..f8e1e0ac30 > --- /dev/null > +++ b/elf/tst-relro-multi.c > @@ -0,0 +1,29 @@ > +/* 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/>. */ > +/* Three page-padded regions in the RW load. A and B become PT_GNU_RELRO > + after post-processing; the .data gap between them stays writable, so a > + correct loader must protect two *non-contiguous* relro regions. */ > +__attribute__ ((section (".note.a"), used)) unsigned long int relro_a = 0xAAAA; > +__attribute__ ((section (".gap"), used)) unsigned long int gap_d = 0xDDDD; > +__attribute__ ((section (".note.b"), used)) unsigned long int relro_b = 0xBBBB; The __attribute__((section(".note.a"))) cannot express a section type, gcc emits '.section .note.a,"aw",@progbits' and this have some problems: * gas warns setting incorrect section attributes for .note.a (and possible an error distro or gas start to make --fatal-warnings default). * this heuristic is gas-specific, it is not strickly required to make those PT_NOTES. It would be better to code the requirements in an explicit TU: .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 > + > +int > +main (void) > +{ > + return 0; This does not add any regression tests, old glibc still loads programs with multiple PT_GNU_RELRO. You need to check whether the RO are properly applied: 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; } > +} > diff --git a/elf/tst-relro-multi.lds b/elf/tst-relro-multi.lds > new file mode 100644 > index 0000000000..eeb6af15d3 > --- /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 ffbe9958fe..93d9308851 100644 > --- a/elf/tst-relro-symbols.py > +++ b/elf/tst-relro-symbols.py > @@ -32,25 +32,29 @@ 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: This is previous issue, but I think with 'end = start + size - 1' a 1-size object is not properly handle here (it should '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'), start, size)) I think we should keep the region in the debug message > > def get_parser(): > """Return an argument parser for this script.""" > @@ -78,7 +82,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 +113,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 8f851d2212..04274b490e 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-relrofy.py b/scripts/tst-relrofy.py > new file mode 100755 > index 0000000000..0c396fcf4e > --- /dev/null > +++ b/scripts/tst-relrofy.py > @@ -0,0 +1,102 @@ > +#! /usr/bin/env python3 > +# ELF editor to convert PT_NOTE to PT_GNU_RELRO. > +# Copyright (C) 2026 Free Software Foundation, Inc. > +# Copyright The GNU Toolchain Authors. > +# 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/>. > +"""Convert placeholder PT_NOTE segments into PT_GNU_RELRO inplace. > + > +Targets only PT_NOTE phdrs whose p_vaddr lies inside a writable PT_LOAD, so > +genuine notes (build-id, gnu.property) in the read-only load are skipped. > +""" > + > +from __future__ import print_function > + > +import struct > +import sys > + > +PT_LOAD = 1 > +PT_NOTE = 4 > +PT_GNU_RELRO = 0x6474E552 > +PF_W = 2 > + > + > +def get_field(elf, base, offset, fmt): > + """Unpack a specific field from an ELF program header.""" > + return struct.unpack_from(fmt, elf, base + offset)[0] > + > + > +def main(path, set_align=None): > + """Convert placeholder PT_NOTE segments into PT_GNU_RELRO inplace. > + > + Args: > + path: the filepath of the ELF binary to make PT_NOTE replacements on > + set_align: optional p_align value to set on converted PT_GNU_RELRO > + segments > + """ > + with open(path, 'rb') as f: > + elf = bytearray(f.read()) > + > + if elf[:4] != b'\x7fELF': > + sys.exit('Error: %s is not a valid ELF file.' % path) > + > + is64 = elf[4] == 2 > + endian = '<' if elf[5] == 1 else '>' > + type_fmt = '%sI' % endian > + addr_fmt = '%sQ' % endian if is64 else '%sI' % endian > + > + if is64: > + ph_off = struct.unpack_from(endian + 'Q', elf, 0x20)[0] > + phentsz, phnum = struct.unpack_from(endian + 'HH', elf, 0x36) > + o_type, o_flags, o_vaddr, o_memsz, o_align = 0, 4, 16, 40, 48 > + else: > + ph_off = struct.unpack_from(endian + 'I', elf, 0x1C)[0] > + phentsz, phnum = struct.unpack_from(endian + 'HH', elf, 0x2A) > + o_type, o_flags, o_vaddr, o_memsz, o_align = 0, 24, 8, 20, 28 > + > + wr_loads = [] > + for i in range(phnum): > + base = ph_off + i * phentsz > + if get_field(elf, base, o_type, type_fmt) == PT_LOAD and ( > + get_field(elf, base, o_flags, type_fmt) & PF_W > + ): > + vaddr = get_field(elf, base, o_vaddr, addr_fmt) > + memsz = get_field(elf, base, o_memsz, addr_fmt) > + wr_loads.append((vaddr, vaddr + memsz)) > + > + converted = 0 > + for i in range(phnum): > + base = ph_off + i * phentsz > + if get_field(elf, base, o_type, type_fmt) != PT_NOTE: > + continue > + > + vaddr = get_field(elf, base, o_vaddr, addr_fmt) > + if not any(lo <= vaddr < hi for lo, hi in wr_loads): > + continue # Real note, skip > + > + struct.pack_into(type_fmt, elf, base + o_type, PT_GNU_RELRO) > + if set_align is not None: > + struct.pack_into(addr_fmt, elf, base + o_align, set_align) > + converted += 1 > + > + with open(path, 'wb') as f: > + f.write(elf) > + print('converted %d PT_NOTE -> PT_GNU_RELRO in %s' % (converted, path)) > + > + > +if __name__ == '__main__': > + align = int(sys.argv[2], 0) if len(sys.argv) > 2 else 1 > + main(sys.argv[1], align) Although I initially suggested this, most of ELF parsing is already implemented in scripts/tst-elf-edit.py. To avoid adding another ELF parsing script, I think it would be better to fold on it: 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