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 <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,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 <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 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 <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
 
@@ -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
+   <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..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
+   <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 *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 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
+   <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 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
+   <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 1b3dd869b5..e69cea2ecc 100644
--- a/sysdeps/unix/sysv/linux/dl-sysdep.c
+++ b/sysdeps/unix/sysv/linux/dl-sysdep.c
@@ -40,6 +40,7 @@
 #include <sys/utsname.h>
 #include <tls.h>
 #include <unistd.h>
+#include <dl-protmem.h>
 
 #include <dl-machine.h>
 #include <dl-hwcap-check.h>
@@ -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
+   <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
