[PATCH v3] elf: Support multiple PT_GNU_RELRO segments

Adhemerval Zanella Netto adhemerval.zanella@linaro.org
Mon Aug 10 17:40:38 GMT 2026



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 <jnrivera@google.com>

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


More information about the Libc-alpha mailing list