三种Enclave输入模式会导致被攻击的风险:
public uint32_t enclave_function([in, size = alloc_size] tlv_t* varlen_input,
uint32_t alloc_size);
// 头文件定义
typedef struct {
unsigned type;
unsigned length;
void* payload;
} tlv_t;
// make sure payload is outside enclave
If (varlen_input.length > alloc_size) {
// error code
... }
Else {
_mm_lfence();
//
// valid length path
//
...
}
// EDL
public uint32_t enclave_function([user_check]const uint8_t* user_check_input,
uint32_t user_check_size);
uint32_t enclave_function(const uint8_t* user_check_input,
uint32_t user_check_size)
{
...
//
// make sure input buffer is outside enclave
//
int SGXAPI sgx_is_outside_enclave(const void *addr, size_t
size);
if (!sgx_is_outside_enclave(user_check_input,
user_check_size)) {
// error code
...
}
else {
_mm_lfence();
...
}
...
}
void victim_function(size_t x) {
if (x < array1_size) {
temp &= array2[array1[x] * 512];
}
}
void victim_function(size_t x) {
if (x < array1_size) {
_mm_lfence();
temp &= array2[array1[x] * 512];
}
}
EDL属性中的sizefunc使得在EDL进行函数定义时,开发人员可以指定一个函数,这个函数用于计算函数参数的长度。sizefunc所修饰的函数是运行在enclave中的,但由于其负责计算输入的长度,因此在进行常规的输入参数是否位于enclave之外这一检查之前,输入参数就会被访问到。这个过程容易引起side channel 攻击风险,因此sizefunc函数应当被马上从EDL定义中移除,开发人员应该使用更安全的size属性来确定输入参数的长度,从而保证在读取输入参数之前就使得index不会越界。
size_t tcalc_size(const tlv_t* varlen_input, size_t maxlen)
{
size_t len = sizeof(tlv_t);
if (len > maxlen)
{
len = 0;
} else {
_mm_lfence(); //fence after check (CVE-2017-5753)
len = varlen_input->length;
if (len > maxlen)
{
len = 0;
}
}
_mm_lfence(); //fence after check (CVE-2017-5753)
return len;
}
void ecall_no_sizefunc(tlv_t* varlen_input, size_t len)
{
// make sure code won't go past input
// before processing
if (tcalc_size(varlen_input, len) == 0)
{
//record the error
return;
}
//tcalc_size performs fence
//process varlen_input
return;
}
/* EDL
* [string]:
* the attribute tells Edger8r 'str' is NULL terminated string,
* so strlen will be used to count the length of buffer pointed
* by 'str'. */
public void ecall_pointer_string([in, string] char *str)
typedef struct ms_ecall_pointer_string_t {
char* ms_str;
} ms_ecall_pointer_string_t;
sgx_status_t ecall_pointer_string(sgx_enclave_id_t eid, char* str)
{
sgx_status_t status;
ms_ecall_pointer_string_t ms;
ms.ms_str = str;
status = sgx_ecall(eid, 12, &ocall_table_Enclave, &ms);
return status;
}
#define CHECK_REF_POINTER(ptr, siz) do { \
if (!(ptr) || ! sgx_is_outside_enclave((ptr), (siz))) \
return SGX_ERROR_INVALID_PARAMETER;\
} while (0)
#define CHECK_UNIQUE_POINTER(ptr, siz) do { \
if ((ptr) && ! sgx_is_outside_enclave((ptr), (siz))) \
return SGX_ERROR_INVALID_PARAMETER;\
} while (0)
static sgx_status_t SGX_CDECL sgx_ecall_pointer_string(void* pms)
{
CHECK_REF_POINTER(pms, sizeof(ms_ecall_pointer_string_t));
ms_ecall_pointer_string_t* ms =
SGX_CAST(ms_ecall_pointer_string_t*, pms);
sgx_status_t status = SGX_SUCCESS;
char* _tmp_str = ms->ms_str;
size_t _len_str = _tmp_str ? strlen(_tmp_str) + 1 : 0;
char* _in_str = NULL;
CHECK_UNIQUE_POINTER(_tmp_str, _len_str);
// fence after pointer checks
_mm_lfence();
if (_tmp_str != NULL && _len_str != 0) {
_in_str = (char*)malloc(_len_str);
if (_in_str == NULL) {
status = SGX_ERROR_OUT_OF_MEMORY;
goto err;
}
memcpy(_in_str, _tmp_str, _len_str);
_in_str[_len_str - 1] = '\0';
}
ecall_pointer_string(_in_str);
err:
if (_in_str) free(_in_str);
return status;
}
上图是Edger8r产生的bridge函数。可以看到,strlen操作在enclave里面完成。
Example - Updated Edger8r
Edger8r工具的更新修改了marshalling structure的定义,在上例中,更新后的Edger8r会产生如下的Marshalling structure:
typedef struct ms_ecall_pointer_string_t {
char* ms_str;
size_t ms_len_str;
} ms_ecall_pointer_string_t;
这里多了一个成员ms_len_str,用于指明这个字符串的长度。
sgx_status_t ecall_pointer_string(sgx_enclave_id_t eid, char* str) {
sgx_status_t status;
ms_ecall_pointer_string_t ms;
ms.ms_str = str;
ms.ms_len_str = strlen(str) + 1;
status = sgx_ecall(eid, 12, &ocall_table_Enclave, &ms);
return status;
}
上图是更新后的Edger8r工具产生的bridge函数,运行在untrusted部分。可以看到,string的长度是由strlen函数在untrusted部分计算得到的,不涉及到enclave的内存。
#define CHECK_REF_POINTER(ptr, siz) do { \
if (!(ptr) || ! sgx_is_outside_enclave((ptr), (siz))) \
return SGX_ERROR_INVALID_PARAMETER;\
} while (0)
#define CHECK_UNIQUE_POINTER(ptr, siz) do { \
if ((ptr) && ! sgx_is_outside_enclave((ptr), (siz))) \
return SGX_ERROR_INVALID_PARAMETER;\
} while (0)
static sgx_status_t SGX_CDECL sgx_ecall_pointer_string(void* pms)
{
CHECK_REF_POINTER(pms, sizeof(ms_ecall_pointer_string_t));
// fence after pointer checks
sgx_lfence();
ms_ecall_pointer_string_t* ms =
SGX_CAST(ms_ecall_pointer_string_t*, pms);
sgx_status_t status = SGX_SUCCESS;
char* _tmp_str = ms->ms_str;
size_t _len_str = ms->ms_len_str;
char* _in_str = NULL;
CHECK_UNIQUE_POINTER(_tmp_str, _len_str);
// fence after pointer checks
sgx_lfence();
if (_tmp_str != NULL) {
_in_str = (char*)malloc(_len_str);
if (_in_str == NULL) {
status = SGX_ERROR_OUT_OF_MEMORY;
goto err;
}
memcpy(_in_str, _tmp_str, _len_str);
_in_str[_len_str - 1] = '\0';
}
ecall_pointer_string(_in_str);
err:
if (_in_str) free(_in_str);
return status;
}
typedef struct _tlv_t {
uint32_t buf_len;
uint8_t buf[];
} tlv_t;
/*
* [sizefunc]:
* the attribute tells Edger8r that calc_size can be used to
* calculate the size of varlen_input
*/
public void ecall_sizefunc([in, sizefunc = calc_size] tlv_t* varlen_input);
size_t calc_size(const tlv_t* varlen_input) {
return ( sizeof(tlv_t) + varlen_input->buf_len );
}
void ecall_sizefunc(tlv_t* varlen_input){
//process varlen_input
return;
}
static sgx_status_t SGX_CDECL sgx_ecall_sizefunc(void* pms) {
CHECK_REF_POINTER(pms, sizeof(ms_ecall_sizefunc_t));
ms_ecall_sizefunc_t* ms = SGX_CAST(ms_ecall_sizefunc_t*, pms);
sgx_status_t status = SGX_SUCCESS;
tlv_t* _tmp_varlen_input = ms->ms_varlen_input;
size_t _len_varlen_input = ((_tmp_varlen_input) ? calc_size(_tmp_varlen_input) : 0);
tlv_t* _in_varlen_input = NULL;
CHECK_UNIQUE_POINTER(_tmp_varlen_input, _len_varlen_input);
// fence after pointer checks
_mm_lfence();
if (_tmp_varlen_input != NULL && _len_varlen_input != 0) {
_in_varlen_input = (tlv_t*)malloc(_len_varlen_input);
if (_in_varlen_input == NULL) {
status = SGX_ERROR_OUT_OF_MEMORY;
goto err;
}
memcpy(_in_varlen_input, _tmp_varlen_input, _len_varlen_input);
/* check whether the pointer is modified. */
if (calc_size(_in_varlen_input) != _len_varlen_input) {
status = SGX_ERROR_INVALID_PARAMETER;
goto err;
}
// fence after final sizefunc check
_mm_lfence();
}
ecall_sizefunc(_in_varlen_input);
err:
if (_in_varlen_input) free(_in_varlen_input);
return status;
}
/*
* [size]:
* the attribute tells Edger8r that len is the size of
* varlen_input
*/
public void ecall_no_sizefunc([in, size = len] tlv_t* varlen_input, size_t len);
/*
* ucalc_size:
* calculates the size of a tlv_t structure
*/
size_t ucalc_size(const tlv_t* varlen_input) {
return (sizeof(tlv_t) + varlen_input->buf_len);
}
int SGX_CDECL main(int argc, char *argv[])
{
tlv_t varlen_struct;
...
ret = ecall_no_sizefunc(global_eid,
&varlen_struct,
ucalc_size(&varlen_struct));
...
}
static sgx_status_t SGX_CDECL sgx_ecall_no_sizefunc(void* pms) {
CHECK_REF_POINTER(pms, sizeof(ms_ecall_no_sizefunc_t));
// fence after pointer checks
sgx_lfence();
ms_ecall_no_sizefunc_t* ms = SGX_CAST(ms_ecall_no_sizefunc_t*, pms);
sgx_status_t status = SGX_SUCCESS;
tlv_t* _tmp_varlen_input = ms->ms_varlen_input;
size_t _tmp_len = ms->ms_len;
size_t _len_varlen_input = _tmp_len;
tlv_t* _in_varlen_input = NULL;
CHECK_UNIQUE_POINTER(_tmp_varlen_input, _len_varlen_input);
// fence after pointer checks
_mm_lfence();
if (_tmp_varlen_input != NULL && _len_varlen_input != 0) {
_in_varlen_input = (tlv_t*)malloc(_len_varlen_input);
if (_in_varlen_input == NULL) {
status = SGX_ERROR_OUT_OF_MEMORY;
goto err;
}
memcpy(_in_varlen_input, _tmp_varlen_input, _len_varlen_input);
}
ecall_no_sizefunc(_in_varlen_input, _tmp_len);
err:
if (_in_varlen_input) free(_in_varlen_input);
return status;
}
size_t tcalc_size(const tlv_t* varlen_input, size_t maxlen) {
size_t len = sizeof(tlv_t);
if (len > maxlen) {
len = 0;
} else {
_mm_lfence(); //fence after maxlen check (CVE-2017-5753)
len = sizeof(tlv_t) + varlen_input->buf_len;
if (len > maxlen) {
len = 0;
}
}
_mm_lfence(); //fence after maxlen check (CVE-2017-5753)
return len;
}
void ecall_no_sizefunc(tlv_t* varlen_input, size_t len) {
if (tcalc_size(varlen_input, len) == 0) {
//record the error
return;
}
//tcalc_size has already performed the fence
//process varlen_input
return;
}