From mboxrd@z Thu Jan 1 00:00:00 1970 X-Msuck: nntp://news.gmane.org/gmane.linux.lib.musl.general/10528 Path: news.gmane.org!.POSTED!not-for-mail From: "LeMay, Michael" Newsgroups: gmane.linux.lib.musl.general Subject: [RFC PATCH 5/7] support SafeStack in init and threading Date: Tue, 27 Sep 2016 15:37:43 -0700 Message-ID: <88558be1-5aa2-e037-bc83-3b8c67d1302c@intel.com> Reply-To: musl@lists.openwall.com NNTP-Posting-Host: blaine.gmane.org Mime-Version: 1.0 Content-Type: text/plain; charset=utf-8; format=flowed Content-Transfer-Encoding: 7bit X-Trace: blaine.gmane.org 1475015904 12535 195.159.176.226 (27 Sep 2016 22:38:24 GMT) X-Complaints-To: usenet@blaine.gmane.org NNTP-Posting-Date: Tue, 27 Sep 2016 22:38:24 +0000 (UTC) User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:45.0) Gecko/20100101 Thunderbird/45.3.0 To: "musl@lists.openwall.com" Original-X-From: musl-return-10541-gllmg-musl=m.gmane.org@lists.openwall.com Wed Sep 28 00:38:20 2016 Return-path: Envelope-to: gllmg-musl@m.gmane.org Original-Received: from mother.openwall.net ([195.42.179.200]) by blaine.gmane.org with smtp (Exim 4.84_2) (envelope-from ) id 1bp10g-00008W-Eo for gllmg-musl@m.gmane.org; Wed, 28 Sep 2016 00:37:58 +0200 Original-Received: (qmail 3789 invoked by uid 550); 27 Sep 2016 22:37:58 -0000 Mailing-List: contact musl-help@lists.openwall.com; run by ezmlm Precedence: bulk List-Post: List-Help: List-Unsubscribe: List-Subscribe: List-ID: Original-Received: (qmail 3754 invoked from network); 27 Sep 2016 22:37:56 -0000 X-ExtLoop1: 1 X-IronPort-AV: E=Sophos;i="5.30,407,1470726000"; d="scan'208";a="14389828" Xref: news.gmane.org gmane.linux.lib.musl.general:10528 Archived-At: This patch adds storage in the thread control block for the unsafe stack pointer for that thread when segmentation-hardened SafeStack is enabled. It modifies the libc initialization routines and thread creation routines to allocate and initialize unsafe stacks. Likewise, it modifies the thread destruction routine to unmap the unsafe stack. The initial thread control block is located in the default data segment, below the safe stacks. The thread control block for each additional thread was allocated above the stack for that thread. This patch avoids the need for adding stack segment override prefixes to all instructions that access those thread control blocks by instead allocating each additional thread control block above the unsafe stack for that thread. The developer should add something like "-I/usr/include/x86_64-linux-gnu/asm" to CPPFLAGS during configuration so that ldt.h can be included in __safestack.c. Signed-off-by: Michael LeMay --- src/env/__libc_start_main.c | 19 ++++ src/env/__safestack.c | 241 ++++++++++++++++++++++++++++++++++++++++++++ src/internal/pthread_impl.h | 4 + src/thread/pthread_create.c | 72 ++++++++++++- 4 files changed, 334 insertions(+), 2 deletions(-) create mode 100644 src/env/__safestack.c diff --git a/src/env/__libc_start_main.c b/src/env/__libc_start_main.c index 5c79be2..6c19c64 100644 --- a/src/env/__libc_start_main.c +++ b/src/env/__libc_start_main.c @@ -19,6 +19,9 @@ weak_alias(dummy1, __init_ssp); #define AUX_CNT 38 +#if SAFE_STACK +__attribute__((noinline)) +#endif void __init_libc(char **envp, char *pn) { size_t i, *auxv, aux[AUX_CNT] = { 0 }; @@ -63,11 +66,27 @@ static void libc_start_init(void) weak_alias(libc_start_init, __libc_start_init); +#if SAFE_STACK +void __preinit_unsafe_stack(void); +void __init_unsafe_stack(void); +void __safestack_dup_argv_env_aux(int argc, char ***argvp, char ***envpp); +void __safestack_restrict_segments(void); + +__attribute__((no_sanitize("safe-stack"))) +#endif int __libc_start_main(int (*main)(int,char **,char **), int argc, char **argv) { char **envp = argv+argc+1; +#if SAFE_STACK + __preinit_unsafe_stack(); +#endif __init_libc(envp, argv[0]); +#if SAFE_STACK + __init_unsafe_stack(); + __safestack_dup_argv_env_aux(argc, &argv, &envp); + __safestack_restrict_segments(); +#endif __libc_start_init(); /* Pass control to the application */ diff --git a/src/env/__safestack.c b/src/env/__safestack.c new file mode 100644 index 0000000..74ddb46 --- /dev/null +++ b/src/env/__safestack.c @@ -0,0 +1,241 @@ +#if SAFE_STACK + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include "pthread_impl.h" +#include "syscall.h" + +/* used prior to the thread-local unsafe stack pointer being initialized. */ +__attribute__((__visibility__("hidden"))) +void *__safestack_unsafe_stack_ptr_init; +/* indicates whether the thread-local unsafe stack pointer is initialized. */ +__attribute__((__visibility__("hidden"))) +bool __safestack_unsafe_stack_ptr_inited; + +/* minimum base address of all existing safe stacks */ +__attribute__((__visibility__("hidden"))) +void *__min_stack_base; + +void *__mmap(void *, size_t, int, int, int, off_t); +int __munmap(void *, size_t); +int __mprotect(void *, size_t, int); + +static int __modify_ldt(int func, void *ptr, unsigned long bytecount) { + return syscall(SYS_modify_ldt, func, ptr, bytecount); +} + +static void __update_ldt(int idx, unsigned long addr, size_t len) { + struct user_desc stack_desc; + stack_desc.entry_number = idx; + stack_desc.base_addr = (unsigned long)addr; + stack_desc.contents = 0; /* data */ + stack_desc.limit = (int)((len - 1) >> 12); + stack_desc.limit_in_pages = 1; + stack_desc.read_exec_only = 0; + stack_desc.seg_not_present = 0; + stack_desc.seg_32bit = 1; + stack_desc.useable = 1; + + if (__modify_ldt(1, &stack_desc, sizeof(stack_desc)) == -1) + a_crash(); +} + +static int __find_available_ldt_slot(void) { + static const int SEG_DESC_P_BIT_OFF = 47; + + uint64_t ldt[1]; + + /* read the current LDT */ + int ldt_len = __modify_ldt(0, ldt, sizeof(ldt)); + if (ldt_len == -1) + a_crash(); + + if (ldt_len == 0) + /* LDT is currently empty */ + return 0; + + for (int i = 0; i < (ldt_len/sizeof(ldt[0])); i++) + if (((ldt[i] >> SEG_DESC_P_BIT_OFF) & 1) == 0) + return i; + + /* crash if no available slot was found */ + a_crash(); + + /* unreachable; just for suppressing compiler warning */ + return 0; +} + +extern char **__environ; + +/* Programs and much of the libc code expect to be able to access the arguments, + * environment, and auxv in DS, but they are initially located on the stack. This + * function moves them to the heap. This uses strdup to copy data from the stack, + * so it must run before segment limits are restricted. + */ +void __safestack_dup_argv_env_aux(int argc, char ***argvp, char ***envpp) +{ + char **argv = *argvp; + char **envp = *envpp; + char **environ_end = envp; + size_t *auxv, *auxv_end; + char **new_argv = 0; + + while (*environ_end) environ_end++; + + auxv_end = (size_t *)environ_end + 1; + while (*auxv_end) auxv_end++; + auxv_end++; + + new_argv = malloc((uintptr_t)auxv_end - (uintptr_t)argvp); + if (!new_argv) + a_crash(); + + *new_argv = (char *)argc; + new_argv++; + + *argvp = new_argv; + + for (int i = 0; i < argc; i++) + new_argv[i] = strdup(argv[i]); + new_argv += argc; + *new_argv = NULL; + new_argv++; + + *envpp = __environ = new_argv; + while (envp != environ_end) { + *new_argv = strdup(*envp); + envp++; + new_argv++; + } + *new_argv = NULL; + envp++; + new_argv++; + + libc.auxv = (size_t *)new_argv; + memcpy(new_argv, envp, (uintptr_t)auxv_end - (uintptr_t)envp); +} + +#define SEG_SEL_LDT 4 +#define SEG_SEL_CPL3 3 +#define SEG_SEL_TO_IDX(sel) ((sel) >> 3) +#define SEG_IDX_TO_LDT_SEL(idx) ((idx) | SEG_SEL_LDT | SEG_SEL_CPL3) + +__attribute__((noinline)) +void __safestack_init_thread(unsigned char *stack_addr) +{ + uintptr_t stack_base = (uintptr_t)stack_addr; + + int data_seg_sel; + __asm__ __volatile__ ("mov %%ds, %0" : "=r"(data_seg_sel)); + + int data_ldt_sel; + if ((data_seg_sel & SEG_SEL_LDT) == SEG_SEL_LDT) { + data_ldt_sel = data_seg_sel; + + /* Read the current limit from the segment register rather than + * relying on __min_stack_base, since __min_stack_base is in the + * default data segment and could potentially be subject to + * memory corruption. */ + static uintptr_t limit; + __asm__ __volatile__ ("lsl %1, %0" : "=r"(limit) : "m"(data_seg_sel)); + + if (limit < stack_base) + return; + } else + data_ldt_sel = SEG_IDX_TO_LDT_SEL(__find_available_ldt_slot()); + + __update_ldt(SEG_SEL_TO_IDX(data_ldt_sel), 0, stack_base); + + /* Reload the DS and ES segment registers from the new or updated LDT + * entry. */ + __asm__ __volatile__ ( + "mov %0, %%ds\n\t" + "mov %0, %%es\n\t" + :: + "r"(data_ldt_sel) + ); +} + +/* The no_sanitize attribute on __init_unsafe_stack will only take effect if + * there are no inlined functions within it that lack the attribute. That is why + * the following noinline wrappers are defined. */ +__attribute__((noinline)) +static struct pthread *__pthread_self_noinline() +{ + return __pthread_self(); +} + +__attribute__((noinline)) +static void a_crash_noinline() +{ + a_crash(); +} + +__attribute__((no_sanitize("safe-stack"))) +void __init_unsafe_stack(void) +{ + size_t stack_size; + pthread_attr_t attr; + struct pthread *self = __pthread_self_noinline(); + + if (__safestack_unsafe_stack_ptr_inited) + return; + + if (pthread_getattr_np(self, &attr) != 0) + a_crash_noinline(); + + if (pthread_attr_getstack(&attr, &__min_stack_base, &stack_size) != 0) + a_crash_noinline(); + + /* There is not yet support for dynamically resizing the unsafe stack to + * handle overflows past its end. The size of the unsafe stack may need + * to be adjusted here and in __pthread_create to handle programs with + * larger unsafe stack requirements. */ + stack_size *= 2; + + /* This mapping is not reclaimed until the process exits. */ + uint8_t *unsafe_stack = __mmap(0, stack_size, PROT_NONE, MAP_PRIVATE|MAP_ANON, -1, 0); + if (unsafe_stack == MAP_FAILED) + a_crash_noinline(); + + unsafe_stack += DEFAULT_GUARD_SIZE; + stack_size -= DEFAULT_GUARD_SIZE; + + if (__mprotect(unsafe_stack, stack_size, PROT_READ|PROT_WRITE) + && errno != ENOSYS) + a_crash(); + + self->unsafe_stack_ptr = unsafe_stack + stack_size; + + __safestack_unsafe_stack_ptr_init = NULL; + __safestack_unsafe_stack_ptr_inited = true; +} + +void __safestack_restrict_segments(void) +{ + __safestack_init_thread(__min_stack_base); +} + +/* There are no checks for overflows past the end of this stack buffer. It must + * be allocated with adequate space to meet the requirements of all of the code + * that runs prior to __init_unsafe_stack. This buffer is not used after + * __init_unsafe_stack is invoked, so it becomes wasted space at that point. */ +__attribute__((__visibility__("hidden"))) +static uint8_t __safestack_preinit_unsafe_stack[PAGE_SIZE]; + +__attribute__((no_sanitize("safe-stack"))) +void __preinit_unsafe_stack(void) +{ + if (__safestack_unsafe_stack_ptr_inited) + return; + + __safestack_unsafe_stack_ptr_init = + __safestack_preinit_unsafe_stack + sizeof(__safestack_preinit_unsafe_stack); +} + +#endif /*SAFE_STACK*/ diff --git a/src/internal/pthread_impl.h b/src/internal/pthread_impl.h index 3890bb5..5c5aca1 100644 --- a/src/internal/pthread_impl.h +++ b/src/internal/pthread_impl.h @@ -18,6 +18,10 @@ struct pthread { uintptr_t sysinfo; uintptr_t canary, canary2; pid_t tid, pid; +#if SAFE_STACK + void *unsafe_stack_ptr; + void *unsafe_stack_base; +#endif int tsd_used, errno_val; volatile int cancel, canceldisable, cancelasync; int detached; diff --git a/src/thread/pthread_create.c b/src/thread/pthread_create.c index e7df34a..734dd13 100644 --- a/src/thread/pthread_create.c +++ b/src/thread/pthread_create.c @@ -89,7 +89,18 @@ _Noreturn void __pthread_exit(void *result) __do_orphaned_stdio_locks(); __dl_thread_cleanup(); - if (self->detached && self->map_base) { + unsigned char *self_alloc_base; +#if SAFE_STACK + self_alloc_base = self->unsafe_stack_base; +#else + self_alloc_base = self->map_base; +#endif + if (self->detached && self_alloc_base) { +#if SAFE_STACK + if (self->map_base) + __munmap(self->map_base, self->map_size); +#endif + /* Detached threads must avoid the kernel clear_child_tid * feature, since the virtual address will have been * unmapped and possibly already reused by a new mapping @@ -110,7 +121,7 @@ _Noreturn void __pthread_exit(void *result) /* The following call unmaps the thread's stack mapping * and then exits without touching the stack. */ - __unmapself(self->map_base, self->map_size); + __unmapself(self_alloc_base, self->map_size); } for (;;) __syscall(SYS_exit, 0); @@ -176,12 +187,21 @@ static void init_file_lock(FILE *f) void *__copy_tls(unsigned char *); +#if SAFE_STACK +void __safestack_init_thread(unsigned char *); + +extern void *__min_stack_base; +#endif + int __pthread_create(pthread_t *restrict res, const pthread_attr_t *restrict attrp, void *(*entry)(void *), void *restrict arg) { int ret, c11 = (attrp == __ATTRP_C11_THREAD); size_t size, guard; struct pthread *self, *new; unsigned char *map = 0, *stack = 0, *tsd = 0, *stack_limit; +#if SAFE_STACK + unsigned char *unsafe_stack; +#endif unsigned flags = CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND | CLONE_THREAD | CLONE_SYSVSEM | CLONE_SETTLS | CLONE_PARENT_SETTID | CLONE_CHILD_CLEARTID | CLONE_DETACHED; @@ -206,6 +226,48 @@ int __pthread_create(pthread_t *restrict res, const pthread_attr_t *restrict att __acquire_ptc(); if (__block_new_threads) __wait(&__block_new_threads, 0, 1, 1); +#if SAFE_STACK + if (attr._a_stackaddr) + /* When segmentation-hardened SafeStack is in use, all safe + * stacks must be allocated at high addresses so that the + * limits for DS and ES can be set below all of them. For + * simplicity, require that the default allocator below be used + * to allocate all new safe stacks. */ + return EINVAL; + + guard = ROUND(DEFAULT_GUARD_SIZE + attr._a_guardsize); + size = guard + ROUND(DEFAULT_STACK_SIZE + attr._a_stacksize + + libc.tls_size + __pthread_tsd_size); + + /* Iteratively seek the highest available block below the lowest + * existing stack to use for the new stack to help avoid a data segment + * limit that is too low and causes faults when accessing non-stack + * data above the limit. */ + + uintptr_t try_map = ((uintptr_t)__min_stack_base) - size; + + do { + map = __mmap((void *)try_map, size, PROT_NONE, MAP_FIXED|MAP_PRIVATE|MAP_ANON, -1, 0); + try_map -= PAGE_SIZE; + } while (map == MAP_FAILED); + if (__mprotect(map+guard, size-guard, PROT_READ|PROT_WRITE) + && errno != ENOSYS) { + __munmap(map, size); + goto fail; + } + + stack = map + size; + stack_limit = map + guard; + + unsafe_stack = __mmap(0, size, PROT_NONE, MAP_PRIVATE|MAP_ANON, -1, 0); + if (__mprotect(unsafe_stack+guard, size-guard, PROT_READ|PROT_WRITE) + && errno != ENOSYS) { + __munmap(unsafe_stack, size); + goto fail; + } + + tsd = unsafe_stack + size - __pthread_tsd_size; +#else if (attr._a_stackaddr) { size_t need = libc.tls_size + __pthread_tsd_size; size = attr._a_stacksize + DEFAULT_STACK_SIZE; @@ -247,6 +309,7 @@ int __pthread_create(pthread_t *restrict res, const pthread_attr_t *restrict att stack_limit = map + guard; } } +#endif new = __copy_tls(tsd - libc.tls_size); new->map_base = map; @@ -269,6 +332,11 @@ int __pthread_create(pthread_t *restrict res, const pthread_attr_t *restrict att new->robust_list.head = &new->robust_list.head; new->unblock_cancel = self->cancel; new->CANARY = self->CANARY; +#if SAFE_STACK + new->unsafe_stack_base = unsafe_stack; + new->unsafe_stack_ptr = tsd - libc.tls_size; + __safestack_init_thread(stack); +#endif a_inc(&libc.threads_minus_1); ret = __clone((c11 ? start_c11 : start), stack, flags, new, &new->tid, TP_ADJ(new), &new->tid); -- 2.7.4