Forráskód Böngészése

[PAL/Linux-SGX] Reuse TLS/TCS pages for newly created threads after previous threads exit

Li Lei 5 éve
szülő
commit
30e0b8bfba

+ 1 - 0
LibOS/shim/test/regression/Makefile

@@ -22,6 +22,7 @@ CFLAGS-bootstrap_pie = -fPIC -pie
 CFLAGS-shared_object = -fPIC -pie
 CFLAGS-syscall += -I$(PALDIR)/../include -I$(PALDIR)/host/$(PAL_HOST)
 CFLAGS-openmp = -fopenmp
+CFLAGS-multi_pthread = -pthread
 
 $(c_executables): %: %.c
 	$(call cmd,csingle)

+ 32 - 0
LibOS/shim/test/regression/multi_pthread.c

@@ -0,0 +1,32 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <pthread.h>
+#include <unistd.h>
+#include <stdatomic.h>
+
+#define THREAD_NUM 32
+#define CONC_THREAD_NUM 4
+
+atomic_int counter = 0;
+
+void* inc(void* arg) {
+    counter++;
+    return NULL;
+}
+
+int main(int argc, char** argv) {
+    for (int i = 0; i < THREAD_NUM; i++){
+      pthread_t thread[CONC_THREAD_NUM];
+
+      /* create several threads running in parallel */
+      for (int j = 0; j < CONC_THREAD_NUM; j++)
+          pthread_create(&thread[j], NULL, inc, NULL);
+
+      /* join threads and continue with the next batch */
+      for (int j = 0; j < CONC_THREAD_NUM; j++)
+          pthread_join(thread[j], NULL);
+    }
+
+    printf("%d Threads Created\n", counter);
+    return 0;
+}

+ 15 - 0
LibOS/shim/test/regression/multi_pthread.manifest.template

@@ -0,0 +1,15 @@
+loader.preload = file:../../src/libsysdb.so
+loader.env.LD_LIBRARY_PATH = /lib
+loader.debug_type = none
+loader.syscall_symbol = syscalldb
+
+fs.mount.lib.type = chroot
+fs.mount.lib.path = /lib
+fs.mount.lib.uri = file:../../../../Runtime
+
+sgx.trusted_files.ld = file:../../../../Runtime/ld-linux-x86-64.so.2
+sgx.trusted_files.libc = file:../../../../Runtime/libc.so.6
+sgx.trusted_files.libpthread = file:../../../../Runtime/libpthread.so.0
+
+# app runs with 4 parallel threads + Graphene has couple internal threads
+sgx.thread_num = 8

+ 6 - 0
LibOS/shim/test/regression/test_libos.py

@@ -125,6 +125,12 @@ class TC_00_Bootstrap(RegressionTestCase):
         except subprocess.CalledProcessError as e:
             self.assertNotEqual(e.returncode, 42, 'expected returncode != 42')
 
