From patchwork Thu Dec 7 10:32:28 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Florian Weimer X-Patchwork-Id: 81652 Return-Path: X-Original-To: patchwork@sourceware.org Delivered-To: patchwork@sourceware.org Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id BB831385EC55 for ; Thu, 7 Dec 2023 10:34:32 +0000 (GMT) X-Original-To: libc-alpha@sourceware.org Delivered-To: libc-alpha@sourceware.org Received: from us-smtp-delivery-124.mimecast.com (us-smtp-delivery-124.mimecast.com [170.10.133.124]) by sourceware.org (Postfix) with ESMTPS id 584333861838 for ; Thu, 7 Dec 2023 10:32:32 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 584333861838 Authentication-Results: sourceware.org; dmarc=pass (p=none dis=none) header.from=redhat.com Authentication-Results: sourceware.org; spf=pass smtp.mailfrom=redhat.com ARC-Filter: OpenARC Filter v1.0.0 sourceware.org 584333861838 Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.133.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1701945156; cv=none; b=lBFWdTI+I9YamYPhWlYXIjV6f5U5Cp8SWFUltMjllrwXptJjf4fb1DwmQCQ82gAWMVOm4UodFOZnetaFr79udpGYMRhiNnOvJFEcQDY1bchRV2AFHBdsd2n/0HG5a5kK6elkqa84mauBnlGCvbq2o4DRjP3NbHFlqOSXOAdNvIU= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1701945156; c=relaxed/simple; bh=yN8XB8SZLQ1gGBZIkqMMrJQNIBItgwN7gGr10mNoxlk=; h=DKIM-Signature:From:To:Subject:Message-ID:Date:MIME-Version; b=J9uX/t6vGfKlOEiMn3e32UPtK4aPiprq0JkcNQdlqC+xfNSXKbSUnATEgMOslh0dIKjJudoGbHd0vusib4XICOuKtDdHjLuAqCxzvOUxNdpHzcYF2DnRHhXjr7oXtQhPD6XZ0rF8gp60QWMdwjVY8yOwdA5ViIy94NLCCjsy9Ec= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1701945152; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: in-reply-to:in-reply-to:references:references; bh=kNQpb78A2d1HS2EkizCc0Q4kBOLymxp4ZExdNrbpSSg=; b=dvdovc1QUPqOXQm/PS5lq56V6roXxqWCpfnJoMI+QynYuIkMyFDGgSv5/8m3i8hd8YwLmH DthB3Tn2mF/5g1+ntJ3o/0odO1i5EvyX1HX3Ty514OQk/Iqoj4MjNvqsj30UC5Be4144ia TjgxyHdFLIuLEqPhgzSfUEalxLCkw1A= Received: from mimecast-mx02.redhat.com (mx-ext.redhat.com [66.187.233.73]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-244-09-_LWO-NhW6vVRStcty2g-1; Thu, 07 Dec 2023 05:32:30 -0500 X-MC-Unique: 09-_LWO-NhW6vVRStcty2g-1 Received: from smtp.corp.redhat.com (int-mx03.intmail.prod.int.rdu2.redhat.com [10.11.54.3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (2048 bits) server-digest SHA256) (No client certificate requested) by mimecast-mx02.redhat.com (Postfix) with ESMTPS id 7325738130BC for ; Thu, 7 Dec 2023 10:32:30 +0000 (UTC) Received: from oldenburg.str.redhat.com (unknown [10.39.192.131]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 7B7E61121312 for ; Thu, 7 Dec 2023 10:32:29 +0000 (UTC) From: Florian Weimer To: libc-alpha@sourceware.org Subject: [PATCH v3 24/32] elf: Implement a basic protected memory allocator In-Reply-To: Message-ID: <4c5e23c8adeaf0a05cb3655eb896c7c40da15772.1701944612.git.fweimer@redhat.com> References: X-From-Line: 4c5e23c8adeaf0a05cb3655eb896c7c40da15772 Mon Sep 17 00:00:00 2001 Date: Thu, 07 Dec 2023 11:32:28 +0100 User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/28.3 (gnu/linux) MIME-Version: 1.0 X-Scanned-By: MIMEDefang 3.4.1 on 10.11.54.3 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-10.1 required=5.0 tests=BAYES_00, DKIMWL_WL_HIGH, DKIM_SIGNED, DKIM_VALID, DKIM_VALID_AU, DKIM_VALID_EF, GIT_PATCH_0, KAM_SHORT, RCVD_IN_DNSWL_NONE, RCVD_IN_MSPIKE_H3, RCVD_IN_MSPIKE_WL, SPF_HELO_NONE, SPF_NONE, TXREP, T_SCC_BODY_TEXT_LINE, URIBL_BLACK autolearn=ham autolearn_force=no version=3.4.6 X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on server2.sourceware.org X-BeenThere: libc-alpha@sourceware.org X-Mailman-Version: 2.1.30 Precedence: list List-Id: Libc-alpha mailing list List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: libc-alpha-bounces+patchwork=sourceware.org@sourceware.org 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 #include #include +#include #include @@ -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 #include +#include 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 #include #include +#include /* 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 #include #include +#include #include @@ -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 #include #include +#include #include @@ -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 + . */ + +/* These declarations are needed by , 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 + . */ + +#include + +#include +#include + +#include +#include + +/* 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 + . */ + +/* 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 + +#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 + +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 @@ . */ #include +#include /* 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 #include #include +#include #include @@ -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 + . */ + +#include +#include +#include + +/* 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 + . */ + +#include +#include +#include +#include +#include +#include +#include +#include + +static int do_test (void); +#include + +/* 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 + +/* 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. */