diff --git a/Makeconfig b/Makeconfig
index 29819363da..76f5772272 100644
--- a/Makeconfig
+++ b/Makeconfig
@@ -356,6 +356,7 @@ endif
 # Must be supported by the linker.
 no-whole-archive = -Wl,--no-whole-archive
 whole-archive = -Wl,--whole-archive
+noexecstack = -Wl,-z,noexecstack
 
 # Installed name of the startup code.
 # The ELF convention is that the startfile is called crt1.o
diff --git a/NEWS b/NEWS
index 20e263f581..3419a0a2e8 100644
--- a/NEWS
+++ b/NEWS
@@ -36,6 +36,10 @@ Major new features:
 * On Linux, update epoll header to include epoll ioctl definitions and
   related structure added in Linux kernel 6.9.
 
+* A new tunable, glibc.rtld.noexecstack, can be used to enable executable
+  stacks from either main program, dependencies, or dynamically loaded
+  libraries.  The default is to disable executable stacks.
+
 Deprecated and removed features, and other changes affecting compatibility:
 
 * Architectures which use a 32-bit seconds-since-epoch field in struct
diff --git a/elf/Makefile b/elf/Makefile
index 57b3a19d36..17768d81f7 100644
--- a/elf/Makefile
+++ b/elf/Makefile
@@ -543,6 +543,15 @@ tests-execstack-yes = \
   tst-execstack-needed \
   tst-execstack-prog \
   # tests-execstack-yes
+tests-execstack-static-yes = \
+  tst-execstack-prog-static \
+  # tests-execstack-static-yes
+tests-execstack-special-yes = \
+  $(objpfx)tst-execstack-default.out \
+  $(objpfx)tst-execstack-needed-default.out \
+  $(objpfx)tst-execstack-prog-default.out \
+  $(objpfx)tst-execstack-prog-static-default.out \
+  # tests-execstack-special-yes
 endif
 ifeq ($(have-depaudit),yes)
 tests += \
@@ -630,6 +639,8 @@ $(objpfx)tst-rtld-does-not-exist.out: tst-rtld-does-not-exist.sh $(objpfx)ld.so
 	$(evaluate-test)
 
 tests += $(tests-execstack-$(have-z-execstack))
+tests-static += $(tests-execstack-static-$(have-z-execstack))
+tests-special += $(tests-execstack-special-$(have-z-execstack))
 ifeq ($(run-built-tests),yes)
 tests-special += \
   $(objpfx)noload-mem.out \
@@ -1845,16 +1856,53 @@ $(objpfx)unload8.out: $(objpfx)unload8mod1.so $(objpfx)unload8mod1x.so
 $(objpfx)tst-tls9-static.out: $(objpfx)tst-tlsmod5.so $(objpfx)tst-tlsmod6.so
 
 ifeq ($(have-z-execstack),yes)
-$(objpfx)tst-execstack.out: $(objpfx)tst-execstack-mod.so
+$(objpfx)tst-execstack: $(objpfx)tst-execstack-mod.so
 CPPFLAGS-tst-execstack.c += -DUSE_PTHREADS=0
 LDFLAGS-tst-execstack = -Wl,-z,noexecstack
 LDFLAGS-tst-execstack-mod.so = -Wl,-z,execstack
 
+$(objpfx)tst-execstack-default.out: $(objpfx)tst-execstack
+	$(test-program-cmd) $< > $@ 2>&1; echo "status: $$?" >> $@; \
+	grep -q 'error while loading shared libraries:.*executable stack is not allowed$$' $@ \
+	  && grep -q '^status: 127$$' $@; \
+	  $(evaluate-test)
+
+tst-execstack-ENV = GLIBC_TUNABLES=glibc.rtld.noexecstack=0 \
+		    ALLOW_EXECSTACK=1
+
 $(objpfx)tst-execstack-needed: $(objpfx)tst-execstack-mod.so
 LDFLAGS-tst-execstack-needed = -Wl,-z,noexecstack
 
