@@ -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
@@ -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
@@ -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
@@ -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
@@ -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. */
@@ -142,6 +142,12 @@ glibc {
maxval: 1
default: 0
}
+ noexecstack {
+ type: INT_32
+ minval: 0
+ maxval: 1
+ default: 1
+ }
}
mem {
@@ -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
new file mode 100644
@@ -0,0 +1 @@
+#include "tst-execstack-prog.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
@@ -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);
}
@@ -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]+)
@@ -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
@@ -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"
new file mode 100644
@@ -0,0 +1,11 @@
+# HPPA defaults to executable stacks.
+glibc {
+ rtld {
+ noexecstack {
+ type: INT_32
+ minval: 0
+ maxval: 1
+ default: 0
+ }
+ }
+}
new file mode 100644
@@ -0,0 +1,11 @@
+# Hurd default to an executable stack.
+glibc {
+ rtld {
+ noexecstack {
+ type: INT_32
+ minval: 0
+ maxval: 1
+ default: 0
+ }
+ }
+}
@@ -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 $@
new file mode 100644
@@ -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
+ }
+ }
+}