x86: Fixes context security
[akaros.git] / kern / arch / x86 / process64.c
index acbfeb7..3e6dc38 100644 (file)
@@ -8,38 +8,15 @@
 #include <assert.h>
 #include <stdio.h>
 
-/* TODO: handle user and kernel contexts */
 void proc_pop_ctx(struct user_context *ctx)
 {
-       struct hw_trapframe *tf = &ctx->tf.hw_tf;
-       assert(ctx->type == ROS_HW_CTX);
-
-       /* Bug with this whole idea (TODO: (TLSV))*/
-       /* Load the LDT for this process.  Slightly ghetto doing it here. */
-       /* copy-in and check the LDT location.  the segmentation hardware writes the
-        * accessed bit, so we want the memory to be in the user-writeable area. */
-       segdesc_t *ldt = current->procdata->ldt;
-       ldt = (segdesc_t*)MIN((uintptr_t)ldt, UWLIM - LDT_SIZE);
-       /* Only set up the ldt if a pointer to the ldt actually exists */
-#if 0 /* think about how to do TLS.  need better seg macros too */
-       if(ldt != NULL) {
-               segdesc_t *my_gdt = per_cpu_info[core_id()].gdt;
-               /* TODO: 64b issues here.  need to redo this anyways.  Considering how
-                * slow userspace TLS changes are (70ns), I might opt for just changing
-                * FS base, either via fast syscall or in userspace on newer versions */
-               segdesc_t ldt_temp = SEG_SYS(STS_LDT, (uint32_t)ldt, LDT_SIZE, 3);
-               my_gdt[GD_LDT >> 3] = ldt_temp;
-               asm volatile("lldt %%ax" :: "a"(GD_LDT));
-       }
-#endif
-
-       /* In case they are enabled elsewhere.  We can't take an interrupt in these
-        * routines, due to how they play with the kernel stack pointer. */
        disable_irq();
-       /* If the process entered the kernel via sysenter, we need to leave via
-        * sysexit.  sysenter trapframes have 0 for a CS, which is pushed in
-        * sysenter_handler. */
-       if (tf->tf_cs) {
+       /* for both HW and SW, note we pass an offset into the TF, beyond the fs and
+        * gs bases */
+       if (ctx->type == ROS_HW_CTX) {
+               struct hw_trapframe *tf = &ctx->tf.hw_tf;
+               write_msr(MSR_GS_BASE, (uint64_t)tf->tf_gsbase);
+               write_msr(MSR_FS_BASE, (uint64_t)tf->tf_fsbase);
                asm volatile ("movq %0, %%rsp;          "
                              "popq %%rax;              "
                              "popq %%rbx;              "
@@ -56,98 +33,102 @@ void proc_pop_ctx(struct user_context *ctx)
                              "popq %%r13;              "
                              "popq %%r14;              "
                              "popq %%r15;              "
-                             "movw 0x4(%%rsp), %%gs;   "
-                             "movw 0x6(%%rsp), %%fs;   "
                              "addq $0x10, %%rsp;       "
                              "iretq                    "
-                             : : "g" (tf) : "memory");
-               panic("iret failed");  /* mostly to placate the compiler */
+                             : : "g" (&tf->tf_rax) : "memory");
+               panic("iretq failed");
        } else {
-               /* Return path of sysexit.  See sysenter_handler's asm for details.
-                * One difference is that this tf could be somewhere other than a stack
-                * (like in a struct proc).  We need to make sure esp is valid once
-                * interrupts are turned on (which would happen on popfl normally), so
-                * we need to save and restore a decent esp (the current one).  We need
-                * a place to save it that is accessible after we change the stack
-                * pointer to the tf *and* that is specific to this core/instance of
-                * sysexit.  The simplest and nicest is to use the tf_esp, which we
-                * can just pop.  Incidentally, the value in oesp would work too.
-                * To prevent popfl from turning interrupts on, we hack the tf's eflags
-                * so that we have a chance to change esp to a good value before
-                * interrupts are enabled.  The other option would be to throw away the
-                * eflags, but that's less desirable. */
-               tf->tf_rflags &= !FL_IF;
-               tf->tf_rsp = read_sp();
-//             asm volatile ("movl %0,%%esp;           "
-//                           "popal;                   "
-//                           "popl %%gs;               "
-//                           "popl %%fs;               "
-//                           "popl %%es;               "
-//                           "popl %%ds;               "
-//                           "addl $0x10,%%esp;        "
-//                           "popfl;                   "
-//                           "movl %%ebp,%%ecx;        "
-//                           "popl %%esp;              "
-//                           "sti;                     "
-//                           "sysexit                  "
-//                           : : "g" (tf) : "memory");
-               panic("sysexit failed");  /* mostly to placate your mom */
+               struct sw_trapframe *tf = &ctx->tf.sw_tf;
+               write_msr(MSR_GS_BASE, (uint64_t)tf->tf_gsbase);
+               write_msr(MSR_FS_BASE, (uint64_t)tf->tf_fsbase);
+               /* We need to 0 out any registers that aren't part of the sw_tf and that
+                * we won't use/clobber on the out-path.  While these aren't part of the
+                * sw_tf, we also don't want to leak any kernel register content. */
+               asm volatile ("movq %0, %%rsp;          "
+                             "movq $0, %%rax;          "
+                                         "movq $0, %%rdx;          "
+                                         "movq $0, %%rsi;          "
+                                         "movq $0, %%rdi;          "
+                                         "movq $0, %%r8;           "
+                                         "movq $0, %%r9;           "
+                                         "movq $0, %%r10;          "
+                             "popq %%rbx;              "
+                             "popq %%rbp;              "
+                             "popq %%r12;              "
+                             "popq %%r13;              "
+                             "popq %%r14;              "
+                             "popq %%r15;              "
+                                         "movq %1, %%r11;          "
+                             "popq %%rcx;              "
+                             "popq %%rsp;              "
+                             "rex.w sysret             "
+                             : : "g"(&tf->tf_rbx), "i"(FL_IF) : "memory");
+               panic("sysret failed");
        }
+       panic("Unknown context type!\n");
+}
+
+/* Helper: if *addr isn't a canonical user address, poison it.  Use this when
+ * you need a canonical address (like MSR_FS_BASE) */
+static void enforce_user_canon(uintptr_t *addr)
+{
+       if (*addr >> 47 != 0)
+               *addr = 0x5a5a5a5a;
 }
 
 /* TODO: consider using a SW context */
 void proc_init_ctx(struct user_context *ctx, uint32_t vcoreid, uintptr_t entryp,
-                   uintptr_t stack_top)
+                   uintptr_t stack_top, uintptr_t tls_desc)
 {
        struct hw_trapframe *tf = &ctx->tf.hw_tf;
+       /* zero the entire structure for any type, prevent potential disclosure */
+       memset(ctx, 0, sizeof(struct user_context));
        ctx->type = ROS_HW_CTX;
-
-       memset(tf,0,sizeof(*tf));
-
-       /* Set up appropriate initial values for the segment registers.
-        * GD_UD is the user data segment selector in the GDT, and
-        * GD_UT is the user text segment selector (see inc/memlayout.h).
-        * The low 2 bits of each segment register contains the
-        * Requestor Privilege Level (RPL); 3 means user mode. */
-       tf->tf_ss = GD_UD | 3;
-       tf->tf_rsp = stack_top-64;
-       tf->tf_cs = GD_UT | 3;
-       /* set the env's EFLAGSs to have interrupts enabled */
-       tf->tf_rflags |= 0x00000200; // bit 9 is the interrupts-enabled
-
+       /* Stack pointers in a fresh stackframe need to be such that adding or
+        * subtracting 8 will result in 16 byte alignment (AMD64 ABI).  The reason
+        * is so that input arguments (on the stack) are 16 byte aligned.  The
+        * extra 8 bytes is the retaddr, pushed on the stack.  Compilers know they
+        * can subtract 8 to get 16 byte alignment for instructions like movaps. */
+       tf->tf_rsp = ROUNDDOWN(stack_top, 16) - 8;
        tf->tf_rip = entryp;
-
        /* Coupled closely with user's entry.S.  id is the vcoreid, which entry.S
         * uses to determine what to do.  vcoreid == 0 is the main core/context. */
        tf->tf_rax = vcoreid;
+       tf->tf_fsbase = tls_desc;
+       proc_secure_ctx(ctx);
 }
 
