[PATCH] elf: Support multiple PT_GNU_RELRO segments

Justin Rivera jnrivera@google.com
Mon Jul 6 21:10:39 GMT 2026


Hello,

While this patch appears to be working as intended, I had a question
regarding any additions to the test suite for testing RELRO behavior.

This patch modifies elf/tst-relro-symbols.py, which can statically check
ELF headers. However, there don't seem to be any dynamic checks that would
work for multiple RELRO segments. We were able to do some ad-hoc testing
with some hand-written assembly, but getting something working within the
test suite seemed to be a bit trickier. (Often ran into
`/usr/bin/ld: foo.so: warning: unable to allocate any sections to PT_GNU_RELRO segment`
without some additional patching hack, for example).

Are there any suggestions on how to best test the behavior changed by this patch?
--- >8 ---
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.

Signed-off-by: Justin Rivera <jnrivera@google.com>
---
 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-symbols.py | 34 +++++++++++++++++++---------------
 include/link.h           |  4 ----
 7 files changed, 46 insertions(+), 71 deletions(-)

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;
+      }
 
-    }
+  /* 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 e5ba71fef1..1204a5dd80 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-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:
+            return
+    error(
+        '{} {!r} of size {} at 0x{:x} is not in any RELRO range'.format(
+            kind, name.decode('UTF-8'), start, size))
 
 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;
   };
-- 
2.55.0.rc2.803.g1fd1e6609c-goog



More information about the Libc-alpha mailing list