+tst-execstack-needed-ENV = GLIBC_TUNABLES=glibc.rtld.noexecstack=0 \
+			   ALLOW_EXECSTACK=1
+
+$(objpfx)tst-execstack-needed-default.out: $(objpfx)tst-execstack-needed
+	$(test-program-cmd) $< > $@ 2>&1; echo "status: $$?" >> $@; \
+	grep -q 'error while loading shared libraries:.*executable stack is not allowed$$' $@ \
+	  && grep -q '^status: 127$$' $@; \
+	  $(evaluate-test)
+
 LDFLAGS-tst-execstack-prog = -Wl,-z,execstack
 CFLAGS-tst-execstack-prog.c += -Wno-trampolines
+tst-execstack-prog-ENV = GLIBC_TUNABLES=glibc.rtld.noexecstack=0
+
+LDFLAGS-tst-execstack-prog-static = -Wl,-z,execstack
+CFLAGS-tst-execstack-prog-static.c += -Wno-trampolines
+tst-execstack-prog-static-ENV = GLIBC_TUNABLES=glibc.rtld.noexecstack=0
+
+$(objpfx)tst-execstack-prog-default.out: $(objpfx)tst-execstack-prog
+	$(test-program-cmd) $< > $@ 2>&1; echo "status: $$?" >> $@; \
+	grep -q 'executable stack is not allowed$$' $@ \
+	grep -q 'error while loading shared libraries:.*executable stack is not allowed$$' $@ \
+	  && grep -q '^status: 127$$' $@; \
+	  $(evaluate-test)
+
+$(objpfx)tst-execstack-prog-static-default.out: $(objpfx)tst-execstack-prog-static
+	$(host-test-program-cmd) $< > $@ 2>&1; echo "status: $$?" >> $@; \
+	grep -q 'executable stack is not allowed$$' $@ \
+	  && grep -q '^status: 127$$' $@; \
+	  $(evaluate-test)
+
 CFLAGS-tst-execstack-mod.c += -Wno-trampolines
 endif
 
diff --git a/elf/dl-load.c b/elf/dl-load.c
index 8a89b71016..414955694f 100644
--- a/elf/dl-load.c
+++ b/elf/dl-load.c
@@ -32,6 +32,7 @@
 #include <sys/stat.h>
 #include <sys/types.h>
 #include <gnu/lib-names.h>
+#include <dl-tunables.h>
 
 /* Type for the buffer we put the ELF header and hopefully the program
    header.  This buffer does not really have to be too large.  In most
@@ -1297,6 +1298,13 @@ _dl_map_object_from_fd (const char *name, const char *origname, int fd,
 
   if (__glibc_unlikely ((stack_flags &~ GL(dl_stack_flags)) & PF_X))
     {
+      if (TUNABLE_GET (glibc, rtld, noexecstack, int32_t, NULL) == 1)
+	{
+	  errstring = N_("\
+executable stack is not allowed");
+	  goto lose;
+	}
+
       /* The stack is presently not executable, but this module
 	 requires that it be executable.  */
 #if PTHREAD_IN_LIBC
diff --git a/elf/dl-support.c b/elf/dl-support.c
index 451932dd03..084e2579b1 100644
--- a/elf/dl-support.c
+++ b/elf/dl-support.c
@@ -45,6 +45,7 @@
 #include <dl-find_object.h>
 #include <array_length.h>
 #include <dl-symbol-redir-ifunc.h>
+#include <dl-tunables.h>
 
 extern char *__progname;
 char **_dl_argv = &__progname;	/* This is checked for some error messages.  */
@@ -335,6 +336,10 @@ _dl_non_dynamic_init (void)
 	break;
       }
 
+  if ((__glibc_unlikely (GL(dl_stack_flags)) & PF_X)
+      && TUNABLE_GET (glibc, rtld, noexecstack, int32_t, NULL) == 1)
+    _dl_fatal_printf ("Fatal glibc error: executable stack is not allowed\n");
+
   call_function_static_weak (_dl_find_object_init);
 
   /* Setup relro on the binary itself.  */
