]> git.ipfire.org Git - thirdparty/glibc.git/commitdiff
elf: Do not change stack permission on dlopen/dlmopen
authorAdhemerval Zanella <adhemerval.zanella@linaro.org>
Thu, 28 Nov 2024 17:36:43 +0000 (14:36 -0300)
committerAdhemerval Zanella <adhemerval.zanella@linaro.org>
Tue, 31 Dec 2024 12:04:20 +0000 (09:04 -0300)
If some shared library loaded with dlopen/dlmopen requires an executable
stack, either implicitly because of a missing GNU_STACK ELF header
(where the ABI default flags implies in the executable bit) or explicitly
because of the executable bit from GNU_STACK; the loader will try to set
the both the main thread and all thread stacks (from the pthread cache)
as executable.

Besides the issue where any __nptl_change_stack_perm failure does not
undo the previous executable transition (meaning that if the library
fails to load, there can be thread stacks with executable stacks), this
behavior was used on a CVE [1] as a vector for RCE.

This patch changes that if a shared library requires an executable
stack, and the current stack is not executable, dlopen fails.  The
change is done only for dynamically loaded modules, if the program
or any dependency requires an executable stack, the loader will still
change the main thread before program execution and any thread created
with default stack configuration.

[1] https://www.qualys.com/2023/07/19/cve-2023-38408/rce-openssh-forwarded-ssh-agent.txt

Checked on x86_64-linux-gnu and i686-linux-gnu.

Reviewed-by: Florian Weimer <fweimer@redhat.com>
13 files changed:
NEWS
elf/dl-load.c
elf/dl-support.c
elf/rtld.c
elf/tst-execstack.c
nptl/allocatestack.c
sysdeps/generic/ldsodefs.h
sysdeps/mach/hurd/Makefile
sysdeps/mach/hurd/dl-execstack.c
sysdeps/nptl/pthreadP.h
sysdeps/unix/sysv/linux/Versions
sysdeps/unix/sysv/linux/dl-execstack.c
sysdeps/unix/sysv/linux/mips/Makefile

diff --git a/NEWS b/NEWS
index 4cb307fd0286cbbb9a8eb5fedb62acb75a1984af..a93c8437852b84c7757ae836d4d066fb65c558d2 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -73,6 +73,12 @@ Deprecated and removed features, and other changes affecting compatibility:
 
 * The nios2*-*-linux-gnu configurations are no longer supported.
 
+* dlopen and dlmopen no longer make the stack executable if a shared
+  library requires it, either implicitly because of a missing GNU_STACK ELF
+  header (and default ABI permission having the executable bit set) or
+  explicitly because of the executable bit in GNU_STACK, and the stack is
+  not already executable.  Instead, loading such objects will fail.
+
 Changes to build and runtime requirements:
 
 * On recent Linux kernels with vDSO getrandom support, getrandom does not
