@@ -73,6 +73,7 @@ dl-routines = \
dl-origin \
dl-printf \
dl-profile \
+ dl-protmem \
dl-reloc \
dl-runtime \
dl-scope \
@@ -117,7 +118,7 @@ elide-routines.os = \
# elide-routines.os
# These object files are only included in the dynamically-linked libc.
-shared-only-routines = libc-dl_find_object
+shared-only-routines = dl-protmem libc-dl_find_object
# ld.so uses those routines, plus some special stuff for being the program
# interpreter and operating independent of libc.
@@ -495,6 +496,7 @@ tests-internal += \
tst-dl_find_object-threads \
tst-dlmopen2 \
tst-ptrguard1 \
+ tst-relro-linkmap \
tst-stackguard1 \
tst-tls-surplus \
tst-tls3 \
@@ -859,6 +861,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 \
@@ -3010,3 +3015,8 @@ LDFLAGS-tst-dlclose-lazy-mod1.so = -Wl,-z,lazy,--no-as-needed
$(objpfx)tst-dlclose-lazy-mod1.so: $(objpfx)tst-dlclose-lazy-mod2.so
$(objpfx)tst-dlclose-lazy.out: \
$(objpfx)tst-dlclose-lazy-mod1.so $(objpfx)tst-dlclose-lazy-mod2.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
@@ -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];
@@ -249,7 +253,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);
@@ -343,8 +350,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. */
@@ -698,7 +708,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);
}
}
@@ -743,6 +753,8 @@ _dl_close_worker (struct link_map_private *map, bool force)
goto retry;
dl_close_state = not_pending;
+
+ _dl_protmem_end ();
}
@@ -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))
@@ -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);
}
@@ -2247,6 +2249,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)
@@ -2264,6 +2282,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 */
@@ -2276,7 +2295,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;
}
@@ -2287,7 +2307,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);
}
}
@@ -2296,7 +2317,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
@@ -2311,4 +2333,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 ();
}
@@ -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);
+}
@@ -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
@@ -516,6 +521,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). */
@@ -789,6 +799,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;
@@ -922,6 +936,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));
new file mode 100644
@@ -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;
+};
new file mode 100644
@@ -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);
+}
new file mode 100644
@@ -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 */
@@ -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;
}
@@ -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);
new file mode 100644
@@ -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. */
new file mode 100644
@@ -0,0 +1,2 @@
+/* Same checking as the first module, but loaded via dlopen. */
+#include "tst-relro-linkmap-mod1.c"
new file mode 100644
@@ -0,0 +1,2 @@
+/* No checking possible because the check_relro_link_maps function
+ from the main program is inaccessible after dlopen. */
new file mode 100644
@@ -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;
+}
@@ -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;
@@ -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. */