diff --git a/elf/dl-tunables.list b/elf/dl-tunables.list
index 1186272c81..745c9ce2c1 100644
--- a/elf/dl-tunables.list
+++ b/elf/dl-tunables.list
@@ -142,6 +142,12 @@ glibc {
       maxval: 1
       default: 0
     }
+    noexecstack {
+      type: INT_32
+      minval: 0
+      maxval: 1
+      default: 1
+    }
   }
 
   mem {
diff --git a/elf/rtld.c b/elf/rtld.c
index e9525ea987..0762d68d68 100644
--- a/elf/rtld.c
+++ b/elf/rtld.c
@@ -1668,6 +1668,10 @@ dl_main (const ElfW(Phdr) *phdr,
 
   bool has_interp = rtld_setup_main_map (main_map);
 
+  if ((__glibc_unlikely (GL(dl_stack_flags)) & PF_X)
+      && TUNABLE_GET (glibc, rtld, noexecstack, int32_t, NULL) == 1)
+    _dl_fatal_printf ("Fatal glibc error: executable stack is not allowed\n");
+
   /* If the current libname is different from the SONAME, add the
      latter as well.  */
   if (GL(dl_rtld_map).l_info[DT_SONAME] != NULL
diff --git a/elf/tst-execstack-prog-static.c b/elf/tst-execstack-prog-static.c
new file mode 100644
index 0000000000..180657e5ef
--- /dev/null
+++ b/elf/tst-execstack-prog-static.c
@@ -0,0 +1 @@
+#include "tst-execstack-prog.c"
diff --git a/elf/tst-execstack.c b/elf/tst-execstack.c
index 560b353918..56fb8bd874 100644
--- a/elf/tst-execstack.c
+++ b/elf/tst-execstack.c
@@ -8,17 +8,10 @@
 #include <unistd.h>
 #include <error.h>
 #include <stackinfo.h>
-
-static void
-print_maps (void)
-{
-#if 0
-  char *cmd = NULL;
-  asprintf (&cmd, "cat /proc/%d/maps", getpid ());
-  system (cmd);
-  free (cmd);
-#endif
-}
+#include <stdlib.h>
+#include <support/check.h>
+#include <support/xthread.h>
+#include <support/xdlfcn.h>
 
 static void deeper (void (*f) (void));
 
@@ -47,7 +40,7 @@ waiter_thread (void *arg)
 }
 #endif
 
-static bool allow_execstack = true;
+static bool kernel_allow_execstack = true;
 
 
 static int
@@ -74,61 +67,46 @@ do_test (void)
 	    {
 	      n = getline (&line, &linelen, fp);
 	      if (n > 0 && line[0] == '0')
-		allow_execstack = false;
+		kernel_allow_execstack = false;
 	    }
 
 	  fclose (fp);
 	}
     }
 
-  printf ("executable stacks %sallowed\n", allow_execstack ? "" : "not ");
+  printf ("kernel allows executable stacks: %s\n",
+	  kernel_allow_execstack ? "yes" : "not ");
+
+  bool glibc_allow_execstack = getenv ("ALLOW_EXECSTACK") != 0;
+  printf ("expected allow executable stacks: %s\n",
+	  glibc_allow_execstack ? "yes" : "not ");
 
   static void *f;		/* Address of this is used in other threads. */
 
 #if USE_PTHREADS
   /* Create some threads while stacks are nonexecutable.  */
   #define N 5
-  pthread_t thr[N];
 
-  pthread_barrier_init (&startup_barrier, NULL, N + 1);
-  pthread_barrier_init (&go_barrier, NULL, N + 1);
+  xpthread_barrier_init (&startup_barrier, NULL, N + 1);
+  xpthread_barrier_init (&go_barrier, NULL, N + 1);
 
   for (int i = 0; i < N; ++i)
-    {
-      int rc = pthread_create (&thr[i], NULL, &waiter_thread, &f);
-      if (rc)
-	error (1, rc, "pthread_create");
-    }
+    xpthread_create (NULL, &waiter_thread, &f);
 
   /* Make sure they are all there using their stacks.  */
