x86: Add vDSO exception fixup for SGX
Subsystems (5)
v4 cover x86: Add vDSO exception fixup for SGX
Episode IV: The vDSO Strikes Back
After a brief detour into ioctl-based fixup, the vDSO implementation is back. Relative to v2 (the previous vDSO RFC), patch 4/4 once again contains the vast majority of changes.
__vdso_sgx_enter_enclave() is now written entirely in straight assembly. Implementing the expanded enclave ABI in inline assembly was an absolute train wreck as GCC's contraints don't play nice with the full spectrum of registers. And I suspect the previous vDSO RFCs were likely broken due to incorrect register clobbering (I never tested them end-to-end).
The exit_handler() concept proposed in v2 is gone. I expect most SGX libraries will directly call __vdso_sgx_enter_enclave(), at which point the overhead of returning effectively boils down to restoring the non-volatile registers, which is likely outweighed by the cost of the retpoline call (to the handler) alone. In other words, I doubt anyone will actually use the exit_handler()...
...except for libraries that want to manipulate the untrusted stack, i.e. force __vdso_sgx_enter_enclave() to treat RSP as volatile. At that point we're effectively maintaining two separate ABIs since the normal ABI would be e.g. "RBP, RSP and the red zone must be preserved" vs. the exit_handler() ABI's "RBP must be preserved". And *if* we want to deal with maintaining two ABIs, supporting the kernel's existing signal ABI is a lot cleaner (from a kernel perspective) than polluting the vDSO.
v1: https://lkml.kernel.org/r/[email protected]
v2: https://lkml.kernel.org/r/[email protected]
v3: https://lkml.kernel.org/r/[email protected]
v4:
- Back to vDSO
- Implement __vdso_sgx_enter_enclave() directly in assembly
- Modify effective enclave register ABI to follow x86-64 kernel ABI
- Split __vdso_sgx_enter_enclave input into separate non-union params
- Drop the exit_handler() concept
Sean Christopherson (5):
x86/vdso: Add support for exception fixup in vDSO functions x86/fault: Add helper function to sanitize error code x86/fault: Attempt to fixup unhandled #PF on ENCLU before signaling x86/traps: Attempt to fixup exceptions in vDSO before signaling x86/vdso: Add __vdso_sgx_enter_enclave() to wrap SGX enclave
transitions
arch/x86/entry/vdso/Makefile | 6 +- arch/x86/entry/vdso/extable.c | 37 ++++++ arch/x86/entry/vdso/extable.h | 29 +++++ arch/x86/entry/vdso/vdso-layout.lds.S | 9 +- arch/x86/entry/vdso/vdso.lds.S | 1 + arch/x86/entry/vdso/vdso2c.h | 58 ++++++++-- arch/x86/entry/vdso/vsgx_enter_enclave.S | 136 +++++++++++++++++++++++ arch/x86/include/asm/vdso.h | 5 + arch/x86/include/uapi/asm/sgx.h | 44 ++++++++ arch/x86/kernel/traps.c | 14 +++ arch/x86/mm/fault.c | 33 ++++-- 11 files changed, 349 insertions(+), 23 deletions(-) create mode 100644 arch/x86/entry/vdso/extable.c create mode 100644 arch/x86/entry/vdso/extable.h create mode 100644 arch/x86/entry/vdso/vsgx_enter_enclave.S
Signature · 1 line
2.19.2
RFC PATCH v4 1/5 x86/vdso: Add support for exception fixup in vDSO functions
The basic concept and implementation is very similar to the kernel's exception fixup mechanism. The key differences are that the kernel handler is hardcoded and the fixup entry addresses are relative to the overall table as opposed to individual entries.
Hardcoding the kernel handler avoids the need to figure out how to get userspace code to point at a kernel function. Given that the expected usage is to propagate information to userspace, dumping all fault information into registers is likely the desired behavior for the vast majority of yet-to-be-created functions. Use registers DI, SI and DX to communicate fault information, which follows Linux's ABI for register consumption and hopefully avoids conflict with hardware features that might leverage the fixup capabilities, e.g. register usage for SGX instructions was at least partially designed with calling conventions in mind.
Making fixup addresses relative to the overall table allows the table to be stripped from the final vDSO image (it's a kernel construct) without complicating the offset logic, e.g. entry-relative addressing would also need to account for the table's location relative to the image.
Regarding stripping the table, modify vdso2c to extract the table from the raw, a.k.a. unstripped, data and dump it as a standalone byte array in the resulting .c file. The original base of the table, its length and a pointer to the byte array are captured in struct vdso_image. Alternatively, the table could be dumped directly into the struct, but because the number of entries can vary per image, that would require either hardcoding a max sized table into the struct definition or defining the table as a flexible length array. The flexible length array approach has zero benefits, e.g. the base/size are still needed, and prevents reusing the extraction code, while hardcoding the max size adds ongoing maintenance just to avoid exporting the explicit size.
The immediate use case is for Intel Software Guard Extensions (SGX). SGX introduces a new CPL3-only "enclave" mode that runs as a sort of black box shared object that is hosted by an untrusted "normal" CPl3 process.
Entering an enclave can only be done through SGX-specific instructions, EENTER and ERESUME, and is a non-trivial process. Because of the complexity of transitioning to/from an enclave, the vast majority of enclaves are expected to utilize a library to handle the actual transitions. This is roughly analogous to how e.g. libc implementations are used by most applications.
Another crucial characteristic of SGX enclaves is that they can generate exceptions as part of their normal (at least as "normal" as SGX can be) operation that need to be handled *in* the enclave and/or are unique to SGX.
And because they are essentially fancy shared objects, a process can host any number of enclaves, each of which can execute multiple threads simultaneously.
Putting everything together, userspace enclaves will utilize a library that must be prepared to handle any and (almost) all exceptions any time at least one thread may be executing in an enclave. Leveraging signals to handle the enclave exceptions is unpleasant, to put it mildly, e.g. the SGX library must constantly (un)register its signal handler based on whether or not at least one thread is executing in an enclave, and filter and forward exceptions that aren't related to its enclaves. This becomes particularly nasty when using multiple levels of libraries that register signal handlers, e.g. running an enclave via cgo inside of the Go runtime.
Enabling exception fixup in vDSO allows the kernel to provide a vDSO function that wraps the low-level transitions to/from the enclave, i.e. the EENTER and ERESUME instructions. The vDSO function can intercept exceptions that would otherwise generate a signal and return the fault information directly to its caller, thus avoiding the need to juggle signal handlers.
--- arch/x86/entry/vdso/Makefile | 4 +- arch/x86/entry/vdso/extable.c | 37 +++++++++++++++++ arch/x86/entry/vdso/extable.h | 29 ++++++++++++++ arch/x86/entry/vdso/vdso-layout.lds.S | 9 ++++- arch/x86/entry/vdso/vdso2c.h | 58 +++++++++++++++++++++++---- arch/x86/include/asm/vdso.h | 5 +++ 6 files changed, 131 insertions(+), 11 deletions(-) create mode 100644 arch/x86/entry/vdso/extable.c create mode 100644 arch/x86/entry/vdso/extable.h
arch/x86/entry/vdso/Makefile +2 -2
@@ -20,7 +20,7 @@ VDSO32-$(CONFIG_IA32_EMULATION) := y vobjs-y := vdso-note.o vclock_gettime.o vgetcpu.o # files to link into kernel -obj-y += vma.o +obj-y += vma.o extable.o OBJECT_FILES_NON_STANDARD_vma.o := n # vDSO images to build
@@ -115,7 +115,7 @@ $(obj)/%-x32.o: $(obj)/%.o FORCE targets += vdsox32.lds $(vobjx32s-y) -$(obj)/%.so: OBJCOPYFLAGS := -S +$(obj)/%.so: OBJCOPYFLAGS := -S --remove-section __ex_table $(obj)/%.so: $(obj)/%.so.dbg $(call if_changed,objcopy)
arch/x86/entry/vdso/extable.c new file +37 -0
@@ -0,0 +1,37 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <linux/err.h> +#include <linux/mm.h> +#include <asm/current.h> +#include <asm/vdso.h> + +struct vdso_exception_table_entry { +int insn, fixup; +}; + +bool fixup_vdso_exception (struct pt_regs *regs,int trapnr, +unsigned long error_code,unsigned long fault_addr) +{ +const struct vdso_image *image = current->mm->context.vdso_image; +const struct vdso_exception_table_entry *extable; +unsigned int nr_entries, i; +unsigned long base; + +if (!current->mm->context.vdso) +return false; + + base = (unsigned long )current->mm->context.vdso + image->extable_base; + nr_entries = image->extable_len / (sizeof (*extable)); + extable = image->extable; + +for (i =0 ; i < nr_entries; i++) { +if (regs->ip == base + extable[i].insn) { + regs->ip = base + extable[i].fixup; + regs->di = trapnr; + regs->si = error_code; + regs->dx = fault_addr; +return true; + } + } + +return false; +}
arch/x86/entry/vdso/extable.h new file +29 -0
@@ -0,0 +1,29 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __VDSO_EXTABLE_H +#define __VDSO_EXTABLE_H + +/* +* Inject exception fixup for vDSO code. Unlike normal exception fixup, +* vDSO uses a dedicated handler the addresses are relative to the overall +* exception table, not each individual entry. +*/ +#ifdef __ASSEMBLY__ +#define _ASM_VDSO_EXTABLE_HANDLE (from, to) \ + ASM_VDSO_EXTABLE_HANDLE from to + +.macro ASM_VDSO_EXTABLE_HANDLE from:req to:req + .pushsection __ex_table, "a" + .long (\from) - __ex_table + .long (\to) - __ex_table + .popsection +.endm +#else +#define _ASM_VDSO_EXTABLE_HANDLE (from, to) \ + ".pushsection __ex_table, \"a\"\n" \ + ".long (" #from ") - __ex_table\n" \ + ".long (" #to ") - __ex_table\n" \ + ".popsection\n" +#endif + +#endif /* __VDSO_EXTABLE_H */ +
arch/x86/entry/vdso/vdso-layout.lds.S +8 -1
@@ -63,11 +63,18 @@ SECTIONS * stuff that isn't used at runtime in between. */ - .text : { *(.text*) } :text =0x90909090, + .text : { + *(.text*) + *(.fixup) + } :text =0x90909090, + + .altinstructions : { *(.altinstructions) } :text .altinstr_replacement : { *(.altinstr_replacement) } :text + __ex_table : { *(__ex_table) } :text + /DISCARD/ : { *(.discard) *(.discard.*)
arch/x86/entry/vdso/vdso2c.h +50 -8
90 lines not on this page · raw message
arch/x86/include/asm/vdso.h +5 -0
@@ -15,6 +15,8 @@ struct vdso_image {unsigned long size;/* Always a multiple of PAGE_SIZE */ unsigned long alt, alt_len; +unsigned long extable_base, extable_len; +const void *extable;long sym_vvar_start;/* Negative offset to the vvar area */
@@ -45,6 +47,9 @@ extern void __init init_vdso_image(const struct vdso_image *image);extern int map_vdso_once (const struct vdso_image *image,unsigned long addr); +extern bool fixup_vdso_exception (struct pt_regs *regs,int trapnr, +unsigned long error_code, +unsigned long fault_addr);#endif /* __ASSEMBLER__ */ #endif /* _ASM_X86_VDSO_H */
Signature · 1 line
2.19.2
RFC PATCH v4 2/5 x86/fault: Add helper function to sanitize error code
In reply to Sean Christopherson...to prepare for vDSO exception fixup, which will expose the error code to userspace and runs before set_signal_archinfo(), i.e. squashes the signal when fixup is successful.
--- arch/x86/mm/fault.c | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-)
arch/x86/mm/fault.c +14 -12
@@ -719,18 +719,22 @@ pgtable_bad(struct pt_regs *regs, unsigned long error_code,oops_end (flags, regs, sig); } -static void set_signal_archinfo (unsigned long address, -unsigned long error_code) +static void sanitize_error_code (unsigned long address, +unsigned long *error_code) { -struct task_struct *tsk = current; -/* * To avoid leaking information about the kernel page * table layout, pretend that user-mode accesses to * kernel addresses are always protection faults. */ if (address >=TASK_SIZE_MAX ) - error_code |=X86_PF_PROT ; + *error_code |=X86_PF_PROT ; +} + +static void set_signal_archinfo (unsigned long address, +unsigned long error_code) +{ +struct task_struct *tsk = current; tsk->thread.trap_nr =X86_TRAP_PF ; tsk->thread.error_code = error_code |X86_PF_USER ;
@@ -771,6 +775,8 @@ no_context(struct pt_regs *regs, unsigned long error_code,* faulting through the emulate_vsyscall() logic. */ if (current->thread.sig_on_uaccess_err && signal) { +sanitize_error_code (address, &error_code); +set_signal_archinfo (address, error_code);/* XXX: hwpoison faults will set the wrong code. */
@@ -920,13 +926,7 @@ __bad_area_nosemaphore(struct pt_regs *regs, unsigned long error_code,if (is_errata100 (regs, address))return ; -/* -* To avoid leaking information about the kernel page table -* layout, pretend that user-mode accesses to kernel addresses -* are always protection faults. -*/ -if (address >=TASK_SIZE_MAX ) - error_code |=X86_PF_PROT ; +sanitize_error_code (address, &error_code);if (likely (show_unhandled_signals))show_signal_msg (regs, error_code, address, tsk);
@@ -1045,6 +1045,8 @@ do_sigbus(struct pt_regs *regs, unsigned long error_code, unsigned long address,if (is_prefetch (regs, error_code, address))return ; +sanitize_error_code (address, &error_code); +set_signal_archinfo (address, error_code);#ifdef CONFIG_MEMORY_FAILURE
Signature · 1 line
2.19.2
RFC PATCH v4 3/5 x86/fault: Attempt to fixup unhandled #PF on ENCLU before signaling
In reply to Sean ChristophersonCall fixup_sgx_enclu_exception() in the SIGSEGV and SIGBUS paths of the page fault handler immediately prior to signaling. If the fault is fixed, return cleanly and do not generate a signal.
In the SIGSEGV flow, make sure the error code passed to userspace has been sanitized.
--- arch/x86/mm/fault.c | 7 +++++++ 1 file changed, 7 insertions(+)
arch/x86/mm/fault.c +7 -0
@@ -28,6 +28,7 @@#include <asm/mmu_context.h> /* vma_pkey() */ #include <asm/efi.h> /* efi_recover_from_page_fault()*/ #include <asm/desc.h> /* store_idt(), ... */ +#include <asm/vdso.h> /* fixup_vdso_exception() */ #define CREATE_TRACE_POINTS #include <asm/trace/exceptions.h>
@@ -928,6 +929,9 @@ __bad_area_nosemaphore(struct pt_regs *regs, unsigned long error_code,sanitize_error_code (address, &error_code); +if (fixup_vdso_exception (regs,X86_TRAP_PF , error_code, address)) +return ; +if (likely (show_unhandled_signals))show_signal_msg (regs, error_code, address, tsk);
@@ -1047,6 +1051,9 @@ do_sigbus(struct pt_regs *regs, unsigned long error_code, unsigned long address,sanitize_error_code (address, &error_code); +if (fixup_vdso_exception (regs,X86_TRAP_PF , error_code, address)) +return ; +set_signal_archinfo (address, error_code);#ifdef CONFIG_MEMORY_FAILURE
Signature · 1 line
2.19.2
RFC PATCH v4 4/5 x86/traps: Attempt to fixup exceptions in vDSO before signaling
In reply to Sean ChristophersonCall fixup_vdso_exception() in all trap flows that generate signals to userspace immediately prior to generating any such signal. If the exception is fixed, return cleanly and do not generate a signal.
The goal of vDSO fixup is not to fixup all faults, nor is it to avoid all signals, but rather to report faults directly to userspace when the fault would otherwise directly result in a signal being sent to the process.
--- arch/x86/kernel/traps.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+)
arch/x86/kernel/traps.c +14 -0
@@ -61,6 +61,7 @@#include <asm/mpx.h> #include <asm/vm86.h> #include <asm/umip.h> +#include <asm/vdso.h> #ifdef CONFIG_X86_64 #include <asm/x86_init.h>
@@ -209,6 +210,9 @@ do_trap_no_signal(struct task_struct *tsk, int trapnr, const char *str, tsk->thread.error_code = error_code; tsk->thread.trap_nr = trapnr;die (str, regs, error_code); + }else { +if (fixup_vdso_exception (regs, trapnr, error_code,0 )) +return 0 ; }/*
@@ -560,6 +564,9 @@ do_general_protection(struct pt_regs *regs, long error_code)return ; } +if (fixup_vdso_exception (regs,X86_TRAP_GP , error_code,0 )) +return ; + tsk->thread.error_code = error_code; tsk->thread.trap_nr =X86_TRAP_GP ;
@@ -774,6 +781,10 @@ dotraplinkage void do_debug(struct pt_regs *regs, long error_code) SIGTRAP) == NOTIFY_STOP) goto exit; +if (user_mode (regs) && +fixup_vdso_exception (regs,X86_TRAP_DB , error_code,0 )) + goto exit; +/* * Let others (NMI) know that the debug stack is in use * as we may switch to the interrupt stack.
@@ -854,6 +865,9 @@ static void math_error(struct pt_regs *regs, int error_code, int trapnr)if (!si_code)return ; +if (fixup_vdso_exception (regs, trapnr, error_code,0 )) +return ; +force_sig_fault (SIGFPE , si_code, (void __user *)uprobe_get_trap_addr (regs), task); }
Signature · 1 line
2.19.2
RFC PATCH v4 5/5 x86/vdso: Add __vdso_sgx_enter_enclave() to wrap SGX enclave transitions
In reply to Sean ChristophersonIntel Software Guard Extensions (SGX) SGX introduces a new CPL3-only enclave mode that runs as a sort of black box shared object that is hosted by an untrusted normal CPL3 process.
Enclave transitions have semantics that are a lovely blend of SYCALL, SYSRET and VM-Exit. In a non-faulting scenario, entering and exiting an enclave can only be done through SGX-specific instructions, EENTER and EEXIT respectively. EENTER+EEXIT is analogous to SYSCALL+SYSRET, e.g. EENTER/SYSCALL load RCX with the next RIP and EEXIT/SYSRET load RIP from R{B,C}X.
But in a faulting/interrupting scenario, enclave transitions act more like VM-Exit and VMRESUME. Maintaining the black box nature of the enclave means that hardware must automatically switch CPU context when an Asynchronous Exiting Event (AEE) occurs, an AEE being any interrupt or exception (exceptions are AEEs because asynchronous in this context is relative to the enclave and not CPU execution, e.g. the enclave doesn't get an opportunity to save/fuzz CPU state).
Like VM-Exits, all AEEs jump to a common location, referred to as the Asynchronous Exiting Point (AEP). The AEP is specified at enclave entry via register passed to EENTER/ERESUME, similar to how the hypervisor specifies the VM-Exit point (via VMCS.HOST_RIP at VMLAUNCH/VMRESUME). Resuming the enclave/VM after the exiting event is handled is done via ERESUME/VMRESUME respectively. In SGX, AEEs that are handled by the kernel, e.g. INTR, NMI and most page faults, IRET will journey back to the AEP which then ERESUMEs th enclave.
Enclaves also behave a bit like VMs in the sense that they can generate exceptions as part of their normal operation that for all intents and purposes need to handled in the enclave/VM. However, unlike VMX, SGX doesn't allow the host to modify its guest's, a.k.a. enclave's, state, as doing so would circumvent the enclave's security. So to handle an exception, the enclave must first be re-entered through the normal EENTER flow (SYSCALL/SYSRET behavior), and then resumed via ERESUME (VMRESUME behavior) after the source of the exception is resolved.
All of the above is just the tip of the iceberg when it comes to running an enclave. But, SGX was designed in such a way that the host process can utilize a library to build, launch and run an enclave. This is roughly analogous to how e.g. libc implementations are used by most applications so that the application can focus on its business logic.
The big gotcha is that because enclaves can generate *and* handle exceptions, any SGX library must be prepared to handle nearly any exception at any time (well, any time a thread is executing in an enclave). In Linux, this means the SGX library must register a signal handler in order to intercept relevant exceptions and forward them to the enclave (or in some cases, take action on behalf of the enclave). Unfortunately, Linux's signal mechanism doesn't mesh well with libraries, e.g. signal handlers are process wide, are difficult to chain, etc... This becomes particularly nasty when using multiple levels of libraries that register signal handlers, e.g. running an enclave via cgo inside of the Go runtime.
In comes vDSO to save the day. Now that vDSO can fixup exceptions, add a function, __vdso_sgx_enter_enclave(), to wrap enclave transitions and intercept any exceptions that occur when running the enclave.
__vdso_sgx_enter_enclave() accepts four parameters:
- The ENCLU leaf to execute (must be EENTER or ERESUME).
- A pointer to a Thread Control Structure (TCS). A TCS is a page within the enclave that defines/tracks the context of an enclave thread.
- An optional 'struct sgx_enclave_regs' pointer. If defined, the corresponding registers are loaded prior to entering the enclave and saved after (cleanly) exiting the enclave.
The effective enclave register ABI follows the kernel x86-64 ABI. The x86-64 userspace ABI is not used due to RCX being usurped by hardware to pass the return RIP to the enclave.
- An optional 'struct sgx_enclave_exception' pointer. If provided, the struct is filled with the faulting ENCLU leaf, trapnr, error code and address if an unhandled exception occurs on ENCLU or in the enclave. An unhandled exception is an exception that would normally be delivered to userspace via a signal, e.g. SIGSEGV.
Note that this means that not all enclave exits are reported to the caller, e.g. interrupts and faults that are handled by the kernel do not trigger fixup and IRET back to ENCLU[ERESUME], i.e. unconditionally resume the enclave.
--- arch/x86/entry/vdso/Makefile | 2 + arch/x86/entry/vdso/vdso.lds.S | 1 + arch/x86/entry/vdso/vsgx_enter_enclave.S | 136 +++++++++++++++++++++++ arch/x86/include/uapi/asm/sgx.h | 44 ++++++++ 4 files changed, 183 insertions(+) create mode 100644 arch/x86/entry/vdso/vsgx_enter_enclave.S
arch/x86/entry/vdso/Makefile +2 -0
@@ -18,6 +18,7 @@ VDSO32-$(CONFIG_IA32_EMULATION) := y # files to link into the vdso vobjs-y := vdso-note.o vclock_gettime.o vgetcpu.o +vobjs-$(VDSO64-y) += vsgx_enter_enclave.o # files to link into kernel obj-y += vma.o extable.o
@@ -85,6 +86,7 @@ CFLAGS_REMOVE_vdso-note.o = -pg CFLAGS_REMOVE_vclock_gettime.o = -pg CFLAGS_REMOVE_vgetcpu.o = -pg CFLAGS_REMOVE_vvar.o = -pg +CFLAGS_REMOVE_vsgx_enter_enclave.o = -pg # # X32 processes use x32 vDSO to access 64bit kernel data.
arch/x86/entry/vdso/vdso.lds.S +1 -0
@@ -25,6 +25,7 @@ VERSION { __vdso_getcpu; time; __vdso_time; + __vdso_sgx_enter_enclave; local: *; }; }
arch/x86/entry/vdso/vsgx_enter_enclave.S new file +136 -0
136 lines not on this page · raw message
arch/x86/include/uapi/asm/sgx.h +44 -0
48 lines not on this page · raw message
Signature · 1 line
2.19.2
Re: [RFC PATCH v4 5/5] x86/vdso: Add __vdso_sgx_enter_enclave() to wrap SGX enclave transitions
arch/x86/include/uapi/asm/sgx.h struct sgx_enclave_modify_pages { · line 117
Quoted 8 lines · Sean Christopherson
arch/x86/include/uapi/asm/sgx.h +8
-0+struct sgx_enclave_regs { +__u64 rdi; +__u64 rsi; +__u64 rdx; +__u64 r8; +__u64 r9; +__u64 r10; +};
This is fine, but why not just cover all 13 normal registers that are not used by SGX?
Minor comments below.
Quoted 2 lines
+/**
+ * structsgx_enclave_exception- structure to pass register in/out of enclave
Typo in struct name.
Quoted 10 lines
+ * by way of
__vdso_sgx_enter_enclave
+ *
+ * @rdi: value of %rdi, loaded/saved on enter/exit
+ * @rsi: value of %rsi, loaded/saved on enter/exit
+ * @rdx: value of %rdx, loaded/saved on enter/exit
+ * @r8: value of %r8, loaded/saved on enter/exit
+ * @r9: value of %r9, loaded/saved on enter/exit
+ * @r10: value of %r10, loaded/saved on enter/exit
+ */
arch/x86/entry/vdso/vsgx_enter_enclave.S line 77
Quoted 4 lines
arch/x86/entry/vdso/vsgx_enter_enclave.S new file +4
-0+ /* load leaf, TCS and AEP for ENCLU */ + mov %edi, %eax + mov %rsi, %rbx + lea 1f(%rip), %rcx
If you move this below the jump, you can use %rcx for @regs
Quoted 19 lines
arch/x86/entry/vdso/vsgx_enter_enclave.S new file +19
-0+ + /* optionally copy @regs to registers */ + test %rdx, %rdx + je 1f + + mov %rdx, %r11 + mov RDI(%r11), %rdi + mov RSI(%r11), %rsi + mov RDX(%r11), %rdx + mov R8(%r11), %r8 + mov R9(%r11), %r9 + mov R10(%r11), %r10 + +1: enclu + + /* ret = 0 */ + xor %eax, %eax + + /* optionally copy registers to @regs */
arch/x86/entry/vdso/vsgx_enter_enclave.S line 106
Quoted 10 lines
arch/x86/entry/vdso/vsgx_enter_enclave.S new file +10
-0+ mov -0x8(%rsp), %r11 + test %r11, %r11 + je 2f + + mov %rdi, RDI(%r11) + mov %rsi, RSI(%r11) + mov %rdx, RDX(%r11) + mov %r8, R8(%r11) + mov %r9, R9(%r11) + mov %r10, R10(%r11)
Here you can use %rax for @regs and clear it at the end.
arch/x86/entry/vdso/vsgx_enter_enclave.S line 115
Quoted 7 lines
+2: pop %rbx
arch/x86/entry/vdso/vsgx_enter_enclave.S new file +6
-0+ pop %r12 + pop %r13 + pop %r14 + pop %r15 + pop %rbp + ret
x86-64 ABI requires that you call CLD here (enclave may set it).
--
Jethro Beekman | Fortanix
Re: [RFC PATCH v4 5/5] x86/vdso: Add __vdso_sgx_enter_enclave() to wrap SGX enclave transitions
Quoted 12 lines · Jethro Beekman
arch/x86/include/uapi/asm/sgx.h struct sgx_enclave_modify_pages { · line 117
Quoted 8 lines · Sean Christopherson
arch/x86/include/uapi/asm/sgx.h +8
-0+struct sgx_enclave_regs { +__u64 rdi; +__u64 rsi; +__u64 rdx; +__u64 r8; +__u64 r9; +__u64 r10; +};This is fine, but why not just cover all 13 normal registers that are not used by SGX?
Trying to balance flexibility/usability with unecessary overhead. And I think this ABI meshes well with the idea of requiring the enclave to be compliant with the x86-64 ABI (see below).
Quoted 6 lines
Minor comments below.
Quoted 2 lines
+/**
+ * structsgx_enclave_exception- structure to pass register in/out of enclaveTypo in struct name.
Doh, thanks.
Quoted 10 lines
Quoted 9 lines
+ * by way of
__vdso_sgx_enter_enclave
+ *
+ * @rdi: value of %rdi, loaded/saved on enter/exit
+ * @rsi: value of %rsi, loaded/saved on enter/exit
+ * @rdx: value of %rdx, loaded/saved on enter/exit
+ * @r8: value of %r8, loaded/saved on enter/exit
+ * @r9: value of %r9, loaded/saved on enter/exit
+ * @r10: value of %r10, loaded/saved on enter/exit
+ */
Quoted 6 lines
arch/x86/entry/vdso/vsgx_enter_enclave.S line 77
Quoted 4 lines
arch/x86/entry/vdso/vsgx_enter_enclave.S new file +4
-0+ /* load leaf, TCS and AEP for ENCLU */ + mov %edi, %eax + mov %rsi, %rbx + lea 1f(%rip), %rcxIf you move this below the jump, you can use %rcx for @regs
EDI needs to be moved to EAX before it is potentially overwritten below and I wanted the loading of the three registers used by hardware grouped together. And IMO using the same register for accessing the structs in all flows improves readability.
Quoted 23 lines
Quoted 23 lines
arch/x86/entry/vdso/vsgx_enter_enclave.S new file +23
-0+ + /* optionally copy @regs to registers */ + test %rdx, %rdx + je 1f + + mov %rdx, %r11 + mov RDI(%r11), %rdi + mov RSI(%r11), %rsi + mov RDX(%r11), %rdx + mov R8(%r11), %r8 + mov R9(%r11), %r9 + mov R10(%r11), %r10 + +1: enclu + + /* ret = 0 */ + xor %eax, %eax + + /* optionally copy registers to @regs */ + mov -0x8(%rsp), %r11 + test %r11, %r11 + je 2f +
Quoted 8 lines
arch/x86/entry/vdso/vsgx_enter_enclave.S line 106
Quoted 6 lines
arch/x86/entry/vdso/vsgx_enter_enclave.S new file +6
-0+ mov %rdi, RDI(%r11) + mov %rsi, RSI(%r11) + mov %rdx, RDX(%r11) + mov %r8, R8(%r11) + mov %r9, R9(%r11) + mov %r10, R10(%r11)Here you can use %rax for @regs and clear it at the end.
Clearing RAX early avoids the use of another label, though obviously that's not exactly critical. The comment about using the same register for accessing structs applies here as well.
Quoted 9 lines
arch/x86/entry/vdso/vsgx_enter_enclave.S line 115
Quoted 7 lines
+2: pop %rbx
arch/x86/entry/vdso/vsgx_enter_enclave.S new file +6
-0+ pop %r12 + pop %r13 + pop %r14 + pop %r15 + pop %rbp + retx86-64 ABI requires that you call CLD here (enclave may set it).
Ugh. Technically MXCSR and the x87 CW also need to be preserved.
What if rather than treating the enclave as hostile we require it to be compliant with the x86-64 ABI like any other function? That would solve the EFLAGS.DF, MXCSR and x87 issues without adding unnecessary overhead. And we wouldn't have to save/restore R12-R15. It'd mean we couldn't use the stack's red zone to hold @regs and @e, but that's poor form anyways.
Quoted 4 lines
--
Jethro Beekman | Fortanix
Re: [RFC PATCH v4 5/5] x86/vdso: Add __vdso_sgx_enter_enclave() to wrap SGX enclave transitions
Quoted 12 lines · Sean Christopherson
Quoted 10 lines · Jethro Beekman
arch/x86/entry/vdso/vsgx_enter_enclave.S line 115
Quoted 7 lines · Sean Christopherson
+2: pop %rbx
arch/x86/entry/vdso/vsgx_enter_enclave.S new file +6
-0+ pop %r12 + pop %r13 + pop %r14 + pop %r15 + pop %rbp + retx86-64 ABI requires that you call CLD here (enclave may set it).
Quoted 7 lines
Ugh. Technically MXCSR and the x87 CW also need to be preserved.
What if rather than treating the enclave as hostile we require it to be compliant with the x86-64 ABI like any other function? That would solve the EFLAGS.DF, MXCSR and x87 issues without adding unnecessary overhead. And we wouldn't have to save/restore R12-R15. It'd mean we couldn't use the stack's red zone to hold @regs and @e, but that's poor form anyways.
Grr, except the processor crushes R12-R15, FCW and MXCSR on asynchronous exits. But not EFLAGS.DF, that's real helpful.
Re: [RFC PATCH v4 5/5] x86/vdso: Add __vdso_sgx_enter_enclave() to wrap SGX enclave transitions
Quoted 15 lines · Sean Christopherson
Quoted 14 lines · Sean Christopherson
Quoted 10 lines · Jethro Beekman
arch/x86/entry/vdso/vsgx_enter_enclave.S line 115
Quoted 7 lines · Sean Christopherson
+2: pop %rbx
arch/x86/entry/vdso/vsgx_enter_enclave.S new file +6
-0+ pop %r12 + pop %r13 + pop %r14 + pop %r15 + pop %rbp + retx86-64 ABI requires that you call CLD here (enclave may set it).
Ugh. Technically MXCSR and the x87 CW also need to be preserved.
Quoted 8 lines
Quoted 5 lines
What if rather than treating the enclave as hostile we require it to be compliant with the x86-64 ABI like any other function? That would solve the EFLAGS.DF, MXCSR and x87 issues without adding unnecessary overhead. And we wouldn't have to save/restore R12-R15. It'd mean we couldn't use the stack's red zone to hold @regs and @e, but that's poor form anyways.
Grr, except the processor crushes R12-R15, FCW and MXCSR on asynchronous exits. But not EFLAGS.DF, that's real helpful.
I can think of three options that are at least somewhat reasonable:
1) Save/restore MXCSR and FCW
+ 100% compliant with the x86-64 ABI
+ Callable from any code
+ Minimal documentation required
- Restoring MXCSR/FCW is likely unnecessary 99% of the time
- Slow
2) Clear EFLAGS.DF but not save/restore MXCSR and FCW
+ Mostly compliant with the x86-64 ABI
+ Callable from any code that doesn't use SIMD registers
- Need to document deviations from x86-64 ABI
3) Require the caller to save/restore everything.
+ Fast
+ Userspace can pass all GPRs to the enclave (minus EAX, RBX and RCX)
- Completely custom ABI
- For all intents and purposes must be called from an assembly wrapper
Option (3) actually isn't all that awful. RCX can be used to pass an optional pointer to a 'struct sgx_enclave_exception' and we can still return standard error codes, e.g. -EFAULT.
E.g.:
/**
* __vdso_sgx_enter_enclave() - Enter an SGX enclave *
* %eax: ENCLU leaf, must be EENTER or ERESUME
* %rbx: TCS, must be non-NULL
* %rcx: Optional pointer to 'struct sgx_enclave_exception'
*
* Return:
* 0 on a clean entry/exit to/from the enclave * -EINVAL if ENCLU leaf is not allowed or if TCS is NULL * -EFAULT if ENCLU or the enclave faults */ ENTRY(__vdso_sgx_enter_enclave) /* EENTER <= leaf <= ERESUME */ cmp $0x2, %eax jb bad_input cmp $0x3, %eax ja bad_input /* TCS must be non-NULL */ test %rbx, %rbx je bad_input /* save @exception pointer */ push %rcx /* load leaf, TCS and AEP for ENCLU */ lea 1f(%rip), %rcx
1: enclu
add 0x8, %rsp xor %eax, %eax ret
bad_input:
mov $(-EINVAL), %rax ret
.pushsection .fixup, "ax"
2: pop %rcx
test %rcx, %rcx je 3f mov %eax, EX_LEAF(%rcx) mov %di, EX_TRAPNR(%rcx) mov %si, EX_ERROR_CODE(%rcx) mov %rdx, EX_ADDRESS(%rcx)
3: mov $(-EFAULT), %rax
ret
.popsection
_ASM_VDSO_EXTABLE_HANDLE(1b, 3b)
ENDPROC(__vdso_sgx_enter_enclave)
Re: [RFC PATCH v4 5/5] x86/vdso: Add __vdso_sgx_enter_enclave() to wrap SGX enclave transitions
Quoted 49 lines · Sean Christopherson
Quoted 15 lines · Sean Christopherson
Quoted 14 lines · Sean Christopherson
Quoted 10 lines · Jethro Beekman
arch/x86/entry/vdso/vsgx_enter_enclave.S line 115
Quoted 7 lines · Sean Christopherson
+2: pop %rbx
arch/x86/entry/vdso/vsgx_enter_enclave.S new file +6
-0+ pop %r12 + pop %r13 + pop %r14 + pop %r15 + pop %rbp + retx86-64 ABI requires that you call CLD here (enclave may set it).
Ugh. Technically MXCSR and the x87 CW also need to be preserved.
Quoted 8 lines
Quoted 5 lines
What if rather than treating the enclave as hostile we require it to be compliant with the x86-64 ABI like any other function? That would solve the EFLAGS.DF, MXCSR and x87 issues without adding unnecessary overhead. And we wouldn't have to save/restore R12-R15. It'd mean we couldn't use the stack's red zone to hold @regs and @e, but that's poor form anyways.
Grr, except the processor crushes R12-R15, FCW and MXCSR on asynchronous exits. But not EFLAGS.DF, that's real helpful.
I can think of three options that are at least somewhat reasonable:
1) Save/restore MXCSR and FCW
+ 100% compliant with the x86-64 ABI
+ Callable from any code
+ Minimal documentation required
- Restoring MXCSR/FCW is likely unnecessary 99% of the time
- Slow2) Clear EFLAGS.DF but not save/restore MXCSR and FCW
+ Mostly compliant with the x86-64 ABI
+ Callable from any code that doesn't use SIMD registers
- Need to document deviations from x86-64 ABI3) Require the caller to save/restore everything.
+ Fast
+ Userspace can pass all GPRs to the enclave (minus EAX, RBX and RCX)
- Completely custom ABI
- For all intents and purposes must be called from an assembly wrapper
Quoted 3 lines
Option (3) actually isn't all that awful. RCX can be used to pass an optional pointer to a 'struct
sgx_enclave_exception' and we can still return standard error codes, e.g.-EFAULT.
Entering and exiting a syscall requires an assembly wrapper, and that doesn't seem completely unreasonable. It's an easy bit of inline assembly.
Re: [RFC PATCH v4 5/5] x86/vdso: Add __vdso_sgx_enter_enclave() to wrap SGX enclave transitions
Quoted 50 lines · Josh Triplett
Quoted 49 lines · Sean Christopherson
Quoted 15 lines · Sean Christopherson
Quoted 14 lines · Sean Christopherson
Quoted 10 lines · Jethro Beekman
arch/x86/entry/vdso/vsgx_enter_enclave.S line 115
Quoted 7 lines · Sean Christopherson
+2: pop %rbx
arch/x86/entry/vdso/vsgx_enter_enclave.S new file +6
-0+ pop %r12 + pop %r13 + pop %r14 + pop %r15 + pop %rbp + retx86-64 ABI requires that you call CLD here (enclave may set it).
Ugh. Technically MXCSR and the x87 CW also need to be preserved.
Quoted 8 lines
Quoted 5 lines
What if rather than treating the enclave as hostile we require it to be compliant with the x86-64 ABI like any other function? That would solve the EFLAGS.DF, MXCSR and x87 issues without adding unnecessary overhead. And we wouldn't have to save/restore R12-R15. It'd mean we couldn't use the stack's red zone to hold @regs and @e, but that's poor form anyways.
Grr, except the processor crushes R12-R15, FCW and MXCSR on asynchronous exits. But not EFLAGS.DF, that's real helpful.
I can think of three options that are at least somewhat reasonable:
1) Save/restore MXCSR and FCW
+ 100% compliant with the x86-64 ABI
+ Callable from any code
+ Minimal documentation required
- Restoring MXCSR/FCW is likely unnecessary 99% of the time
- Slow2) Clear EFLAGS.DF but not save/restore MXCSR and FCW
+ Mostly compliant with the x86-64 ABI
+ Callable from any code that doesn't use SIMD registers
- Need to document deviations from x86-64 ABI3) Require the caller to save/restore everything.
+ Fast
+ Userspace can pass all GPRs to the enclave (minus EAX, RBX and RCX)
- Completely custom ABI
- For all intents and purposes must be called from an assembly wrapper
Quoted 7 lines
Quoted 3 lines
Option (3) actually isn't all that awful. RCX can be used to pass an optional pointer to a 'struct
sgx_enclave_exception' and we can still return standard error codes, e.g.-EFAULT.Entering and exiting a syscall requires an assembly wrapper, and that doesn't seem completely unreasonable. It's an easy bit of inline assembly.
The code I posted had a few typos (stupid AT&T syntax), but with those fixed the idea checks out.
My initial reaction to a barebones ABI was that it would be a "documentation nightmare", but it's not too bad if it returns actual error codes and fills in a struct on exceptions instead of stuffing registers. And with the MXCSR/FCW issues it might actually be less documentation in the long run since we can simply say that all state is the caller's responsibility.
I *really* like that we basically eliminate bikeshedding on which GPRs to pass to/from the enclave.
Re: [RFC PATCH v4 5/5] x86/vdso: Add __vdso_sgx_enter_enclave() to wrap SGX enclave transitions
In reply to Sean ChristophersonQuoted 47 lines · Sean Christopherson
Quoted 17 lines · Sean Christopherson
Quoted 15 lines · Sean Christopherson
Quoted 11 lines · Jethro Beekman
arch/x86/entry/vdso/vsgx_enter_enclave.S line 115
Quoted 8 lines · Sean Christopherson
+2: pop %rbx
arch/x86/entry/vdso/vsgx_enter_enclave.S new file +6
-0+ pop %r12 + pop %r13 + pop %r14 + pop %r15 + pop %rbp + retx86-64 ABI requires that you call CLD here (enclave may set it).
Ugh. Technically MXCSR and the x87 CW also need to be preserved.
Quoted 8 lines
Quoted 5 lines
What if rather than treating the enclave as hostile we require it to be compliant with the x86-64 ABI like any other function? That would solve the EFLAGS.DF, MXCSR and x87 issues without adding unnecessary overhead. And we wouldn't have to save/restore R12-R15. It'd mean we couldn't use the stack's red zone to hold @regs and @e, but that's poor form anyways.
Grr, except the processor crushes R12-R15, FCW and MXCSR on asynchronous exits. But not EFLAGS.DF, that's real helpful.
I can think of three options that are at least somewhat reasonable:
1) Save/restore MXCSR and FCW
+ 100% compliant with the x86-64 ABI
+ Callable from any code
+ Minimal documentation required
- Restoring MXCSR/FCW is likely unnecessary 99% of the time
- Slow2) Clear EFLAGS.DF but not save/restore MXCSR and FCW
+ Mostly compliant with the x86-64 ABI
+ Callable from any code that doesn't use SIMD registers
- Need to document deviations from x86-64 ABI3) Require the caller to save/restore everything.
Quoted 8 lines
+ Fast
+ Userspace can pass all GPRs to the enclave (minus EAX, RBX and RCX)
- Completely custom ABI
- For all intents and purposes must be called from an assembly wrapperOption (3) actually isn't all that awful. RCX can be used to pass an optional pointer to a 'struct
sgx_enclave_exception' and we can still return standard error codes, e.g.-EFAULT.
I like 3, but:
Quoted 47 lines
E.g.:
/**
*__vdso_sgx_enter_enclave()- Enter an SGX enclave ** %eax: ENCLU leaf, must be EENTER or ERESUME * %rbx: TCS, must be non-NULL * %rcx: Optional pointer to 'struct sgx_enclave_exception' * * Return: * 0 on a clean entry/exit to/from the enclave * -EINVAL if ENCLU leaf is not allowed or if TCS is NULL * -EFAULT if ENCLU or the enclave faults*/
ENTRY(__vdso_sgx_enter_enclave)
/* EENTER <= leaf <= ERESUME */
cmp $0x2, %eax
jbbad_inputcmp $0x3, %eax ja bad_input /* TCS must be non-NULL */ test %rbx, %rbx je bad_input /* save @exception pointer */ push %rcx /* load leaf, TCS and AEP for ENCLU */ lea 1f(%rip), %rcx1: enclu
add
0x8, %rsp
xor %eax, %eax
ret
bad_input:mov $(-EINVAL), %rax ret.pushsection .fixup, "ax"
2: pop %rcx
test %rcx, %rcx
je 3f
Quoted 6 lines
mov %eax, EX_LEAF(%rcx) mov %di, EX_TRAPNR(%rcx) mov %si, EX_ERROR_CODE(%rcx) mov %rdx, EX_ADDRESS(%rcx)3: mov $(-EFAULT), %rax
ret
I’m not totally sold on -EFAULT as the error code. That usually indicates a bad pointer. I’m not sure I have a better suggestion.
Quoted 5 lines
.popsection
_ASM_VDSO_EXTABLE_HANDLE(1b, 3b)
ENDPROC(__vdso_sgx_enter_enclave)
Re: [RFC PATCH v4 5/5] x86/vdso: Add __vdso_sgx_enter_enclave() to wrap SGX enclave transitions
Quoted 15 lines · Andy Lutomirski
Quoted 8 lines · Sean Christopherson
.pushsection .fixup, "ax"
2: pop %rcx
test %rcx, %rcx
je 3fQuoted 6 lines
mov %eax, EX_LEAF(%rcx) mov %di, EX_TRAPNR(%rcx) mov %si, EX_ERROR_CODE(%rcx) mov %rdx, EX_ADDRESS(%rcx)3: mov $(-EFAULT), %rax
ret
Quoted 2 lines
I’m not totally sold on
-EFAULTas the error code. That usually indicates a bad pointer. I’m not sure I have a better suggestion.
Hmm, one idea would be to return positive signal numbers, e.g. SIGILL for #UD. I don't like that approach though as it adds a fair amount of code to the fixup handler for dubious value, e.g. userspace would still need to check the exception error code to determine if the EPC is lost. And we'd have to update the vDSO if a new exception and/or signal was added, e.g. #CP for CET.
Encapsulating "you faulted" in a single error code seems cleaner for both kernel and userspace code, and -EFAULT makes that pretty obvious even though we're bastardizing its meaning a bit.
In general, I'd prefer to return only 0 or negative values so that userspace can easily merge in their own (positive value) error codes from the enclave, e.g. in the vDSO wrapper:
/* Enclave's return value is in RDI, overwrite RAX on success */
test %rax, %rax
cmove %rdi, %rax
ret