[v4,14/14] elf: Use memory protection keys for the protected memory allocator

Message ID 79f918d07d20fdf287ab747ae41392ee0a8b80b9.1738530302.git.fweimer@redhat.com (mailing list archive)
State New
Headers
Series RELRO link maps |

Checks

Context Check Description
redhat-pt-bot/TryBot-apply_patch success Patch applied to master at the time it was sent
linaro-tcwg-bot/tcwg_glibc_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_glibc_check--master-aarch64 success Test passed
linaro-tcwg-bot/tcwg_glibc_build--master-arm success Build passed
redhat-pt-bot/TryBot-32bit success Build for i686
linaro-tcwg-bot/tcwg_glibc_check--master-arm success Test passed

Commit Message

Florian Weimer Feb. 2, 2025, 9:14 p.m. UTC
  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                              | 143 +++++++++++++++++-
 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, 404 insertions(+), 6 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
  

Patch

diff --git a/NEWS b/NEWS
index e2e40e141c..9e5fcb1f79 100644
--- a/NEWS
+++ b/NEWS
@@ -9,7 +9,9 @@  Version 2.42
 
 Major new features:
 
-  [Add new features here]
+* 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:
 
diff --git a/elf/Makefile b/elf/Makefile
index d285521ce5..9ca1c5c823 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -539,6 +539,7 @@  tests-internal += \
   tst-hash-collision3 \
   tst-ptrguard1 \
   tst-relro-linkmap \
+  tst-relro-linkmap-disabled \
   tst-stackguard1 \
   tst-tls-surplus \
   tst-tls3 \
@@ -981,6 +982,8 @@  modules-names += \
   tst-recursive-tlsmod13 \
   tst-recursive-tlsmod14 \
   tst-recursive-tlsmod15 \
+  tst-relro-linkmap-disabled-mod1 \
+  tst-relro-linkmap-disabled-mod2 \
   tst-relro-linkmap-mod1 \
   tst-relro-linkmap-mod2 \
   tst-relro-linkmap-mod3 \
@@ -3406,3 +3409,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 fb2cfbeeb8..049a28b1e3 100644
--- a/elf/dl-diagnostics.c
+++ b/elf/dl-diagnostics.c
@@ -242,6 +242,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));
 
diff --git a/elf/dl-protmem.c b/elf/dl-protmem.c
index 453657b3c2..ebcbfddf75 100644
--- a/elf/dl-protmem.c
+++ b/elf/dl-protmem.c
@@ -20,11 +20,17 @@ 
 
 #include <dl-protmem.h>
 #include <dl-protmem-internal.h>
+#include <dl-protmem-pkey.h>
 
 #include <array_length.h>
 #include <assert.h>
 #include <sys/mman.h>
 
+#if IS_IN (rtld)
+# define TUNABLE_NAMESPACE rtld
+# include <dl-tunables.h>
+#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,93 @@  _dl_protmem_state (void)
           - offsetof (struct dl_protmem_state, protmem));
 }
 
+/* Memory protection key management.  */
+
+/* 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;
+}
+
+/* Actual allocator follows.  */
+
 /* Address of a chunk on the free list.  This is an abstract pointer,
    never to be dereferenced explictly.  Use the accessor functions
    below instead.
@@ -232,6 +325,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)
 {
@@ -239,6 +367,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)
@@ -246,8 +377,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;
       }
 }
@@ -258,6 +389,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)
@@ -347,9 +481,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 32182053a5..a1bab3ffdb 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 <stdlib.h>
 
+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 0b6721bc51..7310ad100d 100644
--- a/elf/dl-tunables.list
+++ b/elf/dl-tunables.list
@@ -141,6 +141,12 @@  glibc {
       maxval: 1
       default: 1
     }
+    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 66064df777..99baa0a3e6 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 <ldsodefs.h>
 
+/* 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
 
@@ -261,6 +270,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..3ad073d3e1
--- /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) 2025 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_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..ec1195fe44
--- /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) 2025 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 <dlfcn.h>
+#include <support/check.h>
+#include <support/memprobe.h>
+#include <support/xdlfcn.h>
+
+/* 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_public *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 <support/test-driver.c>
diff --git a/elf/tst-rtld-list-tunables.exp b/elf/tst-rtld-list-tunables.exp
index 9f5990f340..30976e728e 100644
--- a/elf/tst-rtld-list-tunables.exp
+++ b/elf/tst-rtld-list-tunables.exp
@@ -16,3 +16,4 @@  glibc.rtld.enable_secure: 0 (min: 0, max: 1)
 glibc.rtld.execstack: 1 (min: 0, max: 1)
 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 7f0246c789..55fe6945ca 100644
--- a/manual/tunables.texi
+++ b/manual/tunables.texi
@@ -71,6 +71,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
@@ -333,6 +334,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 e1033d4ee6..d0333d75ad 100644
--- a/nptl/pthread_create.c
+++ b/nptl/pthread_create.c
@@ -380,6 +380,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..574bf2536c
--- /dev/null
+++ b/sysdeps/generic/dl-protmem-pkey.h
@@ -0,0 +1,20 @@ 
+/* Protection key support for the protected memory allocator.
+   Copyright (C) 2025 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 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 c8992b0661..7dd23a9d29 100644
--- a/sysdeps/generic/ldsodefs.h
+++ b/sysdeps/generic/ldsodefs.h
@@ -665,6 +665,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..4b64d0caf8
--- /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) 2025 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/>.  */
+
+/* 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 b746ac2644..c0d633ffcb 100644
--- a/sysdeps/unix/sysv/linux/dl-sysdep.c
+++ b/sysdeps/unix/sysv/linux/dl-sysdep.c
@@ -41,6 +41,7 @@ 
 #include <tls.h>
 #include <unistd.h>
 #include <dl-symbol-redir-ifunc.h>
+#include <dl-protmem.h>
 
 #include <dl-machine.h>
 #include <dl-hwcap-check.h>
@@ -109,6 +110,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..f5bc54280a
--- /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) 2025 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/>.  */
+
+/* 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