@@ -48,6 +48,10 @@ Major new features:
* The strlcpy and strlcat functions have been added. They are derived
from OpenBSD, and are expected to be added to a future POSIX version.
+* 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:
* In the Linux kernel for the hppa/parisc architecture some of the
@@ -499,6 +499,7 @@ tests-internal += \
tst-dlmopen2 \
tst-ptrguard1 \
tst-relro-linkmap \
+ tst-relro-linkmap-disabled \
tst-stackguard1 \
tst-tls-surplus \
tst-tls3 \
@@ -863,6 +864,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 \
@@ -3022,3 +3025,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
@@ -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
@@ -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)
@@ -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)
{
@@ -151,6 +151,12 @@ glibc {
minval: 0
default: 512
}
+ protmem {
+ type: INT_32
+ default: 2
+ minval: 0
+ maxval: 3
+ }
}
mem {
@@ -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);
new file mode 100644
@@ -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)");
+}
new file mode 100644
@@ -0,0 +1,2 @@
+/* Same checking as the first module, but loaded via dlopen. */
+#include "tst-relro-linkmap-disabled-mod1.c"
new file mode 100644
@@ -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>
@@ -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)
@@ -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
@@ -334,6 +335,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
@@ -376,6 +376,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
new file mode 100644
@@ -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
@@ -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;
new file mode 100644
@@ -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
@@ -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 ();
new file mode 100644
@@ -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