-/* TODO: handle both HW and SW contexts */
 void proc_secure_ctx(struct user_context *ctx)
 {
-       struct hw_trapframe *tf = &ctx->tf.hw_tf;
-       ctx->type = ROS_HW_CTX;
-       /* we normally don't need to set the non-CS regs, but they could be
-        * gibberish and cause a GPF.  gs can still be gibberish, but we don't
-        * necessarily know what it ought to be (we could check, but that's a pain).
-        * the code protecting the kernel from TLS related things ought to be able
-        * to handle GPFs on popping gs. TODO: (TLSV) */
-       //tf->tf_fs = 0;
-       //tf->tf_gs = whatevs.  ignoring this.
-       tf->tf_ss = GD_UD | 3;
-       tf->tf_cs ? GD_UT | 3 : 0; // can be 0 for sysenter TFs.
-       tf->tf_rflags |= 0x00000200; // bit 9 is the interrupts-enabled
+       if (ctx->type == ROS_SW_CTX) {
+               struct sw_trapframe *tf = &ctx->tf.sw_tf;
+               enforce_user_canon(&tf->tf_gsbase);
+               enforce_user_canon(&tf->tf_fsbase);
+               enforce_user_canon(&tf->tf_rip);
+       } else {
+               /* If we aren't SW, we're assuming (and forcing) a HW ctx.  If this is
+                * somehow fucked up, userspace should die rather quickly. */
+               struct hw_trapframe *tf = &ctx->tf.hw_tf;
+               ctx->type = ROS_HW_CTX;
+               enforce_user_canon(&tf->tf_gsbase);
+               enforce_user_canon(&tf->tf_fsbase);
+               /* GD_UD is the user data segment selector in the GDT, and
+                * GD_UT is the user text segment selector (see inc/memlayout.h).
+                * The low 2 bits of each segment register contains the
+                * Requestor Privilege Level (RPL); 3 means user mode. */
+               tf->tf_ss = GD_UD | 3;
+               tf->tf_cs = GD_UT | 3;
+               tf->tf_rflags |= FL_IF;
+       }
 }
 
 /* Called when we are currently running an address space on our core and want to
  * abandon it.  We need a known good pgdir before releasing the old one.  We
  * decref, since current no longer tracks the proc (and current no longer
- * protects the cr3).  We also need to clear out the TLS registers (before
- * unmapping the address space!) */
+ * protects the cr3). */
 void __abandon_core(void)
 {
        struct per_cpu_info *pcpui = &per_cpu_info[core_id()];
-       asm volatile ("movw %%ax,%%gs; lldt %%ax" :: "a"(0));
        lcr3(boot_cr3);
        proc_decref(pcpui->cur_proc);
        pcpui->cur_proc = 0;