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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.