+    def test_600_multi_pthread(self):
+        stdout, stderr = self.run_binary(['multi_pthread'])
+
+        # Multiple thread creation
+        self.assertIn('128 Threads Created', stdout)
+
 @unittest.skipUnless(HAS_SGX,
     'This test is only meaningful on SGX PAL because only SGX catches raw '
     'syscalls and redirects to Graphene\'s LibOS. If we will add seccomp to '

+ 0 - 7
Pal/regression/test_pal.py

@@ -465,13 +465,6 @@ class TC_20_SingleProcess(RegressionTestCase):
         # Thread Cleanup: Exit by return.
         self.assertIn('Thread 2 ok.', stderr)
 
-    @expectedFailureIf(HAS_SGX)
-    def test_511_thread2_nosgx(self):
-        stdout, stderr = self.run_binary(['Thread2'])
-
-        # The 2 following tests are currently broken on SGX because TCS slots
-        # are not reused yet (needed because of thread limit), see issue #517.
-
         # Thread Cleanup: Exit by DkThreadExit.
         self.assertIn('Thread 3 ok.', stderr)
         self.assertNotIn('Exiting thread 3 failed.', stderr)

+ 2 - 1
Pal/src/host/Linux-SGX/db_main.c

@@ -392,7 +392,8 @@ void pal_linux_main(char * uptr_args, uint64_t args_size,
     SET_HANDLE_TYPE(first_thread, thread);
     first_thread->thread.tcs =
         enclave_base + GET_ENCLAVE_TLS(tcs_offset);
-    SET_ENCLAVE_TLS(thread, (__pal_control.first_thread = first_thread));
+    __pal_control.first_thread = first_thread;
+    SET_ENCLAVE_TLS(thread, &first_thread->thread);
 
     /* call main function */
     pal_main(pal_sec.instance_id, manifest, exec,

+ 9 - 6
Pal/src/host/Linux-SGX/db_threading.c

@@ -140,6 +140,15 @@ void _DkThreadYieldExecution (void)
 /* _DkThreadExit for internal use: Thread exiting */
 noreturn void _DkThreadExit (void)
 {
+    struct pal_handle_thread* exiting_thread = GET_ENCLAVE_TLS(thread);
+
+    /* main thread is not part of the thread_list */
+    if(exiting_thread != &pal_control.first_thread->thread) {
+        _DkInternalLock(&thread_list_lock);
+        LISTP_DEL(exiting_thread, &thread_list, list);
+        _DkInternalUnlock(&thread_list_lock);
+    }
+
     ocall_exit(0, /*is_exitgroup=*/false);
 }
 
@@ -149,12 +158,6 @@ int _DkThreadResume (PAL_HANDLE threadHandle)
     return IS_ERR(ret) ? unix_to_pal_error(ERRNO(ret)) : ret;
 }
 
-int _DkThreadGetCurrent (PAL_HANDLE * threadHandle)
-{
-    *threadHandle = (PAL_HANDLE) GET_ENCLAVE_TLS(thread);
-    return 0;
-}
-
 struct handle_ops thread_ops = {
     /* nothing */
 };

+ 1 - 0
Pal/src/host/Linux-SGX/ecall_types.h

@@ -1,6 +1,7 @@
 enum {
     ECALL_ENCLAVE_START = 0,
     ECALL_THREAD_START,
+    ECALL_THREAD_RESET,
     ECALL_NR,
 };
 

+ 56 - 17
Pal/src/host/Linux-SGX/enclave_entry.S

@@ -14,9 +14,8 @@
 	.type enclave_entry, @function
 
 enclave_entry:
-	# On EENTER, RAX is the current SSA index (aka CSSA),
-	# RBX is the address of TCS, RCX is the address of AEP.
-	# Other registers are not trusted.
+	# On EENTER, RAX is the current SSA index (aka CSSA), RBX is the address of
+	# TCS, RCX is the address of IP following EENTER. Other regs are not trusted.
 
 	# x86-64 sysv abi requires %rFLAGS.DF = 0 on entry to function call.
 	cld
@@ -24,6 +23,9 @@ enclave_entry:
 	cmpq $0, %rax
 	jne .Lprepare_resume
 
+	# ECALL return address in RCX (filled by EENTER hardware flow)
+	movq %rcx, %gs:SGX_ECALL_RETURN_ADDR
+
 	# The following code is hardened to defend attacks from untrusted host.
 	# Any states given by the host instead of the ISA must be assumed
 	# potentially malicious.
@@ -48,24 +50,25 @@ enclave_entry:
 	cmpq $0, %gs:SGX_OCALL_PREPARED
 	jne .Lreturn_from_ocall
 
-	movq %rcx, %gs:SGX_AEP
-
-	# Ecalls are only used to start a thread (either the main or an
-	# additional thread). So per thread we should only get exactly one
-	# ecall. Enforce this here.
-	cmpq $0, %gs:SGX_ECALL_CALLED
-	je 1f
-	FAIL_LOOP
-1:
-	movq $1, %gs:SGX_ECALL_CALLED
-
 	# PAL convention:
 	# RDI - ECALL number
-	# RSI - prointer to ecall arguments
+	# RSI - pointer to ecall arguments
 	# RDX - exit target
 	# RCX (former RSP) - The untrusted stack
 	# R8  - enclave base
 
+	cmpq $ECALL_THREAD_RESET, %rdi
+	je .Lhandle_thread_reset
+
+	# Except ecall_thread_reset, ecalls are only used to start a thread (main
+	# or additional threads). We already checked for case of ecall_thread_reset,
+	# so at this point we should only get exactly one ecall per thread
+	cmpq $0, %gs:SGX_THREAD_STARTED
+	je 1f
+	FAIL_LOOP
+1:
+	movq $1, %gs:SGX_THREAD_STARTED
+
 	# calculate enclave base = RBX (trusted) - %gs:SGX_TCS_OFFSET
 	subq %gs:SGX_TCS_OFFSET, %rbx
 	movq %rbx, %r8
@@ -95,6 +98,27 @@ enclave_entry:
 	# handle_ecall will only return when invalid parameters has been passed.
 	FAIL_LOOP
 
+	# clear TLS variables for thread reuse
+.Lhandle_thread_reset:
+	movq $0, %gs:SGX_READY_FOR_EXCEPTIONS
+
+	# Signals are impossible at this point: benign untrusted runtime blocks
+	# all signals (see sgx_ocall_exit()), and even if malicious one doesn't
+	# block them, signals are ignored due to SGX_READY_FOR_EXCEPTIONS = 0.
+	movq $0, %gs:SGX_THREAD_STARTED
+
+	# Assertion: thread is reset only after special-case OCALL_EXIT which
+	# does *not* set SGX_OCALL_PREPARED = 1.
+	cmpq $0, %gs:SGX_OCALL_PREPARED
+	je 1f
+	FAIL_LOOP
+1:
+	# Instead of jumping to .Lclear_and_eexit, simply perform EEXIT because
+	# there is no modified state to clear in this "thread-reset" code path.
+	movq %gs:SGX_ECALL_RETURN_ADDR, %rbx
+	movq $EEXIT, %rax
+	ENCLU
+
 .Lprepare_resume:
 	# PAL convention:
 	# RDI - external event
@@ -127,6 +151,7 @@ enclave_entry:
 	cmpl $0, %edi
 	jne .Lhandle_exception
 
+.Lignore_exception:
 	# clear the registers
 	xorq %rdi, %rdi
 	xorq %rsi, %rsi
@@ -180,12 +205,18 @@ enclave_entry:
 	cmpq $0, %rsi
 	je .Lsetup_exception_handler
 
-	# Assertion:
+	# The usual case (bar OCALL_EXIT):
 	# SGX_OCALL_PREPARED set to 1 before SGX_STACK is set to enclave stack.
 	# SGX_OCALL_PREPARED set to 0 after SGX_STACK is set to 0.
 	cmpq $0, %gs:SGX_OCALL_PREPARED
 	jne 1f
-	FAIL_LOOP
+
+	# At this point, we are in the exception handler, SGX_STACK != 0 but
+	# SGX_OCALL_PREPARED = 0. This can only happen if we are interrupted
+	# during a special case of never-returning OCALL_EXIT. Because the
+	# thread is going to exit anyway, we can ignore this exception.
+	jmp .Lignore_exception
+
 1:
 	# At this point, we are in the exception handler,
 	# SGX_STACK=<trusted pointer to enclave stack>, SGX_OCALL_PREPARED=1,
@@ -431,6 +462,14 @@ sgx_ocall:
 
 	pushq %rbp
 
+	# OCALL_EXIT should never return (see sgx_ocall_exit(): it always exits
+	# the thread). Skip setting SGX_OCALL_PREPARED to land in special-case
+	# of ECALL_THREAD_RESET (issued in sgx_ocall_exit()) later. Note that if
+	# there is an interrupt (which usually would result in a simulated
+	# return of -EINTR), it will be silently ignored via .Lignore_exception.
+	cmpq $OCALL_EXIT, %rdi
+	je .Locall_after_set_ocall_prepared
+
 .Locall_before_set_ocall_prepared:
 	movq $1, %gs:SGX_OCALL_PREPARED
 .Locall_after_set_ocall_prepared:

+ 12 - 2
Pal/src/host/Linux-SGX/generated-offsets.c

@@ -6,6 +6,8 @@
 #include "pal_linux.h"
 #include "pal_linux_defs.h"
 #include "pal_security.h"
+#include "ecall_types.h"
+#include "ocall_types.h"
 
 #include <generated-offsets-build.h>
 
@@ -68,7 +70,7 @@ void dummy(void)
     OFFSET(SGX_ENCLAVE_SIZE, enclave_tls, enclave_size);
     OFFSET(SGX_TCS_OFFSET, enclave_tls, tcs_offset);
     OFFSET(SGX_INITIAL_STACK_OFFSET, enclave_tls, initial_stack_offset);
-    OFFSET(SGX_AEP, enclave_tls, aep);
+    OFFSET(SGX_ECALL_RETURN_ADDR, enclave_tls, ecall_return_addr);
     OFFSET(SGX_SSA, enclave_tls, ssa);
     OFFSET(SGX_GPR, enclave_tls, gpr);
     OFFSET(SGX_EXIT_TARGET, enclave_tls, exit_target);
@@ -78,7 +80,7 @@ void dummy(void)
     OFFSET(SGX_USTACK, enclave_tls, ustack);
     OFFSET(SGX_THREAD, enclave_tls, thread);
     OFFSET(SGX_OCALL_PREPARED, enclave_tls, ocall_prepared);
-    OFFSET(SGX_ECALL_CALLED, enclave_tls, ecall_called);
+    OFFSET(SGX_THREAD_STARTED, enclave_tls, thread_started);
     OFFSET(SGX_READY_FOR_EXCEPTIONS, enclave_tls, ready_for_exceptions);
     OFFSET(SGX_MANIFEST_SIZE, enclave_tls, manifest_size);
     OFFSET(SGX_HEAP_MIN, enclave_tls, heap_min);
@@ -133,5 +135,13 @@ void dummy(void)
 
     /* errno */
     DEFINE(EINTR, EINTR);
+
+    /* Ecall numbers */
+    DEFINE(ECALL_ENCLAVE_START, ECALL_ENCLAVE_START);
+    DEFINE(ECALL_THREAD_START, ECALL_THREAD_START);
+    DEFINE(ECALL_THREAD_RESET, ECALL_THREAD_RESET);
+
+    /* Ocall Index */
+    DEFINE(OCALL_EXIT, OCALL_EXIT);
 }
 

+ 16 - 1
Pal/src/host/Linux-SGX/sgx_enclave.c

@@ -1,6 +1,7 @@
 #include "ocall_types.h"
 #include "ecall_types.h"
 #include "sgx_internal.h"
+#include "sgx_enclave.h"
 #include "pal_security.h"
 #include "pal_linux_error.h"
 
@@ -30,10 +31,19 @@ static int sgx_ocall_exit(void* pms)
         ms->ms_exitcode = 255;
     }
 