-  pthread_barrier_wait (&startup_barrier);
+  xpthread_barrier_wait (&startup_barrier);
   puts ("threads waiting");
 #endif
 
-  print_maps ();
-
 #if USE_PTHREADS
   void *old_stack_addr, *new_stack_addr;
   size_t stack_size;
   pthread_t me = pthread_self ();
   pthread_attr_t attr;
-  int ret = 0;
 
-  ret = pthread_getattr_np (me, &attr);
-  if (ret)
-    {
-      printf ("before execstack: pthread_getattr_np returned error: %s\n",
-	      strerror (ret));
-      return 1;
-    }
-
-  ret = pthread_attr_getstack (&attr, &old_stack_addr, &stack_size);
-  if (ret)
-    {
-      printf ("before execstack: pthread_attr_getstack returned error: %s\n",
-	      strerror (ret));
-      return 1;
-    }
+  TEST_VERIFY_EXIT (pthread_getattr_np (me, &attr) == 0);
+  TEST_VERIFY_EXIT (pthread_attr_getstack (&attr, &old_stack_addr,
+					   &stack_size) == 0);
 # if _STACK_GROWS_DOWN
     old_stack_addr += stack_size;
 # else
@@ -146,39 +124,22 @@ do_test (void)
   if (h == NULL)
     {
       printf ("cannot load: %s\n", dlerror ());
-      return allow_execstack;
+      return kernel_allow_execstack
+	     ? (glibc_allow_execstack ? 1 : 0)
+	     : 0;
     }
 
-  f = dlsym (h, "tryme");
-  if (f == NULL)
-    {
-      printf ("symbol not found: %s\n", dlerror ());
-      return 1;
-    }
+  f = xdlsym (h, "tryme");
 
   /* Test if that really made our stack executable.
      The `tryme' function should crash if not.  */
 
   (*((void (*) (void)) f)) ();
 
-  print_maps ();
-
 #if USE_PTHREADS
-  ret = pthread_getattr_np (me, &attr);
-  if (ret)
-    {
-      printf ("after execstack: pthread_getattr_np returned error: %s\n",
-	      strerror (ret));
-      return 1;
-    }
-
-  ret = pthread_attr_getstack (&attr, &new_stack_addr, &stack_size);
-  if (ret)
-    {
-      printf ("after execstack: pthread_attr_getstack returned error: %s\n",
-	      strerror (ret));
-      return 1;
-    }
+  TEST_VERIFY_EXIT (pthread_getattr_np (me, &attr) == 0);
+  TEST_VERIFY_EXIT (pthread_attr_getstack (&attr, &new_stack_addr,
+					   &stack_size) == 0);
 
 # if _STACK_GROWS_DOWN
     new_stack_addr += stack_size;
@@ -206,26 +167,19 @@ do_test (void)
   /* Test that growing the stack region gets new executable pages too.  */
   deeper ((void (*) (void)) f);
 
-  print_maps ();
-
 #if USE_PTHREADS
   /* Test that a fresh thread now gets an executable stack.  */
-  {
-    pthread_t th;
-    int rc = pthread_create (&th, NULL, &tryme_thread, f);
-    if (rc)
-      error (1, rc, "pthread_create");
-  }
+  xpthread_create (NULL, &tryme_thread, f);
 
   puts ("threads go");
   /* The existing threads' stacks should have been changed.
      Let them run to test it.  */
-  pthread_barrier_wait (&go_barrier);
+  xpthread_barrier_wait (&go_barrier);
 
-  pthread_exit ((void *) (long int) (! allow_execstack));
+  pthread_exit ((void *) (long int) (! kernel_allow_execstack));
 #endif
 
-  return ! allow_execstack;
+  return ! kernel_allow_execstack;
 }
 
 static void
