mirror of
https://github.com/intel/linux-sgx
synced 2026-06-08 14:49:32 +00:00
b7838ae15b
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Signed-off-by: Scott Constable <scott.d.constable@intel.com>
707 lines
25 KiB
C++
707 lines
25 KiB
C++
/*
|
|
* Copyright (C) 2011-2021 Intel Corporation. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in
|
|
* the documentation and/or other materials provided with the
|
|
* distribution.
|
|
* * Neither the name of Intel Corporation nor the names of its
|
|
* contributors may be used to endorse or promote products derived
|
|
* from this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
*/
|
|
|
|
|
|
/**
|
|
* File: trts_veh.cpp
|
|
* Description:
|
|
* This file implements the support of custom exception handling.
|
|
*/
|
|
|
|
#include "sgx_trts_exception.h"
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include "sgx_trts.h"
|
|
#include "xsave.h"
|
|
#include "arch.h"
|
|
#include "sgx_spinlock.h"
|
|
#include "thread_data.h"
|
|
#include "global_data.h"
|
|
#include "trts_internal.h"
|
|
#include "trts_mitigation.h"
|
|
#include "trts_inst.h"
|
|
#include "util.h"
|
|
#include "trts_util.h"
|
|
#include "trts_shared_constants.h"
|
|
#include "se_cdefs.h"
|
|
#include "emm_private.h"
|
|
#include "sgx_mm_rt_abstraction.h"
|
|
#include "sgx_trts_aex.h"
|
|
#include "ctd.h"
|
|
|
|
#include "se_memcpy.h"
|
|
typedef struct _handler_node_t
|
|
{
|
|
uintptr_t callback;
|
|
struct _handler_node_t *next;
|
|
} handler_node_t;
|
|
|
|
static handler_node_t *g_first_node = NULL;
|
|
static sgx_spinlock_t g_handler_lock = SGX_SPINLOCK_INITIALIZER;
|
|
|
|
static uintptr_t g_veh_cookie = 0;
|
|
sgx_mm_pfhandler_t g_mm_pfhandler = NULL;
|
|
#define ENC_VEH_POINTER(x) (uintptr_t)(x) ^ g_veh_cookie
|
|
#define DEC_VEH_POINTER(x) (sgx_exception_handler_t)((x) ^ g_veh_cookie)
|
|
extern int g_aexnotify_supported;
|
|
extern "C" sgx_status_t sgx_apply_mitigations(const sgx_exception_info_t *);
|
|
extern "C" uintptr_t cselect_mitigation_rip(const sgx_exception_info_t *);
|
|
extern "C" uintptr_t cselect_mitigation_regs(const sgx_exception_info_t *,
|
|
uintptr_t saved_rip,
|
|
uintptr_t c3_byte_address);
|
|
|
|
extern uint16_t aex_notify_c3_cache[2048];
|
|
extern uint8_t *__ct_mitigation_ret;
|
|
|
|
|
|
// sgx_register_exception_handler()
|
|
// register a custom exception handler
|
|
// Parameter
|
|
// is_first_handler - the order in which the handler should be called.
|
|
// if the parameter is nonzero, the handler is the first handler to be called.
|
|
// if the parameter is zero, the handler is the last handler to be called.
|
|
// exception_handler - a pointer to the handler to be called.
|
|
// Return Value
|
|
// handler - success
|
|
void *sgx_register_exception_handler(int is_first_handler, sgx_exception_handler_t exception_handler)
|
|
{
|
|
// initialize g_veh_cookie for the first time sgx_register_exception_handler is called.
|
|
if(unlikely(g_veh_cookie == 0))
|
|
{
|
|
uintptr_t rand = 0;
|
|
do
|
|
{
|
|
if(SGX_SUCCESS != sgx_read_rand((unsigned char *)&rand, sizeof(rand)))
|
|
{
|
|
return NULL;
|
|
}
|
|
} while(rand == 0);
|
|
|
|
sgx_spin_lock(&g_handler_lock);
|
|
if(g_veh_cookie == 0)
|
|
{
|
|
g_veh_cookie = rand;
|
|
}
|
|
sgx_spin_unlock(&g_handler_lock);
|
|
}
|
|
if(!sgx_is_within_enclave((const void*)exception_handler, 0))
|
|
{
|
|
return NULL;
|
|
}
|
|
handler_node_t *node = (handler_node_t *)malloc(sizeof(handler_node_t));
|
|
if(!node)
|
|
{
|
|
return NULL;
|
|
}
|
|
node->callback = ENC_VEH_POINTER(exception_handler);
|
|
|
|
// write lock
|
|
sgx_spin_lock(&g_handler_lock);
|
|
|
|
if((g_first_node == NULL) || is_first_handler)
|
|
{
|
|
node->next = g_first_node;
|
|
g_first_node = node;
|
|
}
|
|
else
|
|
{
|
|
handler_node_t *tmp = g_first_node;
|
|
while(tmp->next != NULL)
|
|
{
|
|
tmp = tmp->next;
|
|
}
|
|
node->next = NULL;
|
|
tmp->next = node;
|
|
}
|
|
// write unlock
|
|
sgx_spin_unlock(&g_handler_lock);
|
|
|
|
return node;
|
|
}
|
|
// sgx_unregister_exception_handler()
|
|
// unregister a custom exception handler.
|
|
// Parameter
|
|
// handler - a handler to the custom exception handler previously
|
|
// registered using the sgx_register_exception_handler function.
|
|
// Return Value
|
|
// none zero - success
|
|
// 0 - fail
|
|
int sgx_unregister_exception_handler(void *handler)
|
|
{
|
|
if(!handler)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int status = 0;
|
|
|
|
// write lock
|
|
sgx_spin_lock(&g_handler_lock);
|
|
|
|
if(g_first_node)
|
|
{
|
|
handler_node_t *node = g_first_node;
|
|
if(node == handler)
|
|
{
|
|
g_first_node = node->next;
|
|
status = 1;
|
|
}
|
|
else
|
|
{
|
|
while(node->next != NULL)
|
|
{
|
|
if(node->next == handler)
|
|
{
|
|
node->next = node->next->next;
|
|
status = 1;
|
|
break;
|
|
}
|
|
node = node->next;
|
|
}
|
|
}
|
|
}
|
|
// write unlock
|
|
sgx_spin_unlock(&g_handler_lock);
|
|
|
|
if(status) free(handler);
|
|
return status;
|
|
}
|
|
|
|
// continue_execution(sgx_exception_info_t *info):
|
|
// try to restore the thread context saved in info to current execution context.
|
|
extern "C" __attribute__((regparm(1))) void continue_execution(sgx_exception_info_t *info);
|
|
extern "C" void restore_xregs(uint8_t *buf);
|
|
|
|
#ifndef SE_SIM
|
|
extern "C" __attribute__((regparm(1))) void second_phase(sgx_exception_info_t *info,
|
|
void *new_sp, void *second_phase_handler_addr);
|
|
|
|
extern "C" void constant_time_apply_sgxstep_mitigation_and_continue_execution(sgx_exception_info_t *info,
|
|
uintptr_t ssa_aexnotify_addr, uintptr_t stack_tickle_pages, uintptr_t code_tickle_page, uintptr_t data_tickle_address, uintptr_t c3_byte_address);
|
|
|
|
// constant time select based on given condition
|
|
static inline uint64_t cselect64(uint64_t pred, const uint64_t expected, uint64_t old_val, uint64_t new_val)
|
|
{
|
|
__asm__("cmp %3, %1\n\t"
|
|
"cmove %2, %0"
|
|
: "+r"(new_val)
|
|
: "rm"(pred), "rm"(old_val), "ri"(expected));
|
|
return new_val;
|
|
}
|
|
|
|
// apply the constant time mitigation handler
|
|
static void apply_constant_time_sgxstep_mitigation_and_continue_execution(sgx_exception_info_t *info)
|
|
{
|
|
thread_data_t *thread_data = get_thread_data();
|
|
int ct_result;
|
|
uint64_t data_address;
|
|
uintptr_t saved_rip;
|
|
uintptr_t code_tickle_page, c3_byte_address, stack_tickle_pages, data_tickle_address,
|
|
stack_base_page = ((thread_data->stack_base_addr & ~0xFFF) == 0) ?
|
|
(thread_data->stack_base_addr) - 0x1000 :
|
|
(thread_data->stack_base_addr & ~0xFFF),
|
|
stack_limit_page = thread_data->stack_limit_addr & ~0xFFF;
|
|
int data_tickle_address_is_within_enclave;
|
|
|
|
// NOTE: use cselect_mitigation_rip to ensure we only ever dereference
|
|
// the interrupted application code page, even if previous interrupt
|
|
// was in the atomic mitigation stub (i.e., zero-step)
|
|
saved_rip = cselect_mitigation_rip(info);
|
|
|
|
// Determine which stack pages can be tickled
|
|
if (((uintptr_t)info & ~0xFFF) == stack_base_page) {
|
|
if (stack_base_page == stack_limit_page) {
|
|
// The stack is only a single page, so we tickle that page
|
|
stack_tickle_pages = stack_base_page;
|
|
} else {
|
|
// The current stack page is the base page, but there are more
|
|
// pages so we tickle the next one as well.
|
|
stack_tickle_pages = (stack_base_page - 0x1000) | 1;
|
|
}
|
|
} else {
|
|
// If the current stack page is not the base page, then it's generally
|
|
// better to also tickle the previous page. For example, the mitigation
|
|
// code and the interrupted code may have separate but adjacent stack
|
|
// pages (in this case, the interrupted code's stack frame must be on
|
|
// the page with a higher address).
|
|
stack_tickle_pages = ((uintptr_t)info & ~0xFFF) | 1;
|
|
}
|
|
|
|
// Look up the code page in the c3 cache
|
|
code_tickle_page = saved_rip & ~0xFFF;
|
|
c3_byte_address = code_tickle_page + *(aex_notify_c3_cache + ((code_tickle_page >> 12) & 0x07FF));
|
|
if (*(uint8_t *)c3_byte_address != 0xc3) {
|
|
uint8_t *i = (uint8_t *)code_tickle_page, *e = i + 4096;
|
|
for (; i != e && *i != 0xc3; ++i) {}
|
|
if (i == e) { // code_tickle_page does not contain a c3 byte
|
|
c3_byte_address = (uintptr_t)&__ct_mitigation_ret;
|
|
} else {
|
|
c3_byte_address = (uintptr_t)i;
|
|
*(aex_notify_c3_cache + ((code_tickle_page >> 12) & 0x07FF)) =
|
|
(uint16_t)(c3_byte_address & 0xFFF);
|
|
}
|
|
}
|
|
|
|
// NOTE: in case the previous interrupt was in the atomic mitigation
|
|
// stub, first restore clobbered application registers in the info
|
|
// struct before determining tickle addresses
|
|
cselect_mitigation_regs(info, saved_rip, c3_byte_address);
|
|
|
|
ct_result = ct_decode(&info->cpu_context, &data_address);
|
|
|
|
data_tickle_address = stack_tickle_pages & ~0x1;
|
|
data_tickle_address = cselect64(ct_result, 1, data_address, data_tickle_address);
|
|
data_tickle_address = cselect64(ct_result, 2, data_address, data_tickle_address);
|
|
data_tickle_address_is_within_enclave =
|
|
sgx_is_within_enclave((void*) data_tickle_address, sizeof(uint8_t));
|
|
|
|
/*
|
|
* Ensure the tickle page dereferenced by the mitigation lies _inside_ the enclave.
|
|
*
|
|
* NOTE:
|
|
* - Unguarded user memory accesses can leak through MMIO stale data.
|
|
* - User memory accesses are detectable and single-steppable anyway.
|
|
* - Below non-cst time check can only ever be false when the next enclave
|
|
* instruction will dereference user memory (trivially known to attacker).
|
|
*/
|
|
data_tickle_address = data_tickle_address_is_within_enclave ?
|
|
data_tickle_address : stack_tickle_pages & ~0x1;
|
|
|
|
code_tickle_page = cselect64(ct_result, 2, code_tickle_page | 0x1, code_tickle_page);
|
|
code_tickle_page = cselect64(data_tickle_address_is_within_enclave, 1, code_tickle_page, code_tickle_page & ~0x1);
|
|
|
|
// Pop an entropy byte from the entropy cache
|
|
if (--thread_data->aex_notify_entropy_remaining < 0) {
|
|
if (0 == do_rdrand(&thread_data->aex_notify_entropy_cache))
|
|
{
|
|
thread_data->exception_flag = -1;
|
|
abort();
|
|
}
|
|
thread_data->aex_notify_entropy_remaining = 31;
|
|
}
|
|
code_tickle_page |= (thread_data->aex_notify_entropy_cache & 1) << 4;
|
|
thread_data->aex_notify_entropy_cache >>= 1;
|
|
|
|
// There are three additional "implicit" parameters to this function:
|
|
// 1. The low-order bit of `stack_tickle_pages` is 1 if a second stack
|
|
// page should be tickled (specifically, the stack page immediately
|
|
// below the page specified in the upper bits)
|
|
// 2. Bit 0 of `code_tickle_page` is 1 if `data_tickle_address`
|
|
// is writable, and therefore should be tested for write permissions
|
|
// by the mitigation
|
|
// 3. Bit 4 of `code_tickle_page` is 1 if the cycle delay
|
|
// should be added to the mitigation
|
|
constant_time_apply_sgxstep_mitigation_and_continue_execution(
|
|
info, thread_data->first_ssa_gpr + offsetof(ssa_gpr_t, aex_notify),
|
|
stack_tickle_pages, code_tickle_page,
|
|
data_tickle_address, c3_byte_address);
|
|
}
|
|
#endif
|
|
|
|
// the 2nd phrase exception handing, which traverse registered exception handlers.
|
|
// if the exception can be handled, then continue execution
|
|
// otherwise, throw abortion, go back to 1st phrase, and call the default handler.
|
|
extern "C" __attribute__((regparm(1))) void internal_handle_exception(sgx_exception_info_t *info)
|
|
{
|
|
int status = EXCEPTION_CONTINUE_SEARCH;
|
|
handler_node_t *node = NULL;
|
|
thread_data_t *thread_data = get_thread_data();
|
|
size_t size = 0;
|
|
uintptr_t *nhead = NULL;
|
|
uintptr_t *ntmp = NULL;
|
|
uintptr_t xsp = 0;
|
|
uint8_t *xsave_in_ssa = (uint8_t*)ROUND_TO_PAGE(thread_data->first_ssa_gpr) - ROUND_TO_PAGE(get_xsave_size() + sizeof(ssa_gpr_t));
|
|
|
|
// AEX Notify allows this handler to handle interrupts
|
|
if (info == NULL) {
|
|
goto failed_end;
|
|
}
|
|
|
|
memcpy_s(info->xsave_area, info->xsave_size, xsave_in_ssa, info->xsave_size);
|
|
|
|
if (info->exception_valid == 0) {
|
|
goto exception_handling_end;
|
|
}
|
|
|
|
if (thread_data->exception_flag < 0)
|
|
goto failed_end;
|
|
thread_data->exception_flag++;
|
|
|
|
if(info->exception_vector == SGX_EXCEPTION_VECTOR_PF &&
|
|
(g_mm_pfhandler != NULL))
|
|
{
|
|
thread_data->exception_flag--;
|
|
sgx_pfinfo* pfinfo = (sgx_pfinfo*)(&info->exinfo);
|
|
if(SGX_MM_EXCEPTION_CONTINUE_EXECUTION == g_mm_pfhandler(pfinfo))
|
|
{
|
|
//instruction triggering the exception will be executed again.
|
|
goto exception_handling_end;
|
|
}
|
|
//restore old flag, and fall thru
|
|
thread_data->exception_flag++;
|
|
}
|
|
// read lock
|
|
sgx_spin_lock(&g_handler_lock);
|
|
|
|
node = g_first_node;
|
|
while(node != NULL)
|
|
{
|
|
size += sizeof(uintptr_t);
|
|
node = node->next;
|
|
}
|
|
|
|
// There's no exception handler registered
|
|
if (size == 0)
|
|
{
|
|
sgx_spin_unlock(&g_handler_lock);
|
|
|
|
//exception cannot be handled
|
|
thread_data->exception_flag = -1;
|
|
|
|
goto exception_handling_end;
|
|
}
|
|
// The customer handler may never return, use alloca instead of malloc
|
|
if ((nhead = (uintptr_t *)alloca(size)) == NULL)
|
|
{
|
|
sgx_spin_unlock(&g_handler_lock);
|
|
goto failed_end;
|
|
}
|
|
ntmp = nhead;
|
|
node = g_first_node;
|
|
while(node != NULL)
|
|
{
|
|
*ntmp = node->callback;
|
|
ntmp++;
|
|
node = node->next;
|
|
}
|
|
|
|
// read unlock
|
|
sgx_spin_unlock(&g_handler_lock);
|
|
|
|
// decrease the nested exception count before the customer
|
|
// handler execution, becasue the handler may never return
|
|
thread_data->exception_flag--;
|
|
|
|
// call exception handler until EXCEPTION_CONTINUE_EXECUTION is returned
|
|
ntmp = nhead;
|
|
while(size > 0)
|
|
{
|
|
sgx_exception_handler_t handler = DEC_VEH_POINTER(*ntmp);
|
|
status = handler(info);
|
|
if(EXCEPTION_CONTINUE_EXECUTION == status)
|
|
{
|
|
break;
|
|
}
|
|
ntmp++;
|
|
size -= sizeof(sgx_exception_handler_t);
|
|
}
|
|
|
|
// call default handler
|
|
// ignore invalid return value, treat to EXCEPTION_CONTINUE_SEARCH
|
|
// check SP to be written on SSA is pointing to the trusted stack
|
|
xsp = info->cpu_context.REG(sp);
|
|
if (!is_valid_sp(xsp))
|
|
{
|
|
goto failed_end;
|
|
}
|
|
|
|
if(EXCEPTION_CONTINUE_EXECUTION != status)
|
|
{
|
|
//exception cannot be handled
|
|
thread_data->exception_flag = -1;
|
|
}
|
|
|
|
exception_handling_end:
|
|
#ifndef SE_SIM
|
|
//instruction triggering the exception will be executed again.
|
|
if(info->do_aex_mitigation == 1)
|
|
{
|
|
if (info->exception_vector == SGX_EXCEPTION_VECTOR_PF &&
|
|
thread_data->exception_flag == -1)
|
|
{
|
|
// The #PF wasn't handled by EDMM or a custom #PF handler, but
|
|
// since do_aex_mitigation == 1 here (AEX-Notify enabled), the #PF
|
|
// will still be "handled" by the AEX-Notify mitigation.
|
|
thread_data->exception_flag = 0;
|
|
}
|
|
|
|
// apply customized mitigation handlers
|
|
// Note that we don't enable AEX-notify for customized mitigation handler
|
|
sgx_apply_mitigations(info);
|
|
restore_xregs(info->xsave_area);
|
|
apply_constant_time_sgxstep_mitigation_and_continue_execution(info);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
//instruction triggering the exception will be executed again.
|
|
restore_xregs(info->xsave_area);
|
|
continue_execution(info);
|
|
}
|
|
failed_end:
|
|
thread_data->exception_flag = -1; // mark the current exception cannot be handled
|
|
abort(); // throw abortion
|
|
}
|
|
|
|
static int expand_stack_by_pages(void *start_addr, size_t page_count)
|
|
{
|
|
int ret = -1;
|
|
|
|
if ((start_addr == NULL) || (page_count == 0))
|
|
return -1;
|
|
|
|
ret = mm_commit(start_addr, page_count << SE_PAGE_SHIFT);
|
|
return ret;
|
|
}
|
|
|
|
extern "C" const char Lereport_inst;
|
|
extern "C" const char Leverifyreport2_inst;
|
|
|
|
// trts_handle_exception(void *tcs)
|
|
// the entry point for the exceptoin handling
|
|
// Parameter
|
|
// the pointer of TCS
|
|
// Return Value
|
|
// none zero - success
|
|
extern "C" sgx_status_t trts_handle_exception(void *tcs)
|
|
{
|
|
thread_data_t *thread_data = get_thread_data();
|
|
ssa_gpr_t *ssa_gpr = NULL;
|
|
sgx_exception_info_t *info = NULL;
|
|
uintptr_t sp_u, sp, *new_sp = NULL;
|
|
size_t size = 0;
|
|
bool is_exception_handled = false;
|
|
|
|
if ((thread_data == NULL) || (tcs == NULL)) goto default_handler;
|
|
if (check_static_stack_canary(tcs) != 0)
|
|
goto default_handler;
|
|
|
|
if(get_enclave_state() != ENCLAVE_INIT_DONE)
|
|
{
|
|
goto default_handler;
|
|
}
|
|
|
|
// check if the exception is raised from 2nd phrase
|
|
if(thread_data->exception_flag == -1) {
|
|
goto default_handler;
|
|
}
|
|
|
|
if ((TD2TCS(thread_data) != tcs)
|
|
|| (((thread_data->first_ssa_gpr)&(~0xfff)) - ROUND_TO_PAGE(get_xsave_size() + sizeof(ssa_gpr_t))) != (uintptr_t)tcs) {
|
|
goto default_handler;
|
|
}
|
|
|
|
// no need to check the result of ssa_gpr because thread_data is always trusted
|
|
ssa_gpr = reinterpret_cast<ssa_gpr_t *>(thread_data->first_ssa_gpr);
|
|
|
|
// The unstrusted RSP should never point inside the enclave
|
|
sp_u = ssa_gpr->REG(sp_u);
|
|
if (!sgx_is_outside_enclave((void *)sp_u, sizeof(sp_u)))
|
|
{
|
|
set_enclave_state(ENCLAVE_CRASHED);
|
|
return SGX_ERROR_STACK_OVERRUN;
|
|
}
|
|
|
|
// The untrusted and trusted RSPs cannot be the same, unless
|
|
// an exception happened before the enclave setup the trusted stack
|
|
sp = ssa_gpr->REG(sp);
|
|
if (sp_u == sp)
|
|
{
|
|
set_enclave_state(ENCLAVE_CRASHED);
|
|
return SGX_ERROR_STACK_OVERRUN;
|
|
}
|
|
|
|
if(!is_stack_addr((void*)sp, 0)) // check stack overrun only, alignment will be checked after exception handled
|
|
{
|
|
set_enclave_state(ENCLAVE_CRASHED);
|
|
return SGX_ERROR_STACK_OVERRUN;
|
|
}
|
|
|
|
size = 0;
|
|
// x86_64 requires a 128-bytes red zone, which begins directly
|
|
// after the return addr and includes func's arguments
|
|
size += RED_ZONE_SIZE;
|
|
|
|
// Add space for reserved slot for GPRs that will be used by mitigation
|
|
// assembly code RIP, RAX, RBX, RCX, RDX, RBP, RSI, RDI, 1st
|
|
// QWORD of red zone
|
|
size += RSVD_SIZE_OF_MITIGATION_STACK_AREA;
|
|
|
|
// decrease the stack to give space for info
|
|
size += sizeof(sgx_exception_info_t);
|
|
size += thread_data->xsave_size;
|
|
sp -= size;
|
|
sp = sp & ~0x3F;
|
|
|
|
// check the decreased sp to make sure it is in the trusted stack range
|
|
if(!is_stack_addr((void *)sp, size))
|
|
{
|
|
set_enclave_state(ENCLAVE_CRASHED);
|
|
return SGX_ERROR_STACK_OVERRUN;
|
|
}
|
|
|
|
info = (sgx_exception_info_t *)sp;
|
|
// decrease the stack to save the SSA[0]->ip
|
|
size = sizeof(uintptr_t);
|
|
sp -= size;
|
|
if(!is_stack_addr((void *)sp, size))
|
|
{
|
|
set_enclave_state(ENCLAVE_CRASHED);
|
|
return SGX_ERROR_STACK_OVERRUN;
|
|
}
|
|
|
|
/* try to allocate memory dynamically */
|
|
if((size_t)sp < thread_data->stack_commit_addr)
|
|
{
|
|
int ret = -1;
|
|
size_t page_aligned_delta = 0;
|
|
/* try to allocate memory dynamically */
|
|
page_aligned_delta = ROUND_TO(thread_data->stack_commit_addr - (size_t)sp, SE_PAGE_SIZE);
|
|
if ((thread_data->stack_commit_addr > page_aligned_delta)
|
|
&& ((thread_data->stack_commit_addr - page_aligned_delta) >= thread_data->stack_limit_addr))
|
|
{
|
|
ret = expand_stack_by_pages((void *)(thread_data->stack_commit_addr - page_aligned_delta),
|
|
(page_aligned_delta >> SE_PAGE_SHIFT));
|
|
}
|
|
if (ret == 0)
|
|
{
|
|
thread_data->stack_commit_addr -= page_aligned_delta;
|
|
is_exception_handled = true; // The exception has been handled in the 1st phase exception handler
|
|
goto handler_end;
|
|
}
|
|
else
|
|
{
|
|
set_enclave_state(ENCLAVE_CRASHED);
|
|
return SGX_ERROR_STACK_OVERRUN;
|
|
}
|
|
}
|
|
|
|
if (size_t(&Lereport_inst) == ssa_gpr->REG(ip) && SE_EREPORT == ssa_gpr->REG(ax))
|
|
{
|
|
// Handle the exception raised by EREPORT instruction
|
|
ssa_gpr->REG(ip) += 3; // Skip ENCLU, which is always a 3-byte instruction
|
|
ssa_gpr->REG(flags) |= 1; // Set CF to indicate error condition, see implementation of do_report()
|
|
is_exception_handled = true; // The exception has been handled in the 1st phase exception handler.
|
|
goto handler_end;
|
|
}
|
|
if (size_t(&Leverifyreport2_inst) == ssa_gpr->REG(ip) && SE_EVERIFYREPORT2 == ssa_gpr->REG(ax))
|
|
{
|
|
// Handle the exception raised by everifyreport2 instruction
|
|
ssa_gpr->REG(ip) += 3; // Skip ENCLU, which is always a 3-byte instruction
|
|
ssa_gpr->REG(flags) |= 64; // Set ZF to indicate error condition, see implementation of do_everifyreport2()
|
|
ssa_gpr->REG(ax) = EVERIFYREPORT2_INVALID_LEAF;
|
|
is_exception_handled = true; // The exception has been handled in the 1st phase exception handler.
|
|
goto handler_end;
|
|
}
|
|
|
|
if(g_aexnotify_supported == 0 && ssa_gpr->exit_info.valid != 1)
|
|
{
|
|
// exception handlers are not allowed to call in a non-exception state
|
|
// add aexnotify check here to skip the case of interrupts
|
|
goto default_handler;
|
|
}
|
|
handler_end:
|
|
// initialize the info with SSA[0]
|
|
info->exception_valid = is_exception_handled ? 0 : ssa_gpr->exit_info.valid;
|
|
info->exception_vector = (sgx_exception_vector_t)ssa_gpr->exit_info.vector;
|
|
info->exception_type = (sgx_exception_type_t)ssa_gpr->exit_info.exit_type;
|
|
info->xsave_size = thread_data->xsave_size;
|
|
|
|
info->cpu_context.REG(ax) = ssa_gpr->REG(ax);
|
|
info->cpu_context.REG(cx) = ssa_gpr->REG(cx);
|
|
info->cpu_context.REG(dx) = ssa_gpr->REG(dx);
|
|
info->cpu_context.REG(bx) = ssa_gpr->REG(bx);
|
|
info->cpu_context.REG(sp) = ssa_gpr->REG(sp);
|
|
info->cpu_context.REG(bp) = ssa_gpr->REG(bp);
|
|
info->cpu_context.REG(si) = ssa_gpr->REG(si);
|
|
info->cpu_context.REG(di) = ssa_gpr->REG(di);
|
|
info->cpu_context.REG(flags) = ssa_gpr->REG(flags);
|
|
info->cpu_context.REG(ip) = ssa_gpr->REG(ip);
|
|
#ifdef SE_64
|
|
info->cpu_context.r8 = ssa_gpr->r8;
|
|
info->cpu_context.r9 = ssa_gpr->r9;
|
|
info->cpu_context.r10 = ssa_gpr->r10;
|
|
info->cpu_context.r11 = ssa_gpr->r11;
|
|
info->cpu_context.r12 = ssa_gpr->r12;
|
|
info->cpu_context.r13 = ssa_gpr->r13;
|
|
info->cpu_context.r14 = ssa_gpr->r14;
|
|
info->cpu_context.r15 = ssa_gpr->r15;
|
|
#endif
|
|
if ((info->exception_vector == SGX_EXCEPTION_VECTOR_PF)
|
|
|| (info->exception_vector == SGX_EXCEPTION_VECTOR_GP))
|
|
{
|
|
misc_exinfo_t* exinfo =
|
|
(misc_exinfo_t*)((uint64_t)ssa_gpr - (uint64_t)MISC_BYTE_SIZE);
|
|
info->exinfo.faulting_address = exinfo->maddr;
|
|
info->exinfo.error_code = exinfo->errcd;
|
|
}
|
|
new_sp = (uintptr_t *)sp;
|
|
if(!(g_aexnotify_supported || is_exception_handled == true))
|
|
{
|
|
// Two cases that we don't need to run below code:
|
|
// 1. AEXNotify is enabled
|
|
// 2. stack expansion or EREPORT exception. We have handled it
|
|
// in the first phase and we should not change anything in the ssa_gpr
|
|
//
|
|
ssa_gpr->REG(ip) = (size_t)internal_handle_exception; // prepare the ip for 2nd phrase handling
|
|
ssa_gpr->REG(sp) = (size_t)new_sp; // new stack for internal_handle_exception
|
|
ssa_gpr->REG(ax) = (size_t)info; // 1st parameter (info) for LINUX32
|
|
ssa_gpr->REG(di) = (size_t)info; // 1st parameter (info) for LINUX64, LINUX32 also uses it while restoring the context
|
|
}
|
|
*new_sp = info->cpu_context.REG(ip); // for debugger to get call trace
|
|
#ifndef SE_SIM
|
|
if(g_aexnotify_supported)
|
|
{
|
|
info->do_aex_mitigation = get_ssa_aexnotify();
|
|
void *first_ssa_xsave = reinterpret_cast<void *>(thread_data->first_ssa_xsave);
|
|
restore_xregs((uint8_t*)first_ssa_xsave);
|
|
// With AEX Notify, we don't need to do a return here (phase-1 handler).
|
|
// Instead, we jump to internal_handle_exception (phase-2 handler).
|
|
// We should not make a function call either, because ideally the return at
|
|
// the end of phase-2 handler should directly return to the interrupted enclave code.
|
|
// Disable aexnotify before EDCSSA
|
|
if(info->do_aex_mitigation == 1)
|
|
{
|
|
sgx_set_ssa_aexnotify(0);
|
|
}
|
|
second_phase(info, new_sp, (void *)internal_handle_exception);
|
|
}
|
|
else
|
|
#endif
|
|
{
|
|
return SGX_SUCCESS;
|
|
}
|
|
|
|
default_handler:
|
|
set_enclave_state(ENCLAVE_CRASHED);
|
|
return SGX_ERROR_ENCLAVE_CRASHED;
|
|
}
|