SGX中,OCALL和Enclave正常退出一样通过EEXIT退出Enclave。
Enclave正常退出Routine:
【sgx_ocall(tRTS)】->【do_ocall(tRTS)】->【__morestack(tRTS, AS)】->【do_ocall(tRTS,AS)】->【__morestack(uRTS, AS)】->【.Leret(uRTS, AS)】->退出__morestack(uRTS, AS)
OCALL执行Routine:
【sgx_ocall(tRTS)】->【do_ocall(tRTS)】->【__morestack(tRTS, AS)】->【do_ocall(tRTS,AS)】->【__morestack(uRTS, AS)】->【stack_sticker(uRTS, AS)】->【sgx_ocall(uRTS)】->【CEnclave::ocall(uRTS)】->【do_ocall(uRTS)】
下面具体展开描述
sgx_ocall(tRTS)
//
// sgx_ocall
// Parameters:
// index - the index in the ocall table
// ms - the mashalling structure
// Return Value:
// OCALL status
//
sgx_status_t sgx_ocall(const unsigned int index, void *ms)
do_ocall(tRTS)
#define do_ocall __morestack
__morestack(tRTS, AS)
/*
* ------------------------------------------------------------------
* this function is the wrapper of do_ocall, which is used to
* stick ocall bridge and proxy frame together
* ------------------------------------------------------------------
*/
DECLARE_LOCAL_FUNC __morestack
do_ocall(tRTS,AS)
/*
* -------------------------------------------------------------------------
* sgx_status_t do_ocall(unsigned int index, void *ms);
*
* Function: do_ocall
* The entry point of the enclave
* Parameters:
* func_addr - target function address
* ms - marshalling structure
*
* Stack: (same as do_oret)
* bottom of stack ->
* -----------------
* | ECALL/OCALL |
* previous TD.last_sp -> | frames |
* -----------------
* | ECALL frame |
* | do_ocall param 2| 3
* | do_ocall param 1| 2
* |do_ocall ret_addr| 1
* | xbp | 0 + xbp
* | .... |
* | xsave buffer |
* | .... |
* | xsave pointer | 19
* | ocall_depth | 18
* | reserved | 17
* | reserved | 16
* | reserved | 15
* | rbx | 14
* | rsi | 13
* | rdi | 12
* | rbp | 11
* | r12 | 10
* | r13 | 9
* | r14 | 8
* | r15 | 7
* | prev TD.last_sp | 6
* | ocall_index | 5
* | OCALL FLAG | 4
* | shadow | 3
* | shadow | 2
* | shadow | 1
* TD.last_sp -> | shadow | 0 + xsp
* -----------------
* -------------------------------------------------------------------------
*/
DECLARE_LOCAL_FUNC do_ocall
压栈内容主要是ocall_context_t结构体。
typedef struct _ocall_context_t
{
uintptr_t shadow0;
uintptr_t shadow1;
uintptr_t shadow2;
uintptr_t shadow3;
uintptr_t ocall_flag;
uintptr_t ocall_index;
uintptr_t pre_last_sp;
uintptr_t r15;
uintptr_t r14;
uintptr_t r13;
uintptr_t r12;
uintptr_t xbp;
uintptr_t xdi;
uintptr_t xsi;
uintptr_t xbx;
uintptr_t reserved[3];
uintptr_t ocall_depth;
uintptr_t ocall_ret;
} ocall_context_t;
上下文存到栈上,X属性存在xsave buffer,从SYNTHETIC_STATE恢复X属性供Enclave外使用,xdi、xsi沿用原来的,代表了传参。更新last_sp。然后从Thread Data恢复Enclave外bp、sp。从上一个last_sp(如下所示)找到ret_addr存到rbx,将SE_EEXIT存到rax。此外,对各种通用寄存器进行清零。调用EEXIT退出Enclave。
/*
* set EEXIT registers
* return address can be read from the ECALL frame:
* TD.last_sp ->
* -------------
* | ret_addr |
* | xbp_u |
* | xsp_u |
* | ... |
*/
注意:压栈的内容都位于Enclave线程栈中,物理上处于受到加密保护的EPC中,压栈目的在于OCALL返回时恢复Enclave内上下文。OCALL时,ocall_index和ms(marshalling parameter)地址分别通过rdi、rsi寄存器传参。ms地址指向位于Enclave外不可信栈上的ms内容,后者事先通过sgx_ocalloc函数分配空间并保存。因此Enclave线程栈上的密文内容,外界无法读取,而被寄存器传递的明文可以被外界读取。
__morestack(uRTS, AS)
然后又回到不可信世界【__morestack(uRTS)】,而且退出Enclave后的位置是一开始进入Enclave时候的ENCLU指令的下一条。
/*
* at this point, we may have returned due to a normal EEXIT,
* or we may have returned due to an OCALL. We differentiate
* by popping the top of the stack. If it is not OCMD_ERET, we have
* an untrusted bridge to call at that address.
*/
/* We have an ocall. Call our bridge function. */
通过OCMD_ERET判断是否正常退出Enclave,如果是,那么直接返回【.Leret】。不然,就需要执行OCALL代码了。
.Ldo_ocall:
/* call ocall
* int ocall(const unsigned int proc, void *ocall_table, const void *ms, CEnclave *enclave);
*
* When EEXIT'ed from tRTS due to an OCALL,
* - `rdi' holds OCALL index
*- `rsi' holds the pointer to marshalling structure
*/
完善参数信息,调用【stack_sticker(uRTS, AS)】
stack_sticker(uRTS, AS)
/*
* function stack_sticker is the wrapper of ocall,
* before call ocall, update the ret address and frame pointer (BP) on the stack
*
* Stack before:
* |__morestack stack |<--|
* ------------- |
* |return adress | |
* xbp -> | caller xbp | --|
* | |
* xsp -> | |
* -------------
*
* Stack after:
* |__morestack stack |
* ------------------
* | __morestack(inside)|
* xbp -> | xbp_t | ---->the frame point of __morestack
* | |
* xsp -> | |
* | <ecall> |
* ------------------
* int stack_sticker(unsigned int proc, sgx_ocall_table_t *ocall_table, void *ms, CTrustThread *trust_thread, tcs_t *tcs)
*/
DECLARE_GLOBAL_FUNC stack_sticker
我们调整返回地址和栈帧 (BP),trust_thread压入OCALL帧信息(如果当前在调试模式下,还将Enclave外看不到的Enclave内调用栈更新到Enclave外调用栈上),然后调用【sgx_ocall(uRTS)】。
sgx_ocall(uRTS)
extern "C" int sgx_ocall(const unsigned int proc, const sgx_ocall_table_t *ocall_table, void *ms, CTrustThread *trust_thread)
{
...
return enclave->ocall(proc, ocall_table, ms);
}
CEnclave::ocall(uRTS)
int CEnclave::ocall(const unsigned int proc, const sgx_ocall_table_t *ocall_table, void *ms)
此时我们是常规的OCALL,不是特殊的OCALL,进而【do_ocall(uRTS)】。
do_ocall(uRTS)
int do_ocall(const bridge_fn_t bridge, void *ms)
{
...
error = bridge(ms);
...
}
执行OCALL
OCALL执行完毕
trust_thread弹出OCALL帧信息,逐级返回到【__morestack(uRTS, AS)】,并重新进入到Enclave,此时进入Enclave的目的编号是【ECMD_ORET】。