diff --git a/elf/tst-ro-dynamic-mod.map b/elf/tst-ro-dynamic-mod.map
index 2fe4a2998c..b68b036d3a 100644
--- a/elf/tst-ro-dynamic-mod.map
+++ b/elf/tst-ro-dynamic-mod.map
@@ -4,7 +4,7 @@ SECTIONS
  .dynamic : { *(.dynamic) } :text :dynamic
  .rodata : { *(.data*) *(.bss*) } :text
  /DISCARD/ : {
-  *(.note.gnu.property)
+  *(.note.gnu.property) *(.note.GNU-stack)
  }
  .note : { *(.note.*) } :text :note
 }
@@ -13,4 +13,5 @@ PHDRS
  text PT_LOAD FLAGS(5) FILEHDR PHDRS;
  dynamic PT_DYNAMIC FLAGS(4);
  note PT_NOTE FLAGS(4);
+ gnu_stack PT_GNU_STACK FLAGS(6);
 }
diff --git a/elf/tst-rtld-list-tunables.exp b/elf/tst-rtld-list-tunables.exp
index db0e1c86e9..2ac06e083a 100644
--- a/elf/tst-rtld-list-tunables.exp
+++ b/elf/tst-rtld-list-tunables.exp
@@ -14,4 +14,5 @@ glibc.malloc.trim_threshold: 0x0 (min: 0x0, max: 0x[f]+)
 glibc.rtld.dynamic_sort: 2 (min: 1, max: 2)
 glibc.rtld.enable_secure: 0 (min: 0, max: 1)
 glibc.rtld.nns: 0x4 (min: 0x1, max: 0x10)
+glibc.rtld.noexecstack: 1 (min: 0, max: 1)
 glibc.rtld.optional_static_tls: 0x200 (min: 0x0, max: 0x[f]+)
diff --git a/manual/tunables.texi b/manual/tunables.texi
index 8dd02d8149..bc8ef6effa 100644
--- a/manual/tunables.texi
+++ b/manual/tunables.texi
@@ -356,6 +356,26 @@ tests for @code{AT_SECURE} programs and not meant to be a security feature.
 The default value of this tunable is @samp{0}.
 @end deftp
 
+@deftp Tunable glibc.rtld.noexecstack
+Initially, @theglibc{} will use either the default architecture flags (that might
+contain the executable bit) or the value of @code{PT_GNU_STACK} (if present).
+If any shared library dependency or dynamic object loaded with @code{dlopen}
+or @code{dlmopen} requires an executable stack (either by the default flags
+or @code{PT_GNU_STACK} from the library) @theglibc{} will try to change the
+permission of the stack to enable execution for all running threads.
+
+The @code{glibc.rtld.noexecstack} tunable allows the user to control whether
+to control executable stacks from the main program, dependencies, or from
+dynamically loaded libraries.  Setting its value to @code{0} allows executable
+stacks, where @code{1} disables it.  The default value is @code{1} (except
+for Hurd, and hppa/mips on Linux which requires executables stacks).
+
+When executable stacks are not allowed, and if the main program or dependencies
+require an executable stack, the loader will fail with an error message.  Trying
+to load a dynamic shared library with @code{dlopen} or @code{dlmopen} will fail,
+with a proper message that can be obtained with @code{dlerror}.
+@end deftp
+
 @node Elision Tunables
 @section Elision Tunables
 @cindex elision tunables
diff --git a/nptl/Makefile b/nptl/Makefile
index b3f8af2e1c..e16be8ff13 100644
--- a/nptl/Makefile
+++ b/nptl/Makefile
@@ -468,6 +468,7 @@ tests-internal += \
   # tests-internal
 ifeq ($(have-z-execstack),yes)
 tests += tst-execstack-threads
+tests-special += $(objpfx)tst-execstack-threads-default.out
 endif
 endif
 
@@ -671,10 +672,18 @@ endif
 
 tst-exec4-ARGS = $(host-test-program-cmd)
 
-$(objpfx)tst-execstack-threads.out: $(objpfx)tst-execstack-threads-mod.so
+$(objpfx)tst-execstack-threads: $(objpfx)tst-execstack-threads-mod.so
 LDFLAGS-tst-execstack-threads = -Wl,-z,noexecstack
 LDFLAGS-tst-execstack-threads-mod.so = -Wl,-z,execstack
 CFLAGS-tst-execstack-threads-mod.c += -Wno-trampolines