+    /* exit the whole process if exit_group() */
     if (ms->ms_is_exitgroup)
         INLINE_SYSCALL(exit_group, 1, (int)ms->ms_exitcode);
-    else
+
+    /* otherwise call SGX-related thread reset and exit this thread */
+    block_async_signals(true);
+    ecall_thread_reset();
+
+    /* threads created with pthread_create() must exit with pthread_exit() */
+    if (!unmap_tcs())
         INLINE_SYSCALL(exit, 1, (int)ms->ms_exitcode);
+    else
+        thread_exit(&ms->ms_exitcode);
 
     return 0;
 }
@@ -727,6 +737,11 @@ int ecall_thread_start (void)
     return sgx_ecall(ECALL_THREAD_START, NULL);
 }
 
+int ecall_thread_reset(void) {
+    EDEBUG(ECALL_THREAD_RESET, NULL);
+    return sgx_ecall(ECALL_THREAD_RESET, NULL);
+}
+
 noreturn void __abort(void) {
     INLINE_SYSCALL(exit_group, 1, -1);
     while (true) {

+ 2 - 0
Pal/src/host/Linux-SGX/sgx_enclave.h

@@ -4,3 +4,5 @@
 int ecall_enclave_start (char * args, size_t args_size, char * env, size_t env_size);
 
 int ecall_thread_start (void);
+
+int ecall_thread_reset (void);

+ 6 - 0
Pal/src/host/Linux-SGX/sgx_entry.S

@@ -9,6 +9,8 @@
 	.type sgx_ecall, @function
 
 sgx_ecall:
+	pushq %rbx
+
 	# put entry address in RDX
 	leaq sgx_entry(%rip), %rdx
 
@@ -25,6 +27,10 @@ sgx_ecall:
 	movq $EENTER, %rax
 	ENCLU
 
+	# currently only ECALL_THREAD_RESET returns
+	popq %rbx
+	retq
+
 	.global async_exit_pointer
 	.type async_exit_pointer, @function
 

+ 3 - 2
Pal/src/host/Linux-SGX/sgx_internal.h

@@ -114,8 +114,9 @@ int interrupt_thread (void * tcs);
 int clone_thread (void);
 
 void create_tcs_mapper (void * tcs_base, unsigned int thread_num);
-void map_tcs (unsigned int tid);
-void unmap_tcs (void);
+void map_tcs(unsigned int tid, bool created_by_pthread);
+bool unmap_tcs(void);
+void thread_exit(void* rv);
 
 extern __thread struct pal_enclave * current_enclave;
 

+ 2 - 1
Pal/src/host/Linux-SGX/sgx_main.c

@@ -482,6 +482,7 @@ int initialize_enclave (struct pal_enclave * enclave)
                     gs->exec_addr = (void *) enclave_secs.baseaddr + exec_area->addr;
                     gs->exec_size = exec_area->size;
                 }
+                gs->thread = NULL;
             }
         } else if (strcmp_static(areas[i].desc, "tcs")) {
             data = (void *) INLINE_SYSCALL(mmap, 6, NULL, areas[i].size,
@@ -884,7 +885,7 @@ static int load_enclave (struct pal_enclave * enclave,
         return ret;
 
     current_enclave = enclave;
-    map_tcs(INLINE_SYSCALL(gettid, 0));
+    map_tcs(INLINE_SYSCALL(gettid, 0), /* created_by_pthread=*/false);
 
     /* start running trusted PAL */
     ecall_enclave_start(args, args_size, env, env_size);

+ 31 - 11
Pal/src/host/Linux-SGX/sgx_thread.c

@@ -1,3 +1,4 @@
+#include "assert.h"
 #include "pal_internal.h"
 #include "sgx_internal.h"
 #include "pal_security.h"
@@ -16,6 +17,7 @@ __thread sgx_arch_tcs_t * current_tcs;
 
 struct thread_map {
     unsigned int     tid;
+    bool             created_by_pthread;
     sgx_arch_tcs_t * tcs;
 };
 
@@ -23,6 +25,8 @@ static sgx_arch_tcs_t * enclave_tcs;
 static int enclave_thread_num;
 static struct thread_map * enclave_thread_map;
 
+pthread_mutex_t tcs_lock = PTHREAD_MUTEX_INITIALIZER;
+
 void create_tcs_mapper (void * tcs_base, unsigned int thread_num)
 {
     enclave_tcs = tcs_base;
@@ -35,34 +39,42 @@ void create_tcs_mapper (void * tcs_base, unsigned int thread_num)
     }
 }
 
-void map_tcs (unsigned int tid)
-{
+void map_tcs(unsigned int tid, bool created_by_pthread) {
+    pthread_mutex_lock(&tcs_lock);
     for (int i = 0 ; i < enclave_thread_num ; i++)
         if (!enclave_thread_map[i].tid) {
             enclave_thread_map[i].tid = tid;
+            enclave_thread_map[i].created_by_pthread = created_by_pthread;
             current_tcs = enclave_thread_map[i].tcs;
             ((struct enclave_dbginfo *) DBGINFO_ADDR)->thread_tids[i] = tid;
             break;
         }
+    pthread_mutex_unlock(&tcs_lock);
 }
 
-void unmap_tcs (void)
-{
+/* return true if unmapped thread was created with pthread_create(), false otherwise */
+bool unmap_tcs(void) {
     int index = current_tcs - enclave_tcs;
+    bool ret = false;
     struct thread_map * map = &enclave_thread_map[index];
-    if (index >= enclave_thread_num)
-        return;
+
+    assert(index < enclave_thread_num);
     SGX_DBG(DBG_I, "unmap TCS at %p\n", map->tcs);
+
+    pthread_mutex_lock(&tcs_lock);
     current_tcs = NULL;
     ((struct enclave_dbginfo *) DBGINFO_ADDR)->thread_tids[index] = 0;
     map->tid = 0;
-    map->tcs = NULL;
+    ret = map->created_by_pthread;
+    pthread_mutex_unlock(&tcs_lock);
+
+    return ret;
 }
 
 static void * thread_start (void * arg)
 {
     int tid = INLINE_SYSCALL(gettid, 0);
-    map_tcs(tid);
+    map_tcs(tid, /*created_by_pthread=*/true);
     current_enclave = arg;
 
     if (!current_tcs) {
@@ -78,10 +90,18 @@ static void * thread_start (void * arg)
     return NULL;
 }
 
-int clone_thread (void)
-{
+void thread_exit(void* rv) {
+    pthread_exit(rv);
+}
+
+int clone_thread(void) {
     pthread_t thread;
-    return pthread_create(&thread, NULL, thread_start, current_enclave);
+
+    pthread_attr_t attr;
+    pthread_attr_init(&attr);
+    pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
+
+    return pthread_create(&thread, &attr, thread_start, current_enclave);
 }
 
 int interrupt_thread (void * tcs)

+ 2 - 2
Pal/src/host/Linux-SGX/sgx_tls.h

@@ -16,7 +16,7 @@ struct enclave_tls {
         uint64_t enclave_size;
         uint64_t tcs_offset;
         uint64_t initial_stack_offset;
-        void*    aep;
+        void*    ecall_return_addr;
         void*    ssa;
         sgx_arch_gpr_t* gpr;
         void*    exit_target;
@@ -26,7 +26,7 @@ struct enclave_tls {
         void*    ustack;
         struct pal_handle_thread* thread;
         uint64_t ocall_prepared;
-        uint64_t ecall_called;
+        uint64_t thread_started;
         uint64_t ready_for_exceptions;
         uint64_t manifest_size;
         void*    heap_min;