unofficial mirror of libc-alpha@sourceware.org
 help / color / mirror / Atom feed
From: Florian Weimer <fweimer@redhat.com>
To: libc-alpha@sourceware.org
Subject: [PATCH v3 24/32] elf: Implement a basic protected memory allocator
Date: Thu, 07 Dec 2023 11:32:28 +0100	[thread overview]
Message-ID: <4c5e23c8adeaf0a05cb3655eb896c7c40da15772.1701944612.git.fweimer@redhat.com> (raw)
In-Reply-To: <cover.1701944612.git.fweimer@redhat.com>

Use it to keep the link maps read-only most of the time.  The path to
the link maps is not yet protected (they still come from GL (dl_nns)).
However, direct overwrites over l_info (l_info[DT_FINI] in particular)
are blocked.

In _dl_new_object, do not assume that the allocator provides
zeroed memory.
---
 elf/Makefile                 |  11 +++
 elf/dl-close.c               |  20 ++++--
 elf/dl-libc_freeres.c        |   5 ++
 elf/dl-load.c                |  33 +++++++--
 elf/dl-object.c              |  24 ++++---
 elf/dl-open.c                |  18 +++++
 elf/dl-protmem-internal.h    |  39 +++++++++++
 elf/dl-protmem.c             | 132 +++++++++++++++++++++++++++++++++++
 elf/dl-protmem.h             |  93 ++++++++++++++++++++++++
 elf/dl-protmem_bootstrap.h   |   8 ++-
 elf/rtld.c                   |  10 +++
 elf/tst-relro-linkmap-mod1.c |  42 +++++++++++
 elf/tst-relro-linkmap-mod2.c |   2 +
 elf/tst-relro-linkmap-mod3.c |   2 +
 elf/tst-relro-linkmap.c      | 112 +++++++++++++++++++++++++++++
 include/link.h               |   3 +
 sysdeps/generic/ldsodefs.h   |   8 ++-
 17 files changed, 544 insertions(+), 18 deletions(-)
 create mode 100644 elf/dl-protmem-internal.h
 create mode 100644 elf/dl-protmem.c
 create mode 100644 elf/dl-protmem.h
 create mode 100644 elf/tst-relro-linkmap-mod1.c
 create mode 100644 elf/tst-relro-linkmap-mod2.c
 create mode 100644 elf/tst-relro-linkmap-mod3.c
 create mode 100644 elf/tst-relro-linkmap.c

diff --git a/elf/Makefile b/elf/Makefile
index feeaffe533..7ababc0fc4 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -72,6 +72,7 @@ dl-routines = \
   dl-open \
   dl-origin \
   dl-printf \
+  dl-protmem \
   dl-reloc \
   dl-runtime \
   dl-scope \
@@ -117,6 +118,7 @@ elide-routines.os = \
 
 # These object files are only included in the dynamically-linked libc.
 shared-only-routines = \
+  dl-protmem \
   libc-dl-profile \
   libc-dl-profstub \
   libc-dl_find_object \
@@ -505,6 +507,7 @@ tests-internal += \
   tst-dl_find_object-threads \
   tst-dlmopen2 \
   tst-ptrguard1 \
+  tst-relro-linkmap \
   tst-stackguard1 \
   tst-tls-surplus \
   tst-tls3 \
@@ -872,6 +875,9 @@ modules-names += \
   tst-null-argv-lib \
   tst-p_alignmod-base \
   tst-p_alignmod3 \
+  tst-relro-linkmap-mod1 \
+  tst-relro-linkmap-mod2 \
+  tst-relro-linkmap-mod3 \
   tst-relsort1mod1 \
   tst-relsort1mod2 \
   tst-ro-dynamic-mod \
@@ -3031,3 +3037,8 @@ $(objpfx)tst-nodeps2-mod.so: $(common-objpfx)libc.so \
 	$(LINK.o) -Wl,--no-as-needed -nostartfiles -nostdlib -shared -o $@ $^
 $(objpfx)tst-nodeps2.out: \
   $(objpfx)tst-nodeps1-mod.so $(objpfx)tst-nodeps2-mod.so
+
+LDFLAGS-tst-relro-linkmap = -Wl,-E
+$(objpfx)tst-relro-linkmap: $(objpfx)tst-relro-linkmap-mod1.so
+$(objpfx)tst-relro-linkmap.out: $(objpfx)tst-dlopenfailmod1.so \
+  $(objpfx)tst-relro-linkmap-mod2.so $(objpfx)tst-relro-linkmap-mod3.so
diff --git a/elf/dl-close.c b/elf/dl-close.c
index 8f9d57df39..8391abe2d7 100644
--- a/elf/dl-close.c
+++ b/elf/dl-close.c
@@ -33,6 +33,7 @@
 #include <tls.h>
 #include <stap-probe.h>
 #include <dl-find_object.h>
+#include <dl-protmem.h>
 
 #include <dl-unmap-segments.h>
 
@@ -130,6 +131,9 @@ _dl_close_worker (struct link_map_private *map, bool force)
       return;
     }
 
+  /* Actual changes are about to happen.  */
+  _dl_protmem_begin ();
+
   Lmid_t nsid = map->l_ns;
   struct link_namespaces *ns = &GL(dl_ns)[nsid];
 