+tst-execstack-threads-ENV = GLIBC_TUNABLES=glibc.rtld.noexecstack=0 \
+			    ALLOW_EXECSTACK=1
+
+$(objpfx)tst-execstack-threads-default.out: $(objpfx)tst-execstack-threads
+	$(test-program-cmd) $< > $@ 2>&1; echo "status: $$?" >> $@; \
+	grep -q 'error while loading shared libraries:.*executable stack is not allowed$$' $@ \
+	  && grep -q '^status: 127$$' $@; \
+	  $(evaluate-test)
 
 tst-stackguard1-ARGS = --command "$(host-test-program-cmd) --child"
 tst-stackguard1-static-ARGS = --command "$(objpfx)tst-stackguard1-static --child"
diff --git a/sysdeps/hppa/dl-tunables.list b/sysdeps/hppa/dl-tunables.list
new file mode 100644
index 0000000000..2e53a235db
--- /dev/null
+++ b/sysdeps/hppa/dl-tunables.list
@@ -0,0 +1,11 @@
+# HPPA defaults to executable stacks.
+glibc {
+  rtld {
+    noexecstack {
+      type: INT_32
+      minval: 0
+      maxval: 1
+      default: 0
+    }
+  }
+}
diff --git a/sysdeps/mach/hurd/dl-tunables.list b/sysdeps/mach/hurd/dl-tunables.list
new file mode 100644
index 0000000000..dc6a7aa21b
--- /dev/null
+++ b/sysdeps/mach/hurd/dl-tunables.list
@@ -0,0 +1,11 @@
+# Hurd default to an executable stack.
+glibc {
+  rtld {
+    noexecstack {
+      type: INT_32
+      minval: 0
+      maxval: 1
+      default: 0
+    }
+  }
+}
diff --git a/sysdeps/unix/sysv/linux/mips/Makefile b/sysdeps/unix/sysv/linux/mips/Makefile
index d5725c69d8..2cb0f6268f 100644
--- a/sysdeps/unix/sysv/linux/mips/Makefile
+++ b/sysdeps/unix/sysv/linux/mips/Makefile
@@ -78,4 +78,17 @@ ASFLAGS-.o += -Wa,-execstack
 ASFLAGS-.os += -Wa,-execstack
 ASFLAGS-.op += -Wa,-execstack
 ASFLAGS-.oS += -Wa,-execstack
+mips_noexecstack := 0
+else
+mips_noexecstack := 1
 endif
+
+# Change the default value of glibc.rtld.noexecstack based on mips-force-execstack
+$(common-objpfx)dl-tunable-list.stmp: $(common-objpfx)dl-tunable-arch.list
+$(common-objpfx)dl-tunable-arch.list: $(common-objpfx)dl-tunable-arch.stmp; @:
+$(common-objpfx)dl-tunable-arch.stmp: \
+		$(wildcard $(sysdirs:%=%/dl-tunables-arch.list))
+	cp $^ ${@:stmp=T}
+	sed -i 's/mips_noexecstack/$(mips_noexecstack)/' ${@:stmp=T}
+	$(move-if-change) ${@:stmp=T} ${@:stmp=list}
+	touch $@
diff --git a/sysdeps/unix/sysv/linux/mips/dl-tunables-arch.list b/sysdeps/unix/sysv/linux/mips/dl-tunables-arch.list
new file mode 100644
index 0000000000..c9656c4a15
--- /dev/null
+++ b/sysdeps/unix/sysv/linux/mips/dl-tunables-arch.list
@@ -0,0 +1,12 @@
+# MIPS migth require an executable stack on kernels older than 4.8.  The
+# 'mips_noexecstack' is set by configure depending on how glibc is configured.
+glibc {
+  rtld {
+    noexecstack {
+      type: INT_32
+      minval: 0
+      maxval: 1
+      default: mips_noexecstack
+    }
+  }
+}
