From patchwork Thu Dec 7 10:33:07 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Florian Weimer X-Patchwork-Id: 1873149 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@legolas.ozlabs.org Authentication-Results: legolas.ozlabs.org; dkim=pass (1024-bit key; unprotected) header.d=redhat.com header.i=@redhat.com header.a=rsa-sha256 header.s=mimecast20190719 header.b=N/Y+lM23; dkim-atps=neutral Authentication-Results: legolas.ozlabs.org; spf=pass (sender SPF authorized) smtp.mailfrom=sourceware.org (client-ip=8.43.85.97; helo=server2.sourceware.org; envelope-from=libc-alpha-bounces+incoming=patchwork.ozlabs.org@sourceware.org; receiver=patchwork.ozlabs.org) Received: from server2.sourceware.org (server2.sourceware.org [8.43.85.97]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (secp384r1) server-digest SHA384) (No client certificate requested) by legolas.ozlabs.org (Postfix) with ESMTPS id 4Sm9dX5GPXz23mf for ; Thu, 7 Dec 2023 21:35:12 +1100 (AEDT) Received: from server2.sourceware.org (localhost [IPv6:::1]) by sourceware.org (Postfix) with ESMTP id 8A3F0386184F for ; Thu, 7 Dec 2023 10:35:07 +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.129.124]) by sourceware.org (Postfix) with ESMTPS id 15C40386189A for ; Thu, 7 Dec 2023 10:33:13 +0000 (GMT) DMARC-Filter: OpenDMARC Filter v1.4.2 sourceware.org 15C40386189A 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 15C40386189A Authentication-Results: server2.sourceware.org; arc=none smtp.remote-ip=170.10.129.124 ARC-Seal: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1701945197; cv=none; b=m+g787vpCiVcZu7U6Jq4oDi6SHeIHlc0sKyBA1jK2AD17sqfohz2eLTFTzFBIphhvSLAA5Cj3QVacBzaISDbPmA9Tu86rIdo2tsDiUwC4xCJ7+gr5c3KmfvFy0ax5be6HDBuTn5ZUT11tHrwD5mi/l1Vu0ftkH6qY3VQvRhzNuk= ARC-Message-Signature: i=1; a=rsa-sha256; d=sourceware.org; s=key; t=1701945197; c=relaxed/simple; bh=spgueLQnX1eHMjTkcLfOr3YxP1jYfY4YoSSJA+Vikpw=; h=DKIM-Signature:From:To:Subject:Message-ID:Date:MIME-Version; b=nEt3Mhe+cc8hWv92k2HN8N/zB/8BVQtPlvfKqpGb1RXwmD37EzwmVYg+RkU2SviKbXOOPmuKUagvLuf0kUy42g4CSIUzx5aQjuLFSW/onC6U7NDB8KzZf9cT4FJ1FO1mGLoNUZf9fvQzk0eB9L2lxr1u6yrb2gzkLfMZBW2697M= ARC-Authentication-Results: i=1; server2.sourceware.org DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=redhat.com; s=mimecast20190719; t=1701945192; 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=sjLiVWZaEHcBfNj7/EqQ1O3tggDQUCrMSsZlnj4zZVM=; b=N/Y+lM23+aNoSMh/PHahwcRByytqKNXR+e913U2BNqGtuw/j+1IP0+Tl6zcgD1o8YNms/D CGa2i5rOL0Qgs1zvZY5kf/pQIb2j6KOOLS9V/ABVoQ60Np6jZ9AnhN+a1SWCq9sr3GFGAd ktqrzySElNJ7BL+hEZtEjCKUCtsW+ZI= Received: from mimecast-mx02.redhat.com (mimecast-mx02.redhat.com [66.187.233.88]) by relay.mimecast.com with ESMTP with STARTTLS (version=TLSv1.3, cipher=TLS_AES_256_GCM_SHA384) id us-mta-561-gGP9H0V-M_eAAx7PqiWrHA-1; Thu, 07 Dec 2023 05:33:11 -0500 X-MC-Unique: gGP9H0V-M_eAAx7PqiWrHA-1 Received: from smtp.corp.redhat.com (int-mx09.intmail.prod.int.rdu2.redhat.com [10.11.54.9]) (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 BC96485CBA3 for ; Thu, 7 Dec 2023 10:33:10 +0000 (UTC) Received: from oldenburg.str.redhat.com (unknown [10.39.192.131]) by smtp.corp.redhat.com (Postfix) with ESMTPS id 99E13492BC6 for ; Thu, 7 Dec 2023 10:33:09 +0000 (UTC) From: Florian Weimer To: libc-alpha@sourceware.org Subject: [PATCH v3 32/32] elf: Use memory protection keys for the protected memory allocator In-Reply-To: Message-ID: <160e47e5faaa2dbd908bffeae458b4271527dd2e.1701944612.git.fweimer@redhat.com> References: X-From-Line: 160e47e5faaa2dbd908bffeae458b4271527dd2e Mon Sep 17 00:00:00 2001 Date: Thu, 07 Dec 2023 11:33:07 +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.9 X-Mimecast-Spam-Score: 0 X-Mimecast-Originator: redhat.com X-Spam-Status: No, score=-10.6 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_H4, RCVD_IN_MSPIKE_WL, SPF_HELO_NONE, SPF_NONE, TXREP, T_SCC_BODY_TEXT_LINE 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+incoming=patchwork.ozlabs.org@sourceware.org If protection keys are not supported by the system, fall back to switching permission flags using mprotect. A complication arises on x86 because the kernel supports protection keys, but they are incompatible with dynamic linker requirements (see bug 22396). Therefore, protection key support is disabled by default on x86, but glibc.rtld.protmem=3 can still force enabling it. --- NEWS | 4 + elf/Makefile | 10 + elf/dl-diagnostics.c | 2 + elf/dl-protmem.c | 171 +++++++++++++++++- elf/dl-protmem.h | 9 + elf/dl-tunables.list | 6 + elf/tst-dl-protmem.c | 10 + elf/tst-relro-linkmap-disabled-mod1.c | 46 +++++ elf/tst-relro-linkmap-disabled-mod2.c | 2 + elf/tst-relro-linkmap-disabled.c | 64 +++++++ elf/tst-rtld-list-tunables.exp | 1 + manual/tunables.texi | 29 +++ nptl/pthread_create.c | 8 + sysdeps/generic/dl-protmem-pkey.h | 20 ++ sysdeps/generic/ldsodefs.h | 5 + sysdeps/unix/sysv/linux/dl-protmem-pkey.h | 23 +++ sysdeps/unix/sysv/linux/dl-sysdep.c | 2 + sysdeps/unix/sysv/linux/x86/dl-protmem-pkey.h | 26 +++ 18 files changed, 430 insertions(+), 8 deletions(-) create mode 100644 elf/tst-relro-linkmap-disabled-mod1.c create mode 100644 elf/tst-relro-linkmap-disabled-mod2.c create mode 100644 elf/tst-relro-linkmap-disabled.c create mode 100644 sysdeps/generic/dl-protmem-pkey.h create mode 100644 sysdeps/unix/sysv/linux/dl-protmem-pkey.h create mode 100644 sysdeps/unix/sysv/linux/x86/dl-protmem-pkey.h diff --git a/NEWS b/NEWS index 8c1c149f91..a5ebe7cd54 100644 --- a/NEWS +++ b/NEWS @@ -149,6 +149,10 @@ Major new features: explicitly enabled, then fortify source is forcibly disabled so to keep original behavior unchanged. +* The dynamic linker keeps link maps and other data structures read-only + most of the time (RELRO link maps). This behavior can be controlled + by the new glibc.rtld.protmem tunable. + Deprecated and removed features, and other changes affecting compatibility: * libcrypt is no longer built by default; one may use the "--enable-crypt" diff --git a/elf/Makefile b/elf/Makefile index d13a959fdb..6b5fbb951a 100644 --- a/elf/Makefile +++ b/elf/Makefile @@ -510,6 +510,7 @@ tests-internal += \ tst-dlmopen2 \ tst-ptrguard1 \ tst-relro-linkmap \ + tst-relro-linkmap-disabled \ tst-stackguard1 \ tst-tls-surplus \ tst-tls3 \ @@ -877,6 +878,8 @@ modules-names += \ tst-null-argv-lib \ tst-p_alignmod-base \ tst-p_alignmod3 \ + tst-relro-linkmap-disabled-mod1 \ + tst-relro-linkmap-disabled-mod2 \ tst-relro-linkmap-mod1 \ tst-relro-linkmap-mod2 \ tst-relro-linkmap-mod3 \ @@ -3044,3 +3047,10 @@ 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 + +tst-relro-linkmap-disabled-ENV = GLIBC_TUNABLES=glibc.rtld.protmem=0 +$(objpfx)tst-relro-linkmap-disabled: \ + $(objpfx)tst-relro-linkmap-disabled-mod1.so +$(objpfx)tst-relro-linkmap-disabled.out: $(objpfx)tst-dlopenfailmod1.so \ + $(objpfx)tst-relro-linkmap-disabled-mod2.so \ + $(objpfx)tst-relro-linkmap-mod3.so diff --git a/elf/dl-diagnostics.c b/elf/dl-diagnostics.c index d742cf0a99..eb2eb96258 100644 --- a/elf/dl-diagnostics.c +++ b/elf/dl-diagnostics.c @@ -241,6 +241,8 @@ _dl_print_diagnostics (char **environ) ("dl_hwcaps_subdirs_active", _dl_hwcaps_subdirs_active ()); _dl_diagnostics_print_labeled_value ("dl_pagesize", GLRO (dl_pagesize)); _dl_diagnostics_print_labeled_string ("dl_platform", GLRO (dl_platform)); + _dl_diagnostics_print_labeled_value ("dl_protmem_key", + (unsigned int) GLRO (dl_protmem_key)); _dl_diagnostics_print_labeled_string ("dl_profile_output", GLRO (dl_profile_output)); _dl_diagnostics_print_labeled_value diff --git a/elf/dl-protmem.c b/elf/dl-protmem.c index cd416e33a5..61ffb2a0de 100644 --- a/elf/dl-protmem.c +++ b/elf/dl-protmem.c @@ -20,11 +20,17 @@ #include #include +#include #include #include #include +#if IS_IN (rtld) +# define TUNABLE_NAMESPACE rtld +# include +#endif + /* 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 @@ -39,6 +45,89 @@ _dl_protmem_state (void) - offsetof (struct dl_protmem_state, protmem)); } +/* Allocate the protection key and if successful, apply it to the + original region. Return true if protected memory is enabled. */ +static bool +_dl_protmem_key_init (void *initial_region, size_t initial_size) +{ + GLRO (dl_protmem_key) = -1; + +#if IS_IN (rtld) /* Disabled for tst-dl-protmem. */ + int pkey_config = TUNABLE_GET (protmem, size_t, NULL); + if (pkey_config == 0) + /* Disable the protected memory allocator completely. */ + return false; + if (pkey_config == 1) + /* Force the use of mprotect. */ + return true; + +# if DL_PROTMEM_PKEY_SUPPORT + /* For tunables values 2 or 3, potentially use memory protection + keys. Do not enable protection keys for pkey_config == 2 by + default for !DL_PROTMEM_PKEY_SUPPORT. Used on x86, see + sysdeps/unix/sysv/linux/x86/dl-protmem-pkey.h. */ + if (DL_PROTMEM_PKEY_ENABLE || pkey_config >= 3) + GLRO (dl_protmem_key) = pkey_alloc (0, 0); +# endif /* !DL_PROTMEM_PKEY_SUPPORT */ +#endif /* IS_IN (rtld) */ + return true; +} + +/* Try to use the protection key to enable writing. Return true if + protection keys are in use. */ +static bool +_dl_protmem_key_allow (void) +{ +#if DL_PROTMEM_PKEY_SUPPORT + /* Enable write access at the beginning. */ + if (GLRO (dl_protmem_key) >= 0) + { + pkey_set (GLRO (dl_protmem_key), 0); + return true; + } +#endif + return false; +} + +/* Try to use the protection key to disable writing. Return true if + protection keys are in use. */ +static bool +_dl_protmem_key_deny (void) +{ +#if DL_PROTMEM_PKEY_SUPPORT + /* Enable write access at the beginning. */ + if (GLRO (dl_protmem_key) >= 0) + { + pkey_set (GLRO (dl_protmem_key), PKEY_DISABLE_WRITE); + return true; + } +#endif + return false; +} + +/* Creates an anonymous memory mapping as backing store. Applies the + protection key if necessary. Returns NULL on failure. */ +static void * +_dl_protmem_mmap (size_t size) +{ + void *result = __mmap (NULL, size, PROT_READ | PROT_WRITE, + MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); + if (result == MAP_FAILED) + return NULL; +#if DL_PROTMEM_PKEY_SUPPORT + if (GLRO (dl_protmem_key) >= 0) + { + if (__pkey_mprotect (result, size, PROT_READ | PROT_WRITE, + GLRO (dl_protmem_key)) != 0) + { + __munmap (result, size); + return NULL; + } + } +#endif + return result; +} + /* Debugging allocator. The real allocator is below. */ #if DL_PROTMEM_DEBUG void @@ -52,6 +141,27 @@ _dl_protmem_init (void) _dl_protmem_begin_count = 1; } +void +_dl_protmem_init_2 (void) +{ + struct dl_protmem_state *state = _dl_protmem_state (); + if (!_dl_protmem_key_init (state, sizeof (struct dl_protmem_state))) + /* Make _dl_protmem_end a no-op. */ + ++_dl_protmem_begin_count; + +#if DL_PROTMEM_PKEY_SUPPORT + if (GLRO (dl_protmem_key) >= 0) + { + for (struct dl_protmem_header *hdr = state->root; + hdr != NULL; hdr = hdr->next) + if (__pkey_mprotect (hdr, hdr->size, PROT_READ | PROT_WRITE, + GLRO (dl_protmem_key)) != 0) + _dl_fatal_printf ("\ +Fatal glibc eror: cannot apply protoection key to protected memory\n"); + } +#endif +} + void * _dl_protmem_allocate (size_t size) { @@ -65,9 +175,8 @@ _dl_protmem_allocate (size_t 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) + hdr = _dl_protmem_mmap (total_size); + if (hdr == NULL) return NULL; hdr->size = total_size; @@ -113,6 +222,9 @@ _dl_protmem_begin (void) if (_dl_protmem_begin_count++ > 0) return; + if (_dl_protmem_key_allow ()) + return; + struct dl_protmem_state *state = _dl_protmem_state (); for (struct dl_protmem_header *hdr = state->root; hdr != NULL; hdr = hdr->next) @@ -127,6 +239,9 @@ _dl_protmem_end (void) if (--_dl_protmem_begin_count > 0) return; + if (_dl_protmem_key_deny ()) + return; + struct dl_protmem_state *state = _dl_protmem_state (); for (struct dl_protmem_header *hdr = state->root; hdr != NULL; hdr = hdr->next) @@ -329,6 +444,41 @@ _dl_protmem_init (void) _dl_protmem_begin_count = 1; } +void +_dl_protmem_init_2 (void) +{ + struct dl_protmem_state *state = _dl_protmem_state (); + if (!_dl_protmem_key_init (state, DL_PROTMEM_INITIAL_REGION_SIZE)) + /* Make _dl_protmem_end a no-op. */ + ++_dl_protmem_begin_count; + + /* Apply the protection key to the existing memory regions. */ +#if DL_PROTMEM_PKEY_SUPPORT + if (GLRO (dl_protmem_key) >= 0) + { + size_t region_size = DL_PROTMEM_INITIAL_REGION_SIZE; + for (unsigned int i = 0; i < array_length (state->regions); ++i) + if (state->regions[i] != NULL) + { + if (__pkey_mprotect (state->regions[i], region_size, + PROT_READ | PROT_WRITE, GLRO (dl_protmem_key)) + != 0) + { + if (i == 0) + /* If the first pkey_mprotect failed, we can allow + reuse of the key. Otherwise, other memory still + use the key. */ + __pkey_free (GLRO (dl_protmem_key)); + /* Always stop using protection keys on pkey_mprotect + failure. */ + GLRO (dl_protmem_key) = -1; + break; + } + } + } +#endif +} + void _dl_protmem_begin (void) { @@ -336,6 +486,9 @@ _dl_protmem_begin (void) /* Already unprotected. */ return; + if (_dl_protmem_key_allow ()) + return; + struct dl_protmem_state *state = _dl_protmem_state (); size_t region_size = DL_PROTMEM_INITIAL_REGION_SIZE; for (unsigned int i = 0; i < array_length (state->regions); ++i) @@ -343,8 +496,8 @@ _dl_protmem_begin (void) { if (__mprotect (state->regions[i], region_size, PROT_READ | PROT_WRITE) != 0) - _dl_signal_error (ENOMEM, NULL, NULL, - "Cannot make protected memory writable"); + _dl_signal_error (ENOMEM, NULL, NULL, + "Cannot make protected memory writable"); region_size *= 2; } } @@ -355,6 +508,9 @@ _dl_protmem_end (void) if (--_dl_protmem_begin_count > 0) return; + if (_dl_protmem_key_deny ()) + return; + struct dl_protmem_state *state = _dl_protmem_state (); size_t region_size = DL_PROTMEM_INITIAL_REGION_SIZE; for (unsigned int i = 0; i < array_length (state->regions); ++i) @@ -444,9 +600,8 @@ _dl_protmem_allocate (size_t requested_size) { if (state->regions[i] == NULL && region_size >= requested_size) { - void *ptr = __mmap (NULL, region_size, PROT_READ | PROT_WRITE, - MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); - if (ptr == MAP_FAILED) + void *ptr = _dl_protmem_mmap (region_size); + if (ptr == NULL) return NULL; state->regions[i] = ptr; if (region_size == requested_size) diff --git a/elf/dl-protmem.h b/elf/dl-protmem.h index 59aeaf630d..3ce941cd9c 100644 --- a/elf/dl-protmem.h +++ b/elf/dl-protmem.h @@ -36,6 +36,10 @@ functions below. Implies the first _dl_protmem_begin call. */ void _dl_protmem_init (void) attribute_hidden; +/* Second phase of initialization. This enables configuration by + tunables. */ +void _dl_protmem_init_2 (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. */ @@ -67,6 +71,11 @@ void _dl_protmem_end (void) attribute_hidden; #include +static inline void +_dl_protmem_init (void) +{ +} + static inline void * _dl_protmem_allocate (size_t size) { diff --git a/elf/dl-tunables.list b/elf/dl-tunables.list index 1b23fc9473..3b9f40875a 100644 --- a/elf/dl-tunables.list +++ b/elf/dl-tunables.list @@ -136,6 +136,12 @@ glibc { minval: 0 default: 512 } + protmem { + type: INT_32 + default: 2 + minval: 0 + maxval: 3 + } } mem { diff --git a/elf/tst-dl-protmem.c b/elf/tst-dl-protmem.c index 6061845ca7..82449ffb83 100644 --- a/elf/tst-dl-protmem.c +++ b/elf/tst-dl-protmem.c @@ -163,8 +163,17 @@ record_free (void *p, size_t size) #define SHARED #include +/* We need to make available these internal functions under their + public names. */ +#define __pkey_alloc pkey_alloc +#define __pkey_free pkey_free +#define __pkey_get pkey_get +#define __pkey_mprotect pkey_mprotect +#define __pkey_set pkey_set + /* Create our own version of GLRO (dl_protmem). */ static struct rtld_protmem *dl_protmem; +static int dl_protmem_key; #undef GLRO #define GLRO(x) x @@ -265,6 +274,7 @@ do_test (void) { dl_protmem = _dl_protmem_bootstrap (); _dl_protmem_init (); + _dl_protmem_init_2 (); /* Perform a random allocations in a loop. */ srand (1); diff --git a/elf/tst-relro-linkmap-disabled-mod1.c b/elf/tst-relro-linkmap-disabled-mod1.c new file mode 100644 index 0000000000..7edc206132 --- /dev/null +++ b/elf/tst-relro-linkmap-disabled-mod1.c @@ -0,0 +1,46 @@ +/* Module with the checking function for read-write 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_rw_link_maps (const char *context) +{ + puts ("error: check_relro_link_maps not interposed"); + _exit (1); +} + +static void __attribute__ ((constructor)) +init (void) +{ + check_rw_link_maps ("ELF fini (DSO)"); +} + +static void __attribute__ ((constructor)) +fini (void) +{ + check_rw_link_maps ("ELF destructor (DSO)"); +} diff --git a/elf/tst-relro-linkmap-disabled-mod2.c b/elf/tst-relro-linkmap-disabled-mod2.c new file mode 100644 index 0000000000..33d2e78542 --- /dev/null +++ b/elf/tst-relro-linkmap-disabled-mod2.c @@ -0,0 +1,2 @@ +/* Same checking as the first module, but loaded via dlopen. */ +#include "tst-relro-linkmap-disabled-mod1.c" diff --git a/elf/tst-relro-linkmap-disabled.c b/elf/tst-relro-linkmap-disabled.c new file mode 100644 index 0000000000..1093097aff --- /dev/null +++ b/elf/tst-relro-linkmap-disabled.c @@ -0,0 +1,64 @@ +/* Verify that link maps are writable if configured so by tunable. + 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 + +/* Defined in tst-relro-linkmap-disabled-mod.so. */ +extern struct r_debug_extended *const r_debug_extended_address; + +/* Check that link maps are writable in all namespaces. */ +void +check_rw_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) + support_memprobe_readwrite (context, l, sizeof (*l)); +} + +static int +do_test (void) +{ + check_rw_link_maps ("initial"); + + /* This is supposed to fail. */ + TEST_VERIFY (dlopen ("tst-dlopenfailmod1.so", RTLD_LAZY) == NULL); + check_rw_link_maps ("after failed dlopen"); + + void *handle = xdlopen ("tst-relro-linkmap-disabled-mod2.so", RTLD_LAZY); + check_rw_link_maps ("after dlopen"); + xdlclose (handle); + check_rw_link_maps ("after dlclose"); + + /* NB: no checking inside the namespace. */ + handle = xdlmopen (LM_ID_NEWLM, "tst-relro-linkmap-mod3.so", RTLD_LAZY); + check_rw_link_maps ("after dlmopen"); + xdlclose (handle); + check_rw_link_maps ("after dlclose 2"); + + handle = xdlopen ("tst-relro-linkmap-disabled-mod2.so", RTLD_LAZY); + check_rw_link_maps ("after dlopen 2"); + /* Run the destructor during process exit. */ + + return 0; +} + +#include diff --git a/elf/tst-rtld-list-tunables.exp b/elf/tst-rtld-list-tunables.exp index 2233ea9c7c..e5dee30916 100644 --- a/elf/tst-rtld-list-tunables.exp +++ b/elf/tst-rtld-list-tunables.exp @@ -14,3 +14,4 @@ glibc.malloc.trim_threshold: 0x0 (min: 0x0, max: 0x[f]+) glibc.rtld.dynamic_sort: 2 (min: 1, max: 2) glibc.rtld.nns: 0x4 (min: 0x1, max: 0x10) glibc.rtld.optional_static_tls: 0x200 (min: 0x0, max: 0x[f]+) +glibc.rtld.protmem: 2 (min: 0, max: 3) diff --git a/manual/tunables.texi b/manual/tunables.texi index b31f16da84..8d0f84aac2 100644 --- a/manual/tunables.texi +++ b/manual/tunables.texi @@ -70,6 +70,7 @@ glibc.pthread.mutex_spin_count: 100 (min: 0, max: 32767) glibc.rtld.optional_static_tls: 0x200 (min: 0x0, max: 0xffffffffffffffff) glibc.malloc.tcache_max: 0x0 (min: 0x0, max: 0xffffffffffffffff) glibc.malloc.check: 0 (min: 0, max: 3) +glibc.rtld.protmem: 2 (min: 0, max: 3) @end example @menu @@ -332,6 +333,34 @@ changed once allocated at process startup. The default allocation of optional static TLS is 512 bytes and is allocated in every thread. @end deftp +@deftp Tunable glibc.rtld.protmem +The dynamic linker supports various operating modes for its protected +memory allocator. The following settings are available. + +@table @code +@item 0 +The protected memory allocator is disabled. All memory remains writable +during the life-time of the process. + +@item 1 +The protected memory allocator is enabled and unconditionally uses +@code{mprotect} to switch protections on or off. + +@item 2 +The protected memory allocator is enabled and uses memory protection +keys if supported by the system, and the memory protection key +implementation provides full compatibility. This is the default. + +@item 3 +The protected memory allocator is enabled. If the system supports +memory protection keys, they are used, even if there are +incompatibilities. Such incompatibilities exist on x86-64 because +signal handlers disable access (including read access) to protected +memory, which means that lazy binding will not work from signal handlers +in this mode. +@end table +@end deftp + @deftp Tunable glibc.rtld.dynamic_sort Sets the algorithm to use for DSO sorting, valid values are @samp{1} and @samp{2}. For value of @samp{1}, an older O(n^3) algorithm is used, which is diff --git a/nptl/pthread_create.c b/nptl/pthread_create.c index 63cb684f04..21cad9d7d0 100644 --- a/nptl/pthread_create.c +++ b/nptl/pthread_create.c @@ -379,6 +379,14 @@ start_thread (void *arg) __libc_fatal ("Fatal glibc error: rseq registration failed\n"); } +#ifdef SHARED + /* If the dynamic linker uses memory protection keys, new threads + may have to disable access because clone may have inherited + access if called from an write-enabled code region. */ + if (GLRO (dl_protmem_key) >= 0) + __pkey_set (GLRO (dl_protmem_key), PKEY_DISABLE_WRITE); +#endif + #ifndef __ASSUME_SET_ROBUST_LIST if (__nptl_set_robust_list_avail) #endif diff --git a/sysdeps/generic/dl-protmem-pkey.h b/sysdeps/generic/dl-protmem-pkey.h new file mode 100644 index 0000000000..3e42b491f8 --- /dev/null +++ b/sysdeps/generic/dl-protmem-pkey.h @@ -0,0 +1,20 @@ +/* Protection key support for the protected memory allocator. + 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 generic implementation does not support memory protection keys. */ +#define DL_PROTMEM_PKEY_SUPPORT 0 diff --git a/sysdeps/generic/ldsodefs.h b/sysdeps/generic/ldsodefs.h index b7fe3b48e6..f153dfa70b 100644 --- a/sysdeps/generic/ldsodefs.h +++ b/sysdeps/generic/ldsodefs.h @@ -678,6 +678,11 @@ struct rtld_global_ro EXTERN enum dso_sort_algorithm _dl_dso_sort_algo; #ifdef SHARED + /* Memory protection key for the memory allocator regions. Used + during thread initialization, to revoke access if necessary. Set + to -1 in _dl_protmem_init if protection keys are not available. */ + EXTERN int _dl_protmem_key; + /* Pointer to the protected memory area. */ EXTERN struct rtld_protmem *_dl_protmem; diff --git a/sysdeps/unix/sysv/linux/dl-protmem-pkey.h b/sysdeps/unix/sysv/linux/dl-protmem-pkey.h new file mode 100644 index 0000000000..93dc3c5d4a --- /dev/null +++ b/sysdeps/unix/sysv/linux/dl-protmem-pkey.h @@ -0,0 +1,23 @@ +/* Protection key support for the protected memory allocator. Linux version. + 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 + . */ + +/* Linux supports the pkey_* interfaces. */ +#define DL_PROTMEM_PKEY_SUPPORT 1 + +/* Use a protection key if pkey_alloc succeeds. */ +#define DL_PROTMEM_PKEY_ENABLE 1 diff --git a/sysdeps/unix/sysv/linux/dl-sysdep.c b/sysdeps/unix/sysv/linux/dl-sysdep.c index 1b3dd869b5..e69cea2ecc 100644 --- a/sysdeps/unix/sysv/linux/dl-sysdep.c +++ b/sysdeps/unix/sysv/linux/dl-sysdep.c @@ -40,6 +40,7 @@ #include #include #include +#include #include #include @@ -108,6 +109,7 @@ _dl_sysdep_start (void **start_argptr, dl_hwcap_check (); __tunables_init (_environ); + _dl_protmem_init_2 (); /* Initialize DSO sorting algorithm after tunables. */ _dl_sort_maps_init (); diff --git a/sysdeps/unix/sysv/linux/x86/dl-protmem-pkey.h b/sysdeps/unix/sysv/linux/x86/dl-protmem-pkey.h new file mode 100644 index 0000000000..887374a3d9 --- /dev/null +++ b/sysdeps/unix/sysv/linux/x86/dl-protmem-pkey.h @@ -0,0 +1,26 @@ +/* Protection key support for the protected memory allocator. x86 version. + 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 + . */ + +/* Linux supports the pkey_* interfaces. */ +#define DL_PROTMEM_PKEY_SUPPORT 1 + +/* Linux support is incompatible with signal handlers because the + kernel forces PKEY_DISABLE_ACCESS in signal handlers, which breaks + lazy binding and other dynamic linker features. See bug 22396 + comment 7. */ +#define DL_PROTMEM_PKEY_ENABLE 0