index 284857ddf6a07471a1f1f0ddbc0c4a16f2a23e5b..a238ff4286124c2a53080327c7531e375511be5c 100644 (file)
@@ -1315,12 +1315,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))
     {
       /* The stack is presently not executable, but this module
-        requires that it be executable.  */
-#if PTHREAD_IN_LIBC
-      errval = _dl_make_stacks_executable (stack_endp);
-#else
-      errval = (*GL(dl_make_stack_executable_hook)) (stack_endp);
-#endif
+        requires that it be executable.  Only tries to change the
+        stack protection during process startup.  */
+      if ((mode & __RTLD_DLOPEN) == 0)
+       errval = _dl_make_stack_executable (stack_endp);
+      else
+       errval = EINVAL;
+
       if (errval)
        {
          errstring = N_("\
index ee590edf93824d9b5711b2af114527c64f1b7ac5..fe1f8c8f6a896d866a00097ec2b4084b929b0cf0 100644 (file)
@@ -178,10 +178,6 @@ size_t _dl_stack_cache_actsize;
 uintptr_t _dl_in_flight_stack;
 int _dl_stack_cache_lock;
 #else
-/* If loading a shared object requires that we make the stack executable
-   when it was not, we do it by calling this function.
-   It returns an errno code or zero on success.  */
-int (*_dl_make_stack_executable_hook) (void **) = _dl_make_stack_executable;
 void (*_dl_init_static_tls) (struct link_map *) = &_dl_nothread_init_static_tls;
 #endif
 struct dl_scope_free_list *_dl_scope_free_list;
index 0637c53017c8c1dd2b87d439420a78f20ba6f3de..5eb130be307fe7c4147898179bd4e9f51dfeccca 100644 (file)
@@ -1336,12 +1336,6 @@ dl_main (const ElfW(Phdr) *phdr,
 
   __tls_pre_init_tp ();
 
-#if !PTHREAD_IN_LIBC
-  /* The explicit initialization here is cheaper than processing the reloc
-     in the _rtld_local definition's initializer.  */
-  GL(dl_make_stack_executable_hook) = &_dl_make_stack_executable;
-#endif
-
   /* Process the environment variable which control the behaviour.  */
   skip_env = process_envvars (&state);
 
index fae2930f709e796683dfd07720af53c694a5e741..66979547adefa22c5f75032c7bfc40d2755ae68d 100644 (file)
 #include <stackinfo.h>
 #include <stdbool.h>
 #include <string.h>
+#include <stdlib.h>
 #include <support/xdlfcn.h>
 #include <support/xthread.h>
 #include <support/check.h>
 #include <support/xstdio.h>
 
-static void deeper (void (*f) (void));
+/* The DEFAULT_RWX_STACK controls whether the toolchain enables an executable
+   stack for the testcase (which does not contain features that might require
+   an executable stack, such as nested function).
+   Some ABIs do require an executable stack, even if the toolchain supports
+   non-executable stack.  In this cases the DEFAULT_RWX_STACK can be
+   overridden.  */
+#ifndef DEFAULT_RWX_STACK
+# define DEFAULT_RWX_STACK 0
+#else
+static void
+deeper (void (*f) (void))
+{
+  char stack[1100 * 1024];
+  explicit_bzero (stack, sizeof stack);
+  (*f) ();
+  memfrob (stack, sizeof stack);
+}
+#endif
 
 #if USE_PTHREADS
-# include <pthread.h>
-
+# if DEFAULT_RWX_STACK
 static void *
 tryme_thread (void *f)
 {
@@ -40,16 +57,21 @@ tryme_thread (void *f)
 
   return 0;
 }
+# endif
 
 static pthread_barrier_t startup_barrier, go_barrier;
 static void *
 waiter_thread (void *arg)
 {
-  void **f = arg;
   xpthread_barrier_wait (&startup_barrier);
   xpthread_barrier_wait (&go_barrier);
 
+# if DEFAULT_RWX_STACK
+  void **f = arg;
   (*((void (*) (void)) *f)) ();
+# else
+  abort ();
+# endif
 
   return 0;
 }
@@ -91,7 +113,9 @@ do_test (void)
 
   printf ("executable stacks %sallowed\n", allow_execstack ? "" : "not ");
 
+#if USE_PTHREADS || DEFAULT_RWX_STACK
   static void *f;              /* Address of this is used in other threads. */
+#endif
 
 #if USE_PTHREADS
   /* Create some threads while stacks are nonexecutable.  */
@@ -108,7 +132,7 @@ do_test (void)
   puts ("threads waiting");
 #endif
 
-#if USE_PTHREADS
+#if USE_PTHREADS && DEFAULT_RWX_STACK
   void *old_stack_addr, *new_stack_addr;
   size_t stack_size;
   pthread_t me = pthread_self ();
@@ -130,11 +154,10 @@ do_test (void)
   const char *soname = "tst-execstack-mod.so";
 #endif
   void *h = dlopen (soname, RTLD_LAZY);
-  if (h == NULL)
-    {
-      printf ("cannot load: %s\n", dlerror ());
-      return allow_execstack;
-    }
+#if !DEFAULT_RWX_STACK
+  TEST_VERIFY_EXIT (h == NULL);
+#else
+  TEST_VERIFY_EXIT (h != NULL);
 
   f = xdlsym (h, "tryme");
 
@@ -150,9 +173,9 @@ do_test (void)
 
 # if _STACK_GROWS_DOWN
     new_stack_addr += stack_size;
-# else
+#  else
     new_stack_addr -= stack_size;
-# endif
+#  endif
 
   /* It is possible that the dlopen'd module may have been mmapped just below
      the stack.  The stack size is taken as MIN(stack rlimit size, end of last
@@ -164,12 +187,12 @@ do_test (void)
      should remain the same, which is computed as stackaddr + stacksize.  */
   TEST_VERIFY_EXIT (old_stack_addr == new_stack_addr);
   printf ("Stack address remains the same: %p\n", old_stack_addr);
-#endif
+# endif
 
   /* Test that growing the stack region gets new executable pages too.  */
   deeper ((void (*) (void)) f);
 
-#if USE_PTHREADS
+# if USE_PTHREADS
   /* Test that a fresh thread now gets an executable stack.  */
   xpthread_create (NULL, &tryme_thread, f);
 
@@ -179,19 +202,10 @@ do_test (void)
   xpthread_barrier_wait (&go_barrier);
 
   pthread_exit ((void *) (long int) (! allow_execstack));
+# endif
 #endif
 
   return ! allow_execstack;
 }
 
-static void
-deeper (void (*f) (void))
-{
-  char stack[1100 * 1024];
-  explicit_bzero (stack, sizeof stack);
-  (*f) ();
-  memfrob (stack, sizeof stack);
-}
-
-
 #include <support/test-driver.c>
index d9adb5856cefa53376261ba519e9fcae8d9c8785..9662b43afe9cf5184615937897d0aedac7ef309b 100644 (file)
@@ -448,25 +448,6 @@ allocate_stack (const struct pthread_attr *attr, struct pthread **pdp,
 
          lll_unlock (GL (dl_stack_cache_lock), LLL_PRIVATE);
 
-
-         /* There might have been a race.  Another thread might have
-            caused the stacks to get exec permission while this new
-            stack was prepared.  Detect if this was possible and
-            change the permission if necessary.  */
-         if (__builtin_expect ((GL(dl_stack_flags) & PF_X) != 0
-                               && (prot & PROT_EXEC) == 0, 0))
-           {
-             int err = __nptl_change_stack_perm (pd);
-             if (err != 0)
-               {
-                 /* Free the stack memory we just allocated.  */
-                 (void) __munmap (mem, size);
-
-                 return err;
-               }
-           }
-
-
          /* Note that all of the stack and the thread descriptor is
             zeroed.  This means we do not have to initialize fields
             with initial value zero.  This is specifically true for
index cec56e2214c38b1d2ba5634dec8ba29b938d8610..172bcd2cf749e55408b4f3c8be0dd3b0fdc8828a 100644 (file)
@@ -399,13 +399,6 @@ struct rtld_global
 #endif
 #include <dl-procruntime.c>
 
-#if !PTHREAD_IN_LIBC
-  /* If loading a shared object requires that we make the stack executable
-     when it was not, we do it by calling this function.
-     It returns an errno code or zero on success.  */
-  EXTERN int (*_dl_make_stack_executable_hook) (void **);
-#endif
-
   /* Prevailing state of the stack, PF_X indicating it's executable.  */
   EXTERN ElfW(Word) _dl_stack_flags;
 
@@ -702,17 +695,10 @@ extern const ElfW(Phdr) *_dl_phdr;
 extern size_t _dl_phnum;
 #endif
 
-#if PTHREAD_IN_LIBC
-/* This function changes the permissions of all stacks (not just those
-   of the main stack).  */
-int _dl_make_stacks_executable (void **stack_endp) attribute_hidden;
-#else
-/* This is the initial value of GL(dl_make_stack_executable_hook).
-   A threads library can change it.  The ld.so implementation changes
-   the permissions of the main stack only.  */
-extern int _dl_make_stack_executable (void **stack_endp);
-rtld_hidden_proto (_dl_make_stack_executable)
-#endif
+/* This function changes the permission of the memory region pointed
+   by STACK_ENDP to executable and update the internal memory protection
+   flags for future thread stack creation.  */
+int _dl_make_stack_executable (void **stack_endp) attribute_hidden;
 
 /* Variable pointing to the end of the stack (or close to it).  This value
    must be constant over the runtime of the application.  Some programs
index 698729a8a6a289d33da730716fcde881ac5e754e..576c42eb6811b9bed45e1e252f45e81b0368bba0 100644 (file)
@@ -300,6 +300,8 @@ ifeq ($(subdir),elf)
 check-execstack-xfail += ld.so libc.so libpthread.so
 # We always create a thread for signals
 test-xfail-tst-single_threaded-pthread-static = yes
+
+CFLAGS-tst-execstack.c += -DDEFAULT_RWX_STACK=1
 endif
 
 # For bug 30166
index 31371bc6e344b5ab1563c02f47080f29c22b5be8..0222430131d8826f8bbcb6b250a7b98bbae97a81 100644 (file)
@@ -47,4 +47,3 @@ _dl_make_stack_executable (void **stack_endp)
   return ENOSYS;
 #endif
 }
-rtld_hidden_def (_dl_make_stack_executable)
index c2db1650520dda3c103ae38eb84353bde5c1e283..a8e09bf754d369acb22584ec6cb5b8e47ce2d41c 100644 (file)
@@ -289,12 +289,6 @@ extern _Noreturn void __syscall_do_cancel (void) attribute_hidden;
 extern void __nptl_free_tcb (struct pthread *pd);
 libc_hidden_proto (__nptl_free_tcb)
 
-/* Change the permissions of a thread stack.  Called from
-   _dl_make_stacks_executable and pthread_create.  */
-int
-__nptl_change_stack_perm (struct pthread *pd);
-rtld_hidden_proto (__nptl_change_stack_perm)
-
 /* longjmp handling.  */
 extern void __pthread_cleanup_upto (__jmp_buf target, char *targetframe);
 libc_hidden_proto (__pthread_cleanup_upto)
index 213ff5f1fe757718eb6c73ef89ef031a9bac5f73..55d565545ab3b601d30c330bc63be324fd5d681e 100644 (file)
@@ -360,7 +360,4 @@ ld {
     __rseq_offset;
     __rseq_size;
   }
-  GLIBC_PRIVATE {
-    __nptl_change_stack_perm;
-  }
 }
index b986898598588024d051644e6209d7741736b628..68db6737f009b7dfb2cd7edebd6cf6fa687744d2 100644 (file)
    License along with the GNU C Library; if not, see
    <https://www.gnu.org/licenses/>.  */
 
-#include <errno.h>
 #include <ldsodefs.h>
-#include <libintl.h>
-#include <list.h>
-#include <pthreadP.h>
-#include <stackinfo.h>
-#include <stdbool.h>
-#include <sys/mman.h>
-#include <sysdep.h>
-#include <unistd.h>
 
-static int
-make_main_stack_executable (void **stack_endp)
+int
+_dl_make_stack_executable (void **stack_endp)
 {
   /* This gives us the highest/lowest page that needs to be changed.  */
   uintptr_t page = ((uintptr_t) *stack_endp
@@ -52,57 +43,3 @@ make_main_stack_executable (void **stack_endp)
 
   return 0;
 }
-
-int
-_dl_make_stacks_executable (void **stack_endp)
-{
-  /* First the main thread's stack.  */
-  int err = make_main_stack_executable (stack_endp);
-  if (err != 0)
-    return err;
-
-  lll_lock (GL (dl_stack_cache_lock), LLL_PRIVATE);
-
-  list_t *runp;
-  list_for_each (runp, &GL (dl_stack_used))
-    {
-      err = __nptl_change_stack_perm (list_entry (runp, struct pthread, list));
-      if (err != 0)
-       break;
-    }
-
-  /* Also change the permission for the currently unused stacks.  This
-     might be wasted time but better spend it here than adding a check
-     in the fast path.  */
-  if (err == 0)
-    list_for_each (runp, &GL (dl_stack_cache))
-      {
-       err = __nptl_change_stack_perm (list_entry (runp, struct pthread,
-                                                   list));
-       if (err != 0)
-         break;
-      }
-
-  lll_unlock (GL (dl_stack_cache_lock), LLL_PRIVATE);
-
-  return err;
-}
-
-int
-__nptl_change_stack_perm (struct pthread *pd)
-{
-#if _STACK_GROWS_DOWN
-  void *stack = pd->stackblock + pd->guardsize;
-  size_t len = pd->stackblock_size - pd->guardsize;
-#elif _STACK_GROWS_UP
-  void *stack = pd->stackblock;
-  size_t len = (uintptr_t) pd - pd->guardsize - (uintptr_t) pd->stackblock;
-#else
-# error "Define either _STACK_GROWS_DOWN or _STACK_GROWS_UP"
-#endif
-  if (__mprotect (stack, len, PROT_READ | PROT_WRITE | PROT_EXEC) != 0)
-    return errno;
-
-  return 0;
-}
-rtld_hidden_def (__nptl_change_stack_perm)
index d5725c69d8a6a517fbbc761d1e223d316f9bc01f..05ec9150b276a7d581ce91ed95250b825d1fdc9d 100644 (file)
@@ -61,6 +61,7 @@ ifeq ($(subdir),elf)
 # this test is expected to fail.
 ifneq ($(mips-has-gnustack),yes)
 test-xfail-check-execstack = yes
+CFLAGS-tst-execstack.c += -DDEFAULT_RWX_STACK=1
 endif
 endif
 
@@ -68,6 +69,12 @@ ifeq ($(subdir),stdlib)
 gen-as-const-headers += ucontext_i.sym
 endif
 
+ifeq ($(subdir),nptl)
+ifeq ($(mips-force-execstack),yes)
+CFLAGS-tst-execstack-threads.c += -DDEFAULT_RWX_STACK=1
+endif
+endif
+
 ifeq ($(mips-force-execstack),yes)
 CFLAGS-.o += -Wa,-execstack
 CFLAGS-.os += -Wa,-execstack