[PATCH] elf: Support multiple PT_GNU_RELRO segments

Justin Rivera <[email protected]>
Newsgroups gmane.comp.lib.glibc.alpha
Message-ID <[email protected]>
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 <[email protected]>
---
 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
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.