mirror of
git://sourceware.org/git/glibc.git
synced 2025-03-13 13:37:38 +08:00
elf: Do not change stack permission on dlopen/dlmopen
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>
This commit is contained in:
parent
ca96ea06b3
commit
0ca8785a28
6
NEWS
6
NEWS
@ -73,6 +73,12 @@ Deprecated and removed features, and other changes affecting compatibility:
|
|||||||
|
|
||||||
* The nios2*-*-linux-gnu configurations are no longer supported.
|
* 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:
|
Changes to build and runtime requirements:
|
||||||
|
|
||||||
* On recent Linux kernels with vDSO getrandom support, getrandom does not
|
* On recent Linux kernels with vDSO getrandom support, getrandom does not
|
||||||
|
@ -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))
|
if (__glibc_unlikely ((stack_flags &~ GL(dl_stack_flags)) & PF_X))
|
||||||
{
|
{
|
||||||
/* The stack is presently not executable, but this module
|
/* The stack is presently not executable, but this module
|
||||||
requires that it be executable. */
|
requires that it be executable. Only tries to change the
|
||||||
#if PTHREAD_IN_LIBC
|
stack protection during process startup. */
|
||||||
errval = _dl_make_stacks_executable (stack_endp);
|
if ((mode & __RTLD_DLOPEN) == 0)
|
||||||
#else
|
errval = _dl_make_stack_executable (stack_endp);
|
||||||
errval = (*GL(dl_make_stack_executable_hook)) (stack_endp);
|
else
|
||||||
#endif
|
errval = EINVAL;
|
||||||
|
|
||||||
if (errval)
|
if (errval)
|
||||||
{
|
{
|
||||||
errstring = N_("\
|
errstring = N_("\
|
||||||
|
@ -178,10 +178,6 @@ size_t _dl_stack_cache_actsize;
|
|||||||
uintptr_t _dl_in_flight_stack;
|
uintptr_t _dl_in_flight_stack;
|
||||||
int _dl_stack_cache_lock;
|
int _dl_stack_cache_lock;
|
||||||
#else
|
#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;
|
void (*_dl_init_static_tls) (struct link_map *) = &_dl_nothread_init_static_tls;
|
||||||
#endif
|
#endif
|
||||||
struct dl_scope_free_list *_dl_scope_free_list;
|
struct dl_scope_free_list *_dl_scope_free_list;
|
||||||
|
@ -1336,12 +1336,6 @@ dl_main (const ElfW(Phdr) *phdr,
|
|||||||
|
|
||||||
__tls_pre_init_tp ();
|
__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. */
|
/* Process the environment variable which control the behaviour. */
|
||||||
skip_env = process_envvars (&state);
|
skip_env = process_envvars (&state);
|
||||||
|
|
||||||
|
@ -23,16 +23,33 @@
|
|||||||
#include <stackinfo.h>
|
#include <stackinfo.h>
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <stdlib.h>
|
||||||
#include <support/xdlfcn.h>
|
#include <support/xdlfcn.h>
|
||||||
#include <support/xthread.h>
|
#include <support/xthread.h>
|
||||||
#include <support/check.h>
|
#include <support/check.h>
|
||||||
#include <support/xstdio.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
|
#if USE_PTHREADS
|
||||||
# include <pthread.h>
|
# if DEFAULT_RWX_STACK
|
||||||
|
|
||||||
static void *
|
static void *
|
||||||
tryme_thread (void *f)
|
tryme_thread (void *f)
|
||||||
{
|
{
|
||||||
@ -40,16 +57,21 @@ tryme_thread (void *f)
|
|||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
# endif
|
||||||
|
|
||||||
static pthread_barrier_t startup_barrier, go_barrier;
|
static pthread_barrier_t startup_barrier, go_barrier;
|
||||||
static void *
|
static void *
|
||||||
waiter_thread (void *arg)
|
waiter_thread (void *arg)
|
||||||
{
|
{
|
||||||
void **f = arg;
|
|
||||||
xpthread_barrier_wait (&startup_barrier);
|
xpthread_barrier_wait (&startup_barrier);
|
||||||
xpthread_barrier_wait (&go_barrier);
|
xpthread_barrier_wait (&go_barrier);
|
||||||
|
|
||||||
|
# if DEFAULT_RWX_STACK
|
||||||
|
void **f = arg;
|
||||||
(*((void (*) (void)) *f)) ();
|
(*((void (*) (void)) *f)) ();
|
||||||
|
# else
|
||||||
|
abort ();
|
||||||
|
# endif
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -91,7 +113,9 @@ do_test (void)
|
|||||||
|
|
||||||
printf ("executable stacks %sallowed\n", allow_execstack ? "" : "not ");
|
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. */
|
static void *f; /* Address of this is used in other threads. */
|
||||||
|
#endif
|
||||||
|
|
||||||
#if USE_PTHREADS
|
#if USE_PTHREADS
|
||||||
/* Create some threads while stacks are nonexecutable. */
|
/* Create some threads while stacks are nonexecutable. */
|
||||||
@ -108,7 +132,7 @@ do_test (void)
|
|||||||
puts ("threads waiting");
|
puts ("threads waiting");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if USE_PTHREADS
|
#if USE_PTHREADS && DEFAULT_RWX_STACK
|
||||||
void *old_stack_addr, *new_stack_addr;
|
void *old_stack_addr, *new_stack_addr;
|
||||||
size_t stack_size;
|
size_t stack_size;
|
||||||
pthread_t me = pthread_self ();
|
pthread_t me = pthread_self ();
|
||||||
@ -130,11 +154,10 @@ do_test (void)
|
|||||||
const char *soname = "tst-execstack-mod.so";
|
const char *soname = "tst-execstack-mod.so";
|
||||||
#endif
|
#endif
|
||||||
void *h = dlopen (soname, RTLD_LAZY);
|
void *h = dlopen (soname, RTLD_LAZY);
|
||||||
if (h == NULL)
|
#if !DEFAULT_RWX_STACK
|
||||||
{
|
TEST_VERIFY_EXIT (h == NULL);
|
||||||
printf ("cannot load: %s\n", dlerror ());
|
#else
|
||||||
return allow_execstack;
|
TEST_VERIFY_EXIT (h != NULL);
|
||||||
}
|
|
||||||
|
|
||||||
f = xdlsym (h, "tryme");
|
f = xdlsym (h, "tryme");
|
||||||
|
|
||||||
@ -179,19 +202,10 @@ do_test (void)
|
|||||||
xpthread_barrier_wait (&go_barrier);
|
xpthread_barrier_wait (&go_barrier);
|
||||||
|
|
||||||
pthread_exit ((void *) (long int) (! allow_execstack));
|
pthread_exit ((void *) (long int) (! allow_execstack));
|
||||||
|
# endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
return ! allow_execstack;
|
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>
|
#include <support/test-driver.c>
|
||||||
|
@ -448,25 +448,6 @@ allocate_stack (const struct pthread_attr *attr, struct pthread **pdp,
|
|||||||
|
|
||||||
lll_unlock (GL (dl_stack_cache_lock), LLL_PRIVATE);
|
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
|
/* Note that all of the stack and the thread descriptor is
|
||||||
zeroed. This means we do not have to initialize fields
|
zeroed. This means we do not have to initialize fields
|
||||||
with initial value zero. This is specifically true for
|
with initial value zero. This is specifically true for
|
||||||
|
@ -399,13 +399,6 @@ struct rtld_global
|
|||||||
#endif
|
#endif
|
||||||
#include <dl-procruntime.c>
|
#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. */
|
/* Prevailing state of the stack, PF_X indicating it's executable. */
|
||||||
EXTERN ElfW(Word) _dl_stack_flags;
|
EXTERN ElfW(Word) _dl_stack_flags;
|
||||||
|
|
||||||
@ -702,17 +695,10 @@ extern const ElfW(Phdr) *_dl_phdr;
|
|||||||
extern size_t _dl_phnum;
|
extern size_t _dl_phnum;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if PTHREAD_IN_LIBC
|
/* This function changes the permission of the memory region pointed
|
||||||
/* This function changes the permissions of all stacks (not just those
|
by STACK_ENDP to executable and update the internal memory protection
|
||||||
of the main stack). */
|
flags for future thread stack creation. */
|
||||||
int _dl_make_stacks_executable (void **stack_endp) attribute_hidden;
|
int _dl_make_stack_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
|
|
||||||
|
|
||||||
/* Variable pointing to the end of the stack (or close to it). This value
|
/* 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
|
must be constant over the runtime of the application. Some programs
|
||||||
|
@ -300,6 +300,8 @@ ifeq ($(subdir),elf)
|
|||||||
check-execstack-xfail += ld.so libc.so libpthread.so
|
check-execstack-xfail += ld.so libc.so libpthread.so
|
||||||
# We always create a thread for signals
|
# We always create a thread for signals
|
||||||
test-xfail-tst-single_threaded-pthread-static = yes
|
test-xfail-tst-single_threaded-pthread-static = yes
|
||||||
|
|
||||||
|
CFLAGS-tst-execstack.c += -DDEFAULT_RWX_STACK=1
|
||||||
endif
|
endif
|
||||||
|
|
||||||
# For bug 30166
|
# For bug 30166
|
||||||
|
@ -47,4 +47,3 @@ _dl_make_stack_executable (void **stack_endp)
|
|||||||
return ENOSYS;
|
return ENOSYS;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
rtld_hidden_def (_dl_make_stack_executable)
|
|
||||||
|
@ -289,12 +289,6 @@ extern _Noreturn void __syscall_do_cancel (void) attribute_hidden;
|
|||||||
extern void __nptl_free_tcb (struct pthread *pd);
|
extern void __nptl_free_tcb (struct pthread *pd);
|
||||||
libc_hidden_proto (__nptl_free_tcb)
|
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. */
|
/* longjmp handling. */
|
||||||
extern void __pthread_cleanup_upto (__jmp_buf target, char *targetframe);
|
extern void __pthread_cleanup_upto (__jmp_buf target, char *targetframe);
|
||||||
libc_hidden_proto (__pthread_cleanup_upto)
|
libc_hidden_proto (__pthread_cleanup_upto)
|
||||||
|
@ -360,7 +360,4 @@ ld {
|
|||||||
__rseq_offset;
|
__rseq_offset;
|
||||||
__rseq_size;
|
__rseq_size;
|
||||||
}
|
}
|
||||||
GLIBC_PRIVATE {
|
|
||||||
__nptl_change_stack_perm;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -16,19 +16,10 @@
|
|||||||
License along with the GNU C Library; if not, see
|
License along with the GNU C Library; if not, see
|
||||||
<https://www.gnu.org/licenses/>. */
|
<https://www.gnu.org/licenses/>. */
|
||||||
|
|
||||||
#include <errno.h>
|
|
||||||
#include <ldsodefs.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
|
int
|
||||||
make_main_stack_executable (void **stack_endp)
|
_dl_make_stack_executable (void **stack_endp)
|
||||||
{
|
{
|
||||||
/* This gives us the highest/lowest page that needs to be changed. */
|
/* This gives us the highest/lowest page that needs to be changed. */
|
||||||
uintptr_t page = ((uintptr_t) *stack_endp
|
uintptr_t page = ((uintptr_t) *stack_endp
|
||||||
@ -52,57 +43,3 @@ make_main_stack_executable (void **stack_endp)
|
|||||||
|
|
||||||
return 0;
|
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)
|
|
||||||
|
@ -61,6 +61,7 @@ ifeq ($(subdir),elf)
|
|||||||
# this test is expected to fail.
|
# this test is expected to fail.
|
||||||
ifneq ($(mips-has-gnustack),yes)
|
ifneq ($(mips-has-gnustack),yes)
|
||||||
test-xfail-check-execstack = yes
|
test-xfail-check-execstack = yes
|
||||||
|
CFLAGS-tst-execstack.c += -DDEFAULT_RWX_STACK=1
|
||||||
endif
|
endif
|
||||||
endif
|
endif
|
||||||
|
|
||||||
@ -68,6 +69,12 @@ ifeq ($(subdir),stdlib)
|
|||||||
gen-as-const-headers += ucontext_i.sym
|
gen-as-const-headers += ucontext_i.sym
|
||||||
endif
|
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)
|
ifeq ($(mips-force-execstack),yes)
|
||||||
CFLAGS-.o += -Wa,-execstack
|
CFLAGS-.o += -Wa,-execstack
|
||||||
CFLAGS-.os += -Wa,-execstack
|
CFLAGS-.os += -Wa,-execstack
|
||||||
|
Loading…
Reference in New Issue
Block a user