@@ -260,7 +264,10 @@ _dl_close_worker (struct link_map_private *map, bool force)
 
 	  /* Call its termination function.  Do not do it for
 	     half-cooked objects.  Temporarily disable exception
-	     handling, so that errors are fatal.  */
+	     handling, so that errors are fatal.
+
+	     Link maps are writable during this call, but avoiding
+	     that is probably too costly.  */
 	  if (imap->l_rw->l_init_called)
 	    _dl_catch_exception (NULL, _dl_call_fini, imap);
 
@@ -354,8 +361,11 @@ _dl_close_worker (struct link_map_private *map, bool force)
 		  newp = (struct r_scope_elem **)
 		    malloc (new_size * sizeof (struct r_scope_elem *));
 		  if (newp == NULL)
-		    _dl_signal_error (ENOMEM, "dlclose", NULL,
-				      N_("cannot create scope list"));
+		    {
+		      _dl_protmem_end ();
+		      _dl_signal_error (ENOMEM, "dlclose", NULL,
+					N_("cannot create scope list"));
+		    }
 		}
 
 	      /* Copy over the remaining scope elements.  */
@@ -709,7 +719,7 @@ _dl_close_worker (struct link_map_private *map, bool force)
 	  if (imap == GL(dl_initfirst))
 	    GL(dl_initfirst) = NULL;
 
-	  free (imap);
+	  _dl_free_object (imap);
 	}
     }
 
@@ -758,6 +768,8 @@ _dl_close_worker (struct link_map_private *map, bool force)
     }
 
   dl_close_state = not_pending;
+
+  _dl_protmem_end ();
 }
 
 
diff --git a/elf/dl-libc_freeres.c b/elf/dl-libc_freeres.c
index 65fc70837a..88c0e444b8 100644
--- a/elf/dl-libc_freeres.c
+++ b/elf/dl-libc_freeres.c
@@ -18,6 +18,7 @@
 
 #include <ldsodefs.h>
 #include <dl-find_object.h>
+#include <dl-protmem.h>
 
 static bool
 free_slotinfo (struct dtv_slotinfo_list **elemp)
@@ -52,6 +53,10 @@ __rtld_libc_freeres (void)
   struct link_map_private *l;
   struct r_search_path_elem *d;
 
+  /* We are about to write to link maps.  This is not paired with
+     _dl_protmem_end because the process is going away anyway.  */
+  _dl_protmem_begin ();
+
   /* Remove all search directories.  */
   d = GL(dl_all_dirs);
   while (d != GLRO(dl_init_all_dirs))
diff --git a/elf/dl-load.c b/elf/dl-load.c
index 30727afddb..560a83ea60 100644
--- a/elf/dl-load.c
+++ b/elf/dl-load.c
@@ -33,6 +33,7 @@
 #include <sys/types.h>
 #include <gnu/lib-names.h>
 #include <alloc_buffer.h>
+#include <dl-protmem.h>
 
 /* Type for the buffer we put the ELF header and hopefully the program
    header.  This buffer does not really have to be too large.  In most
@@ -943,7 +944,8 @@ _dl_map_object_from_fd (const char *name, const char *origname, int fd,
 	    free (l->l_libname);
 	  if (l != NULL && l->l_phdr_allocated)
 	    free ((void *) l->l_phdr);
-	  free (l);
+	  if (l != NULL)
+	    _dl_free_object (l);
 	  free (realname);
 	  _dl_signal_error (errval, name, NULL, errstring);
 	}
@@ -2251,6 +2253,22 @@ add_path (struct add_path_state *p, const struct r_search_path_struct *sps,
     }
 }
 
+/* Wrap cache_rpath to unprotect memory first if necessary.  */
+static bool
+cache_rpath_unprotect (struct link_map_private *l,
+		       struct r_search_path_struct *sp,
+		       int tag,
+		       const char *what,
+		       bool *unprotected)
+{
+  if (sp->dirs == NULL && !*unprotected)
+    {
+      _dl_protmem_begin ();
+      *unprotected = true;
+    }
+  return cache_rpath (l, sp, tag, what);
+}
+
 void
 _dl_rtld_di_serinfo (struct link_map_private *loader, Dl_serinfo *si,
 		     bool counting)
@@ -2268,6 +2286,7 @@ _dl_rtld_di_serinfo (struct link_map_private *loader, Dl_serinfo *si,
       .si = si,
       .allocptr = (char *) &si->dls_serpath[si->dls_cnt]
     };
+  bool unprotected = false;
 
 # define add_path(p, sps, flags) add_path(p, sps, 0) /* XXX */
 
