sgx_thread.c 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. #include "assert.h"
  2. #include "pal_internal.h"
  3. #include "pal_security.h"
  4. #include "sgx_internal.h"
  5. #include "spinlock.h"
  6. #include <asm/errno.h>
  7. #include <asm/prctl.h>
  8. #include <asm/signal.h>
  9. #include <linux/futex.h>
  10. #include <linux/signal.h>
  11. #include "sgx_enclave.h"
  12. #include "debugger/sgx_gdb.h"
  13. struct thread_map {
  14. unsigned int tid;
  15. sgx_arch_tcs_t * tcs;
  16. };
  17. static sgx_arch_tcs_t * enclave_tcs;
  18. static int enclave_thread_num;
  19. static struct thread_map * enclave_thread_map;
  20. void pal_tcb_urts_init(PAL_TCB_URTS* tcb, void* stack, void* alt_stack) {
  21. tcb->self = tcb;
  22. tcb->tcs = NULL; /* initialized by child thread */
  23. tcb->stack = stack;
  24. tcb->alt_stack = alt_stack;
  25. }
  26. static spinlock_t tcs_lock = INIT_SPINLOCK_UNLOCKED;
  27. void create_tcs_mapper (void * tcs_base, unsigned int thread_num)
  28. {
  29. size_t thread_map_size = ALIGN_UP_POW2(sizeof(struct thread_map) * thread_num, PRESET_PAGESIZE);
  30. enclave_tcs = tcs_base;
  31. enclave_thread_num = thread_num;
  32. enclave_thread_map = (struct thread_map*)INLINE_SYSCALL(mmap, 6, NULL, thread_map_size,
  33. PROT_READ | PROT_WRITE,
  34. MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  35. for (uint32_t i = 0 ; i < thread_num ; i++) {
  36. enclave_thread_map[i].tid = 0;
  37. enclave_thread_map[i].tcs = &enclave_tcs[i];
  38. }
  39. }
  40. void map_tcs(unsigned int tid) {
  41. spinlock_lock(&tcs_lock);
  42. for (int i = 0 ; i < enclave_thread_num ; i++)
  43. if (!enclave_thread_map[i].tid) {
  44. enclave_thread_map[i].tid = tid;
  45. get_tcb_urts()->tcs = enclave_thread_map[i].tcs;
  46. ((struct enclave_dbginfo *) DBGINFO_ADDR)->thread_tids[i] = tid;
  47. break;
  48. }
  49. spinlock_unlock(&tcs_lock);
  50. }
  51. void unmap_tcs(void) {
  52. spinlock_lock(&tcs_lock);
  53. int index = get_tcb_urts()->tcs - enclave_tcs;
  54. struct thread_map * map = &enclave_thread_map[index];
  55. assert(index < enclave_thread_num);
  56. get_tcb_urts()->tcs = NULL;
  57. ((struct enclave_dbginfo *) DBGINFO_ADDR)->thread_tids[index] = 0;
  58. map->tid = 0;
  59. spinlock_unlock(&tcs_lock);
  60. }
  61. /*
  62. * pal_thread_init(): An initialization wrapper of a newly-created thread (including
  63. * the first thread). This function accepts a TCB pointer to be set to the GS register
  64. * of the thread. The rest of the TCB is used as the alternative stack for signal
  65. * handling. Notice that this sets up the untrusted thread -- an enclave thread is set
  66. * up by other means (e.g., the GS register is set by an SGX-enforced TCS.OGSBASGX).
  67. */
  68. int pal_thread_init(void* tcbptr) {
  69. PAL_TCB_URTS* tcb = tcbptr;
  70. int ret;
  71. /* set GS reg of this thread to thread's TCB; after this point, can use get_tcb_urts() */
  72. ret = INLINE_SYSCALL(arch_prctl, 2, ARCH_SET_GS, tcb);
  73. if (IS_ERR(ret)) {
  74. ret = -EPERM;
  75. goto out;
  76. }
  77. if (tcb->alt_stack) {
  78. /* align stack to 16 bytes */
  79. void* alt_stack = ALIGN_DOWN_PTR(tcb, 16);
  80. assert(alt_stack > tcb->alt_stack);
  81. stack_t ss;
  82. ss.ss_sp = alt_stack;
  83. ss.ss_flags = 0;
  84. ss.ss_size = alt_stack - tcb->alt_stack;
  85. ret = INLINE_SYSCALL(sigaltstack, 2, &ss, NULL);
  86. if (IS_ERR(ret)) {
  87. ret = -EPERM;
  88. goto out;
  89. }
  90. }
  91. int tid = INLINE_SYSCALL(gettid, 0);
  92. map_tcs(tid); /* updates tcb->tcs */
  93. if (!tcb->tcs) {
  94. SGX_DBG(DBG_E,
  95. "There are no available TCS pages left for a new thread!\n"
  96. "Please try to increase sgx.thread_num in the manifest.\n"
  97. "The current value is %d\n", enclave_thread_num);
  98. ret = -ENOMEM;
  99. goto out;
  100. }
  101. if (!tcb->stack) {
  102. /* only first thread doesn't have a stack (it uses the one provided by Linux); first
  103. * thread calls ecall_enclave_start() instead of ecall_thread_start() so just exit */
  104. return 0;
  105. }
  106. /* not-first (child) thread, start it */
  107. ecall_thread_start();
  108. unmap_tcs();
  109. ret = 0;
  110. out:
  111. INLINE_SYSCALL(munmap, 2, tcb->stack, THREAD_STACK_SIZE + ALT_STACK_SIZE);
  112. return ret;
  113. }
  114. noreturn void thread_exit(int status) {
  115. PAL_TCB_URTS* tcb = get_tcb_urts();
  116. /* technically, async signals were already blocked before calling this function
  117. * (by sgx_ocall_exit()) but we keep it here for future proof */
  118. block_async_signals(true);
  119. if (tcb->alt_stack) {
  120. stack_t ss;
  121. ss.ss_sp = NULL;
  122. ss.ss_flags = SS_DISABLE;
  123. ss.ss_size = 0;
  124. /* take precautions to unset the TCB and alternative stack first */
  125. INLINE_SYSCALL(arch_prctl, 2, ARCH_SET_GS, 0);
  126. INLINE_SYSCALL(sigaltstack, 2, &ss, NULL);
  127. }
  128. /* free the thread stack (via munmap) and exit; note that exit() needs a "status" arg
  129. * but it could be allocated on a stack, so we must put it in register and do asm */
  130. __asm__ volatile("cmpq $0, %%rdi \n\t" /* check if tcb->stack != NULL */
  131. "je 1f \n\t"
  132. "syscall \n\t" /* all args are already prepared, call munmap */
  133. "1: \n\t"
  134. "movq %%rdx, %%rax \n\t" /* prepare for exit: rax = __NR_exit */
  135. "movq %%rbx, %%rdi \n\t" /* prepare for exit: rdi = status */
  136. "syscall \n\t" /* all args are prepared, call exit */
  137. : /* no output regs since we don't return from exit */
  138. : "a"(__NR_munmap), "D"(tcb->stack), "S"(THREAD_STACK_SIZE + ALT_STACK_SIZE),
  139. "d"(__NR_exit), "b"(status)
  140. : "cc", "rcx", "r11", "memory" /* syscall instr clobbers cc, rcx, and r11 */
  141. );
  142. while (true) {
  143. /* nothing */
  144. }
  145. }
  146. int clone_thread(void) {
  147. int ret = 0;
  148. void* stack = (void*)INLINE_SYSCALL(mmap, 6, NULL, THREAD_STACK_SIZE + ALT_STACK_SIZE,
  149. PROT_READ | PROT_WRITE,
  150. MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
  151. if (IS_ERR_P(stack))
  152. return -ENOMEM;
  153. /* Stack layout for the new thread looks like this (recall that stacks grow towards lower
  154. * addresses on Linux on x86-64):
  155. *
  156. * stack +--> +-------------------+
  157. * | child stack | THREAD_STACK_SIZE
  158. * child_stack +--> +-------------------+
  159. * | alternate stack | ALT_STACK_SIZE - sizeof(PAL_TCB_URTS)
  160. * tcb +--> +-------------------+
  161. * | PAL TCB | sizeof(PAL_TCB_URTS)
  162. * +-------------------+
  163. *
  164. * Note that this whole memory region is zeroed out because we use mmap(). */
  165. void* child_stack_top = stack + THREAD_STACK_SIZE;
  166. /* initialize TCB at the top of the alternative stack */
  167. PAL_TCB_URTS* tcb = child_stack_top + ALT_STACK_SIZE - sizeof(PAL_TCB_URTS);
  168. pal_tcb_urts_init(tcb, stack, child_stack_top);
  169. /* align child_stack to 16 */
  170. child_stack_top = ALIGN_DOWN_PTR(child_stack_top, 16);
  171. int dummy_parent_tid_field = 0;
  172. ret = clone(pal_thread_init, child_stack_top,
  173. CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SYSVSEM | CLONE_THREAD |
  174. CLONE_SIGHAND | CLONE_PARENT_SETTID,
  175. (void*)tcb, &dummy_parent_tid_field, NULL);
  176. if (IS_ERR(ret)) {
  177. INLINE_SYSCALL(munmap, 2, stack, THREAD_STACK_SIZE + ALT_STACK_SIZE);
  178. return -ERRNO(ret);
  179. }
  180. return 0;
  181. }
  182. int interrupt_thread (void * tcs)
  183. {
  184. int index = (sgx_arch_tcs_t *) tcs - enclave_tcs;
  185. struct thread_map * map = &enclave_thread_map[index];
  186. if (index >= enclave_thread_num)
  187. return -EINVAL;
  188. if (!map->tid)
  189. return -EINVAL;
  190. INLINE_SYSCALL(tgkill, 3, pal_enclave.pal_sec.pid, map->tid, SIGCONT);
  191. return 0;
  192. }