Follow @Openwall on Twitter for new release announcements and other news
[<prev] [next>] [day] [month] [year] [list]
Date: Tue, 27 Sep 2016 15:37:43 -0700
From: "LeMay, Michael" <michael.lemay@...el.com>
To: "musl@...ts.openwall.com" <musl@...ts.openwall.com>
Subject: [RFC PATCH 5/7] support SafeStack in init and threading

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 <michael.lemay@...el.com>
---
  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 <ldt.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/resource.h>
+#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


Powered by blists - more mailing lists

Confused about mailing lists and their use? Read about mailing lists on Wikipedia and check out these guidelines on proper formatting of your messages.