@@ -2280,7 +2299,8 @@ _dl_rtld_di_serinfo (struct link_map_private *loader, Dl_serinfo *si,
       struct link_map_private *l = loader;
       do
 	{
-	  if (cache_rpath (l, &l->l_rpath_dirs, DT_RPATH, "RPATH"))
+	  if (cache_rpath_unprotect (l, &l->l_rpath_dirs, DT_RPATH,
+				     "RPATH", &unprotected))
 	    add_path (&p, &l->l_rpath_dirs, XXX_RPATH);
 	  l = l->l_loader;
 	}
@@ -2291,7 +2311,8 @@ _dl_rtld_di_serinfo (struct link_map_private *loader, Dl_serinfo *si,
 	{
 	  l = GL(dl_ns)[LM_ID_BASE]._ns_loaded;
 	  if (l != NULL && l->l_type != lt_loaded && l != loader)
-	    if (cache_rpath (l, &l->l_rpath_dirs, DT_RPATH, "RPATH"))
+	    if (cache_rpath_unprotect (l, &l->l_rpath_dirs, DT_RPATH,
+				       "RPATH", &unprotected))
 	      add_path (&p, &l->l_rpath_dirs, XXX_RPATH);
 	}
     }
@@ -2300,7 +2321,8 @@ _dl_rtld_di_serinfo (struct link_map_private *loader, Dl_serinfo *si,
   add_path (&p, &__rtld_env_path_list, XXX_ENV);
 
   /* Look at the RUNPATH information for this binary.  */
-  if (cache_rpath (loader, &loader->l_runpath_dirs, DT_RUNPATH, "RUNPATH"))
+  if (cache_rpath_unprotect (loader, &loader->l_runpath_dirs, DT_RUNPATH,
+			     "RUNPATH", &unprotected))
     add_path (&p, &loader->l_runpath_dirs, XXX_RUNPATH);
 
   /* XXX
@@ -2315,4 +2337,7 @@ _dl_rtld_di_serinfo (struct link_map_private *loader, Dl_serinfo *si,
     /* Count the struct size before the string area, which we didn't
        know before we completed dls_cnt.  */
     si->dls_size += (char *) &si->dls_serpath[si->dls_cnt] - (char *) si;
+
+  if (unprotected)
+    _dl_protmem_end ();
 }
diff --git a/elf/dl-object.c b/elf/dl-object.c
index 0741371b80..0ea3f6e2da 100644
--- a/elf/dl-object.c
+++ b/elf/dl-object.c
@@ -21,6 +21,7 @@
 #include <stdlib.h>
 #include <unistd.h>
 #include <ldsodefs.h>
+#include <dl-protmem.h>
 
 #include <assert.h>
 
@@ -89,15 +90,19 @@ _dl_new_object (char *realname, const char *libname, int type,
 # define audit_space 0
 #endif
 
-  new = calloc (sizeof (*new)
-		+ sizeof (struct link_map_private *)
-		+ sizeof (*newname) + libname_len, 1);
+  size_t l_size = (sizeof (*new)
+		   + sizeof (struct link_map_private *)
+		   + sizeof (*newname) + libname_len);
+
+  new = _dl_protmem_allocate (l_size);
   if (new == NULL)
     return NULL;
+  memset (new, 0, sizeof (*new));
+  new->l_size = l_size;
   new->l_rw = calloc (1, sizeof (*new->l_rw) + audit_space);
   if (new->l_rw == NULL)
     {
-      free (new);
+      _dl_protmem_free (new, l_size);
       return NULL;
     }
 
@@ -108,7 +113,7 @@ _dl_new_object (char *realname, const char *libname, int type,
   new->l_libname = newname
     = (struct libname_list *) (new->l_symbolic_searchlist.r_list + 1);
   newname->name = (char *) memcpy (newname + 1, libname, libname_len);
-  /* newname->next = NULL;	We use calloc therefore not necessary.  */
+  newname->next = NULL;
   newname->dont_free = 1;
 
   /* When we create the executable link map, or a VDSO link map, we start
@@ -143,12 +148,9 @@ _dl_new_object (char *realname, const char *libname, int type,
 
 #ifdef SHARED
   for (unsigned int cnt = 0; cnt < naudit; ++cnt)
-    /* No need to initialize bindflags due to calloc.  */
     link_map_audit_state (new, cnt)->cookie = (uintptr_t) new;
 #endif
 
-  /* new->l_global = 0;	We use calloc therefore not necessary.  */
-
   /* Use the 'l_scope_mem' array by default for the 'l_scope'
      information.  If we need more entries we will allocate a large
      array dynamically.  */
@@ -267,3 +269,9 @@ _dl_new_object (char *realname, const char *libname, int type,
 
   return new;
 }
+
+void
+_dl_free_object (struct link_map_private *l)
+{
+  _dl_protmem_free (l, l->l_size);
+}
diff --git a/elf/dl-open.c b/elf/dl-open.c
index d270672c1f..afac8498be 100644
--- a/elf/dl-open.c
+++ b/elf/dl-open.c
@@ -37,6 +37,7 @@
 #include <libc-early-init.h>
 #include <gnu/lib-names.h>
 #include <dl-find_object.h>
+#include <dl-protmem.h>
 
 #include <dl-prop.h>
 
@@ -174,6 +175,8 @@ add_to_global_update (struct link_map_private *new)
 {
   struct link_namespaces *ns = &GL (dl_ns)[new->l_ns];
 
+  _dl_protmem_begin ();
+
   /* Now add the new entries.  */
   unsigned int new_nlist = ns->_ns_main_searchlist->r_nlist;
   for (unsigned int cnt = 0; cnt < new->l_searchlist.r_nlist; ++cnt)
@@ -204,6 +207,8 @@ add_to_global_update (struct link_map_private *new)
 
   atomic_write_barrier ();
   ns->_ns_main_searchlist->r_nlist = new_nlist;
+
+  _dl_protmem_end ();
 }
 
 /* Search link maps in all namespaces for the DSO that contains the object at
@@ -560,6 +565,11 @@ dl_open_worker_begin (void *a)
 	args->nsid = call_map->l_ns;
     }
 
+  /* Prepare for link map updates.  If dl_open_worker below returns
+     normally, a matching _dl_protmem_end call is performed there.  On
+     an exception, the handler in the caller has to perform it.  */
+  _dl_protmem_begin ();
+
   /* The namespace ID is now known.  Keep track of whether libc.so was
      already loaded, to determine whether it is necessary to call the
      early initialization routine (or clear libc_map on error).  */
@@ -808,6 +818,10 @@ dl_open_worker (void *a)
       _dl_signal_exception (err, &ex, NULL);
   }
 
+  /* Make state read-only before running user code in ELF
+     constructors.  */
+  _dl_protmem_end ();
+
   if (!args->worker_continue)
     return;
 
@@ -941,6 +955,10 @@ no more namespaces available for dlmopen()"));
 	     the flag here.  */
 	}
 
+      /* Due to the exception, we did not end the protmem transaction
+	 before.  */
+      _dl_protmem_end ();
+
       /* Release the lock.  */
       __rtld_lock_unlock_recursive (GL(dl_load_lock));
 
diff --git a/elf/dl-protmem-internal.h b/elf/dl-protmem-internal.h
new file mode 100644
index 0000000000..ce50d174a6
--- /dev/null
+++ b/elf/dl-protmem-internal.h
@@ -0,0 +1,39 @@
+/* Protected memory allocator for ld.so.  Internal interfaces.
+   Copyright (C) 2023 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/>.  */
+
+/* These declarations are needed by <dl-protmem_bootstrap.h>, which
+   has to be inlined into _dl_start.  */
+
+/* Header before all protected memory allocations.  */
+struct dl_protmem_header
+{
+  struct dl_protmem_header *next;
+  unsigned int size;
+};
+
+/* Singleton allocator state.  It also serves as the bootstrap
+   allocation.  */
+struct dl_protmem_state
+{
+  struct dl_protmem_header hdr;  /* For consistency with other allocations.  */
+  struct rtld_protmem protmem; /* GLRO (dl_protmem) points to this field.  */
+
+  /* Allocator state: Linked list of allocations.  Initially points to
+     this structure.  */
+  struct dl_protmem_header *root;
+};
diff --git a/elf/dl-protmem.c b/elf/dl-protmem.c
new file mode 100644
index 0000000000..f5a66868e6
--- /dev/null
+++ b/elf/dl-protmem.c
@@ -0,0 +1,132 @@
+/* Protected memory allocator for ld.so.
+   Copyright (C) 2023 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/>.  */
+
+#include <ldsodefs.h>
+
+#include <dl-protmem.h>
+#include <dl-protmem-internal.h>
+
+#include <assert.h>
+#include <sys/mman.h>
+
+/* Nesting counter for _dl_protmem_begin/_dl_protmem_end.  This is
+   primaryly required because we may have a call sequence dlopen,
+   malloc, dlopen.  Without the counter, _dl_protmem_end in the inner
+   dlopen would make a link map that is still being initialized
+   read-only.  */
+static unsigned int _dl_protmem_begin_count;
+
+static inline struct dl_protmem_state *
+_dl_protmem_state (void)
+{
+  return ((void *) GLRO (dl_protmem)
+          - offsetof (struct dl_protmem_state, protmem));
+}
+
+void
+_dl_protmem_init (void)
+{
+  /* Go back from the start of the protected memory area to the
+     wrapping bootstrap allocation.  */
+  struct dl_protmem_state *state = _dl_protmem_state ();
+  state->hdr.size = sizeof (struct dl_protmem_state);
+  state->root = &state->hdr;
+  _dl_protmem_begin_count = 1;
+}
+
+void *
+_dl_protmem_allocate (size_t size)
+{
+  assert (_dl_protmem_begin_count > 0);
+  assert (size > 0);
+
+  struct dl_protmem_header *hdr;
+
+  /* Add the header.  */
+  unsigned int total_size;
+  if (__builtin_add_overflow (size, sizeof (*hdr), &total_size))
+    return NULL;
+
+  hdr = __mmap (NULL, total_size, PROT_READ | PROT_WRITE,
+                MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+  if (hdr == MAP_FAILED)
+    return NULL;
+  hdr->size = total_size;
+
+  /* Put the allocation on the list of allocations.  */
+  struct dl_protmem_state *state = _dl_protmem_state ();
+  hdr->next = state->root;
+  state->root = hdr;
+
+  /* Return aa pointer to the user data.  */
+  return (char *) hdr + sizeof (*hdr);
+}
+
+void
+_dl_protmem_free (void *ptr, size_t size)
+{
+  assert (_dl_protmem_begin_count > 0);
+
+  struct dl_protmem_header *hdr = ptr - sizeof (*hdr);
+  assert (hdr->size == size + sizeof (*hdr));
+
+  struct dl_protmem_state *state = _dl_protmem_state ();
+  if (hdr == state->root)
+    {
+      state->root = hdr->next;
+      (void) __munmap (hdr, hdr->size);
+      return;
+    }
+
+  for (struct dl_protmem_header *p = state->root; p != NULL; p = p ->next)
+    if (p->next == hdr)
+      {
+        p->next = hdr->next;
+        (void) __munmap (hdr, hdr->size);
+        return;
+      }
+  _dl_fatal_printf ("\
+Fatal glibc error: Protected memory allocation not found during free\n");
+}
+
+void
+_dl_protmem_begin (void)
+{
+  if (_dl_protmem_begin_count++ > 0)
+    return;
+
+  struct dl_protmem_state *state = _dl_protmem_state ();
+  for (struct dl_protmem_header *hdr = state->root;
+       hdr != NULL; hdr = hdr->next)
+    if (__mprotect (hdr, hdr->size, PROT_READ | PROT_WRITE) != 0)
+      _dl_signal_error (ENOMEM, NULL, NULL,
+                        "Cannot make protected memory writable");
+}
+
+void
+_dl_protmem_end (void)
+{
+  if (--_dl_protmem_begin_count > 0)
+    return;
+
+  struct dl_protmem_state *state = _dl_protmem_state ();
+  for (struct dl_protmem_header *hdr = state->root;
+       hdr != NULL; hdr = hdr->next)
+    /* If the mapping is left read-write, this is not fatal.  */
+    (void) __mprotect (hdr, hdr->size, PROT_READ);
+}
diff --git a/elf/dl-protmem.h b/elf/dl-protmem.h
new file mode 100644
index 0000000000..59aeaf630d
--- /dev/null
+++ b/elf/dl-protmem.h
@@ -0,0 +1,93 @@
+/* Protected memory allocator for ld.so.
+   Copyright (C) 2023 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/>.  */
+
+/* The protected memory allocation manages the memory for the GLPM
+   variables (in shared builds), and for additional memory managed by
+   _dl_protmem_allocate and _dl_protmem_free.
+
+   After a call to _dl_protmem_begin and until the matching call to
+   _dl_protmem_end, the GLPM variables and memory allocated using
+   _dl_protmem_allocate is writable.  _dl_protmem_begin and
+   _dl_protmem_end calls can be nested.  In this case, only the
+   outermost _dl_protmem_end call makes memory read-only.  */
+
+#ifndef DL_PROTMEM_H
+#define DL_PROTMEM_H
+
+#include <stddef.h>
+
+#ifdef SHARED
+/* Must be called after _dl_allocate_rtld_map and before any of the
+   functions below.  Implies the first _dl_protmem_begin call.  */
+void _dl_protmem_init (void) attribute_hidden;
+
+/* Frees memory allocated using _dl_protmem_allocate.  The passed size
+   must be the same that was passed to _dl_protmem_allocate.
+   Protected memory must be writable when this function is called.  */
+void _dl_protmem_free (void *ptr, size_t size) attribute_hidden;
+
+/* Allocate protected memory of SIZE bytes.  Returns NULL on
+   allocation failure.  Protected memory must be writable when this
+   function is called.  The allocation will be writable and contains
+   unspecified bytes (similar to malloc).  */
+void *_dl_protmem_allocate (size_t size) attribute_hidden
+  __attribute_malloc__ __attribute_alloc_size__ ((1))
+  __attr_dealloc (_dl_protmem_free, 1);
+
+/* _dl_protmem_begin makes protected memory writable, and
+   _dl_protmem_end makes it read-only again. Calls to these functions
+   must be paired.  Within this region, protected memory is writable.
+   See the initial description above.
+
+   Failure to make memory writable in _dl_protmem_end is communicated
+   via an ld.so exception, typically resulting in a dlopen failure.
+   This can happen after a call to fork if memory overcommitment is
+   disabled.  */
+void _dl_protmem_begin (void) attribute_hidden;
+void _dl_protmem_end (void) attribute_hidden;
+
+#else /*!SHARED */
+/* The protected memory allocator does not exist for static builds.
+   Use malloc directly.  */
+
+#include <stdlib.h>
+
+static inline void *
+_dl_protmem_allocate (size_t size)
+{
+  return calloc (size, 1);
+}
+
+static inline void
+_dl_protmem_free (void *ptr, size_t size)
+{
+  free (ptr);
+}
+
+static inline void
+_dl_protmem_begin (void)
+{
+}
+
+static inline void
+_dl_protmem_end (void)
+{
+}
+#endif /* !SHARED */
+
+#endif /* DL_PROTMEM_H */
diff --git a/elf/dl-protmem_bootstrap.h b/elf/dl-protmem_bootstrap.h
index 2ba0973d07..a9d763bc7b 100644
--- a/elf/dl-protmem_bootstrap.h
+++ b/elf/dl-protmem_bootstrap.h
@@ -17,6 +17,7 @@
    <https://www.gnu.org/licenses/>.  */
 
 #include <dl-early_mmap.h>
+#include <dl-protmem-internal.h>
 
 /* Return a pointer to the protected memory area, or NULL if
    allocation fails.  This function is called before self-relocation,
@@ -25,5 +26,10 @@
 static inline __attribute__ ((always_inline)) struct rtld_protmem *
 _dl_protmem_bootstrap (void)
 {
-  return _dl_early_mmap (sizeof (struct rtld_protmem));
+  /* The protected memory area is nested within the bootstrap
+     allocation.  */
+  struct dl_protmem_state *ptr = _dl_early_mmap (sizeof (*ptr));
+  if (ptr == NULL)
+    return NULL;
+  return &ptr->protmem;
 }
diff --git a/elf/rtld.c b/elf/rtld.c
index 4abede1bab..fb752e0dfd 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -54,6 +54,7 @@
 #include <dl-audit-check.h>
 #include <dl-call_tls_init_tp.h>
 #include <dl-protmem_bootstrap.h>
+#include <dl-protmem.h>
 
 #include <assert.h>
 
@@ -460,6 +461,10 @@ _dl_start_final (void *arg, struct dl_start_final_info *info)
   if (GLRO (dl_protmem) == NULL)
     _dl_fatal_printf ("Fatal glibc error: Cannot allocate link map\n");
 
+  /* Set up the protected memory allocator, transferring the rtld link
+     map allocation in GLRO (dl_rtld_map).  */
+  _dl_protmem_init ();
+
   __rtld_malloc_init_stubs ();
 
   /* Do not use an initializer for these members because it would
@@ -2385,6 +2390,11 @@ dl_main (const ElfW(Phdr) *phdr,
   /* Auditing checkpoint: we have added all objects.  */
   _dl_audit_activity_nsid (LM_ID_BASE, LA_ACT_CONSISTENT);
 
+  /* Most of the initialization work has happened by this point, and
+     it should not be necessary to make the link maps read-write after
+     this point.  */
+  _dl_protmem_end ();
+
   /* Notify the debugger all new objects are now ready to go.  We must re-get
      the address since by now the variable might be in another object.  */
   r = _dl_debug_update (LM_ID_BASE);
diff --git a/elf/tst-relro-linkmap-mod1.c b/elf/tst-relro-linkmap-mod1.c
new file mode 100644
index 0000000000..dd73d26936
--- /dev/null
+++ b/elf/tst-relro-linkmap-mod1.c
@@ -0,0 +1,42 @@
+/* Module with the checking function for read-only link maps.
+   Copyright (C) 2023 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/>.  */
+
+#include <link.h>
+#include <stdio.h>
+#include <unistd.h>
+
+/* Export for use by the main program, to avoid copy relocations on
+   _r_debug.  */
+struct r_debug_extended *const r_debug_extended_address
+  = (struct r_debug_extended *) &_r_debug;
+
+/* The real definition is in the main program.  */
+void
+check_relro_link_maps (const char *context)
+{
+  puts ("error: check_relro_link_maps not interposed");
+  _exit (1);
+}
+
+static void __attribute__ ((constructor))
+init (void)
+{
+  check_relro_link_maps ("ELF constructor (DSO)");
+}
+
+/* NB: destructor not checked.  Memory is writable when they run.  */
diff --git a/elf/tst-relro-linkmap-mod2.c b/elf/tst-relro-linkmap-mod2.c
new file mode 100644
index 0000000000..f022264ffd
--- /dev/null
+++ b/elf/tst-relro-linkmap-mod2.c
@@ -0,0 +1,2 @@
+/* Same checking as the first module, but loaded via dlopen.  */
+#include "tst-relro-linkmap-mod1.c"
diff --git a/elf/tst-relro-linkmap-mod3.c b/elf/tst-relro-linkmap-mod3.c
new file mode 100644
index 0000000000..b2b7349200
--- /dev/null
+++ b/elf/tst-relro-linkmap-mod3.c
@@ -0,0 +1,2 @@
+/* No checking possible because the check_relro_link_maps function
+   from the main program is inaccessible after dlopen.  */
diff --git a/elf/tst-relro-linkmap.c b/elf/tst-relro-linkmap.c
new file mode 100644
index 0000000000..08cfd32c52
--- /dev/null
+++ b/elf/tst-relro-linkmap.c
@@ -0,0 +1,112 @@
+/* Verify that link maps are read-only most of the time.
+   Copyright (C) 2023 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/>.  */
+
+#include <support/memprobe.h>
+#include <support/check.h>
+#include <support/xdlfcn.h>
+#include <support/xunistd.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <support/support.h>
+
+static int do_test (void);
+#include <support/test-driver.c>
+
+/* This hack results in a definition of struct rtld_global_ro and
+   related data structures.  Do this after all the other header
+   inclusions, to minimize the impact.  This only works from the main
+   program due to tests-internal.  */
+#define SHARED
+#include <ldsodefs.h>
+
+/* Defined in tst-relro-linkmap-mod1.so.  */
+extern struct r_debug_extended *const r_debug_extended_address;
+
+/* Check that link maps are read-only in all namespaces.  */
+void
+check_relro_link_maps (const char *context)
+{
+  for (struct r_debug_extended *r = r_debug_extended_address;
+       r != NULL; r = r->r_next)
+    for (struct link_map *l = r->base.r_map; l != NULL; l = l->l_next)
+      {
+        char *ctx;
+
+        ctx = xasprintf ("%s: link map for %s", context, l->l_name);
+        support_memprobe_readonly (ctx, l_private (l),
+                                   sizeof (*l_private (l)));
+        free (ctx);
+        if (false)              /* Link map names are currently writable.  */
+          {
+            ctx = xasprintf ("%s: link map name for %s", context, l->l_name);
+            support_memprobe_readonly (ctx, l->l_name, strlen (l->l_name) + 1);
+            free (ctx);
+          }
+      }
+}
+
+static void __attribute__ ((constructor))
+init (void)
+{
+  check_relro_link_maps ("ELF constructor (main)");
+}
+
+static void __attribute__ ((destructor))
+deinit (void)
+{
+  /* _dl_fini does not make link maps writable.   */
+  check_relro_link_maps ("ELF destructor (main)");
+}
+
+static int
+do_test (void)
+{
+  check_relro_link_maps ("initial do_test");
+
+  /* Avoid copy relocations.  Do this from the main program because we
+     need access to internal headers.  */
+  {
+    struct rtld_global_ro *ro = xdlsym (RTLD_DEFAULT, "_rtld_global_ro");
+    check_relro_link_maps ("after _rtld_global_ro");
+    support_memprobe_readonly ("_rtld_global_ro", ro, sizeof (*ro));
+    support_memprobe_readonly ("GLPM", ro->_dl_protmem,
+                               sizeof (*ro->_dl_protmem));
+  }
+  support_memprobe_readwrite ("_rtld_global",
+                              xdlsym (RTLD_DEFAULT, "_rtld_global"),
+                              sizeof (struct rtld_global_ro));
+  check_relro_link_maps ("after _rtld_global");
+
+  /* This is supposed to fail.  */
+  TEST_VERIFY (dlopen ("tst-dlopenfailmod1.so", RTLD_LAZY) == NULL);
+  check_relro_link_maps ("after failed dlopen");
+
+  /* This should succeed.  */
+  void *handle = xdlopen ("tst-relro-linkmap-mod2.so", RTLD_LAZY);
+  check_relro_link_maps ("after successful dlopen");
+  xdlclose (handle);
+  check_relro_link_maps ("after dlclose 1");
+
+  handle = xdlmopen (LM_ID_NEWLM, "tst-relro-linkmap-mod3.so", RTLD_LAZY);
+  check_relro_link_maps ("after dlmopen");
+  xdlclose (handle);
+  check_relro_link_maps ("after dlclose 2");
+
+  return 0;
+}
diff --git a/include/link.h b/include/link.h
index 2632337e29..1651a9b118 100644
--- a/include/link.h
+++ b/include/link.h
@@ -164,6 +164,9 @@ struct link_map_private
        than one namespace.  */
     struct link_map_private *l_real;
 
+    /* Allocated size of this link map.  */
+    size_t l_size;
+
     /* Run-time writable fields.  */
     struct link_map_rw *l_rw;
 
diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h
index e8f7c8b70b..b2bb42e8c6 100644
--- a/sysdeps/generic/ldsodefs.h
+++ b/sysdeps/generic/ldsodefs.h
@@ -524,7 +524,10 @@ extern struct rtld_global _rtld_global __rtld_global_attribute__;
 #endif
 
 #ifdef SHARED
-/* Implementation structure for the protected memory area.  */
+/* Implementation structure for the protected memory area.  In static
+   builds, the protected memory area is just regular (.data) memory,
+   as there is no RELRO support anyway.  Some fields are only needed
+   for SHARED builds and are not included for static builds.  */
 struct rtld_protmem
 {
   /* Structure describing the dynamic linker itself.  */
@@ -1043,6 +1046,9 @@ struct link_map_private *_dl_new_object (char *realname,
 					 int mode, Lmid_t nsid)
      attribute_hidden;
 
+/* Deallocates the specified link map (only the link map itself).  */
+void _dl_free_object (struct link_map_private *) attribute_hidden;
+
 /* Relocate the given object (if it hasn't already been).
    SCOPE is passed to _dl_lookup_symbol in symbol lookups.
    If RTLD_LAZY is set in RELOC-MODE, don't relocate its PLT.  */
-- 
2.43.0



  parent reply	other threads:[~2023-12-07 10:34 UTC|newest]

Thread overview: 77+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2023-12-07 10:30 [PATCH v3 00/32] RELRO linkmaps Florian Weimer
2023-12-07 10:30 ` [PATCH v3 01/32] support: Add <support/memprobe.h> for protection flags probing Florian Weimer
2024-02-22 22:39   ` Joseph Myers
2023-12-07 10:30 ` [PATCH v3 02/32] misc: Enable internal use of memory protection keys Florian Weimer
2024-02-22  1:06   ` Joseph Myers
2023-12-07 10:31 ` [PATCH v3 03/32] elf: Remove _dl_sysdep_open_object hook function Florian Weimer
2024-01-31 13:10   ` Joseph Myers
2024-03-11 17:19     ` Florian Weimer
2024-03-11 17:33       ` Joseph Myers
2024-03-11 17:46         ` Florian Weimer
2024-03-11 18:02           ` Joseph Myers
2024-03-11 18:16             ` Florian Weimer
2023-12-07 10:31 ` [PATCH v3 04/32] elf: Eliminate second loop in find_version in dl-version.c Florian Weimer
2024-02-19 22:17   ` Joseph Myers
2023-12-07 10:31 ` [PATCH v3 05/32] elf: In rtld_setup_main_map, assume ld.so has a DYNAMIC segment Florian Weimer
2024-02-19 22:18   ` Joseph Myers
2023-12-07 10:31 ` [PATCH v3 06/32] elf: Remove version assert in check_match in elf/dl-lookup.c Florian Weimer
2024-03-04 23:22   ` Joseph Myers
2023-12-07 10:31 ` [PATCH v3 07/32] elf: Disambiguate some failures in _dl_load_cache_lookup Florian Weimer
2024-02-19 23:07   ` Joseph Myers
2023-12-07 10:31 ` [PATCH v3 08/32] elf: Eliminate alloca in open_verify Florian Weimer
2024-02-19 23:26   ` Joseph Myers
2023-12-07 10:31 ` [PATCH v3 09/32] Do not export <alloc_buffer.h> functions from libc Florian Weimer
2024-02-21 17:13   ` Joseph Myers
2023-12-07 10:31 ` [PATCH v3 10/32] elf: Make <alloc_buffer.h> usable in ld.so Florian Weimer
2024-02-21 17:19   ` Joseph Myers
2023-12-07 10:31 ` [PATCH v3 11/32] elf: Merge the three implementations of _dl_dst_substitute Florian Weimer
2024-02-28 17:52   ` Joseph Myers
2023-12-07 10:31 ` [PATCH v3 12/32] elf: Move __rtld_malloc_init_stubs call into _dl_start_final Florian Weimer
2024-02-22 22:30   ` Joseph Myers
2024-02-22 23:06   ` Andreas Schwab
2023-12-07 10:31 ` [PATCH v3 13/32] elf: Merge __dl_libc_freemem into __rtld_libc_freeres Florian Weimer
2024-02-22 23:23   ` Joseph Myers
2023-12-07 10:31 ` [PATCH v3 14/32] elf: Use struct link_map_private for the internal link map Florian Weimer
2024-02-22 23:36   ` Joseph Myers
2023-12-07 10:32 ` [PATCH v3 15/32] elf: Remove run-time-writable fields from struct link_map_private Florian Weimer
2024-02-23  0:09   ` Joseph Myers
2023-12-07 10:32 ` [PATCH v3 16/32] elf: Move l_tls_offset into read-write part of link map Florian Weimer
2024-02-26 21:57   ` Joseph Myers
2023-12-07 10:32 ` [PATCH v3 17/32] elf: Allocate auditor state after read-write " Florian Weimer
2024-02-26 22:01   ` Joseph Myers
2023-12-07 10:32 ` [PATCH v3 18/32] elf: Move link map fields used by dependency sorting to writable part Florian Weimer
2024-02-27 17:51   ` Joseph Myers
2023-12-07 10:32 ` [PATCH v3 19/32] elf: Split _dl_lookup_map, _dl_map_new_object from _dl_map_object Florian Weimer
2024-02-27 17:56   ` Joseph Myers
2023-12-07 10:32 ` [PATCH v3 20/32] elf: Add l_soname accessor function for DT_SONAME values Florian Weimer
2024-02-27 22:14   ` Joseph Myers
2023-12-07 10:32 ` [PATCH v3 21/32] elf: _dl_rtld_map should not exist in static builds Florian Weimer
2024-02-28 12:38   ` Joseph Myers
2023-12-07 10:32 ` [PATCH v3 22/32] elf: Introduce GLPM accessor for the protected memory area Florian Weimer
2024-02-28 12:44   ` Joseph Myers
2023-12-07 10:32 ` [PATCH v3 23/32] elf: Bootstrap allocation for future protected memory allocator Florian Weimer
2024-02-28 15:04   ` Joseph Myers
2023-12-07 10:32 ` Florian Weimer [this message]
2024-02-28 18:46   ` [PATCH v3 24/32] elf: Implement a basic " Joseph Myers
2023-12-07 10:32 ` [PATCH v3 25/32] elf: Move most of the _dl_find_object data to the protected heap Florian Weimer
2024-02-28 19:06   ` Joseph Myers
2023-12-07 10:32 ` [PATCH v3 26/32] elf: Switch to a region-based protected memory allocator Florian Weimer
2024-03-05 23:36   ` Joseph Myers
2023-12-07 10:32 ` [PATCH v3 27/32] elf: Determine the caller link map in _dl_open Florian Weimer
2024-02-28 19:23   ` Joseph Myers
2023-12-07 10:32 ` [PATCH v3 28/32] elf: Add fast path to dlopen for fully-opened maps Florian Weimer
2024-02-28 19:26   ` Joseph Myers
2023-12-07 10:32 ` [PATCH v3 29/32] elf: Use _dl_find_object instead of _dl_find_dso_for_object in dlopen Florian Weimer
2024-02-28 19:27   ` Joseph Myers
2023-12-07 10:32 ` [PATCH v3 30/32] elf: Put critical _dl_find_object pointers into protected memory area Florian Weimer
2024-03-04 21:39   ` Joseph Myers
2023-12-07 10:32 ` [PATCH v3 31/32] elf: Add hash tables to speed up DT_NEEDED, dlopen lookups Florian Weimer
2024-03-06  0:04   ` Joseph Myers
2023-12-07 10:33 ` [PATCH v3 32/32] elf: Use memory protection keys for the protected memory allocator Florian Weimer
2024-03-06  0:11   ` Joseph Myers
2023-12-07 10:53 ` [PATCH v3 00/32] RELRO linkmaps Andreas Schwab
2023-12-07 10:56   ` Florian Weimer
2023-12-07 11:34     ` Andreas Schwab
2024-03-01 14:45     ` Adhemerval Zanella Netto
2024-03-11 17:24       ` Florian Weimer
2024-03-12 12:51         ` Adhemerval Zanella Netto

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.gnu.org/software/libc/involved.html

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=4c5e23c8adeaf0a05cb3655eb896c7c40da15772.1701944612.git.fweimer@redhat.com \
    --to=fweimer@redhat.com \
    --cc=libc-alpha@sourceware.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).