@Intel SGX 读书笔记…
基本类型和用户定义的数据类型可以在structure/union中使用,除了在以下方便与标准不同:
不支持的语法:
enclave{
// 1. Each member of the structure has to be defined separately
struct data_def_t{
int a, b, c; // Not allowed
// It has to be int a; int b; int c;
};
// 2. Bit fields in structures/unions are not allowed.
struct bitfields_t{
short i : 3;
short j : 6;
short k : 7;
};
//3. Nested structure definition is not allowed
struct my_struct_t{
int out_val;
float out_fval;
struct inner_struct_t{
int in_val;
float in_fval;
};
};
};
合法的语法:
enclave{
include "user_types.h" //for ufloat: typedef float ufloat
struct struct_foo_t {
uint32_t struct_foo_0;
uint64_t struct_foo_1;
};
enum enum_foo_t {
ENUM_FOO_0 = 0,
ENUM_FOO_1 = 1
};
union union_foo_t {
uint32_t union_foo_0;
uint32_t union_foo_1;
uint64_t union_foo_3;
};
trusted {
public void test_char(char val);
public void test_int(int val);
public void test_long(long long val);
public void test_float(float val);
public void test_ufloat(ufloat val);
public void test_double(double val);
public void test_long_double(long double val);
public void test_size_t(size_t val);
public void test_wchar_t(wchar_t val);
public void test_struct(struct struct_foo_t val);
public void test_struct2(struct_foo_t val);
public void test_enum(enum enum_foo_t val);
public void test_enum2(enum_foo_t val);
public void test_union(union union_foot_t val);
public void test_union2(union_foo_t val);
};
};
注意:当在EDL文件中需要引用一个structure、enum或union时,必须遵循C语言风格并使用相应的关键字struct、enum或union。
结构深拷贝
结构中的成员指针可以用缓冲区大小属性size修饰,或者用count表示深度复制结构,而不是浅复制。当可信边例程复制结构指针所指向的缓冲区时,它们也复制结构指针的方向属性所指示的结构成员指针所指向的缓冲区。成员指针值也相应地进行了修改。
结构的缓冲区大小必须是结构大小的倍数,并且将缓冲区作为结构数组进行深度复制。由于按值调用函数会生成浅拷贝,所以不允许按值调用深拷贝结构。深度复制结构指针的方向属性可以是in和in,out。如果成员指针不是基本类型,可信的边例程不会递归地深度复制它。
举例:
enclave {
struct struct_foo_t {
uint32_t count;
size_t size;
[count = count, size = size] uint64_t* buf;
};
trusted {
public void test_ecall_deep_copy([in, count = 1] struct struct_foo_t * ptr);
};
};
在调用该ECALL之前,在不可信域中准备以下数据作为参数:
struct struct_foo_t foo = {
4, 8, data};
foo.count = 4;
foo.size = 8;
foo.buf = address of data[] in untrusted domain.
data[] = {
0x1112131415161718, 0x2122232425262728,
0x3132333435363738,
0x4142434445464748}
在调用该ECALL后,可信域中的数据将为:
struct struct_foo_t foo = {
4, 8, data2};
foo.count = 4;
foo.size = 8;
foo.buf = address of data2[] in trusted domain.
data2[] = {
0x1112131415161718,
0x2122232425262728,
0x3132333435363738,
0x4142434445464748}
注意:当在OCALL中使用in属性深度复制指针参数时,结构中的指针(即可信域的地址)将被临时复制到不可信域。如果地址是敏感数据,则必须避免这种情况。
数组
EDL支持在数据结构定义和参数声明中使用多维、固定大小的数组。
数组还应该显式地用属性[in]、[out]或[user_check]修饰,它们与指针类似。
注意:数组使用的局限:
- size/count不能用作数组类型
- const不能用作数组类型
- EDL语法不支持0长度的数组和可调节大小的数组
- EDL语法不支持指针数组
举例:
enclave {
include "user_types.h" //for uArray - typedef int uArray[10];
trusted {
public void test_array([in] int arr[4]);
public void test_array_multi([in] int arr[4][4]);
};
};
不支持的语法:
enclave {
include "user_types.h" //for uArray - typedef int uArray[10];
trusted {
// Flexible array is not supported
public void test_flexible(int arr[][4]);
// Zero-length array is not supported.
public void test_zero(int arr[0]);
};
};
特殊属性isary用于指定用户定义类型数组的函数参数。详细信息,参考User Defined Data Types。(即下一节)
用户定义的数据类型
EDL支持用户定义的数据类型,但是应该在头文件中定义。将任何基本类型定义为另一种类型的数据类型都将成为用户定义的数据类型。
一些用户数据类型需要使用特殊的EDL属性进行注释,比如isptr、isary和readonly。如果用户定义的类型参数需要这些属性中的一个,那么编译器将在sgx_edger8r生成地代码中发出编译错误。
注意:isptr、isary和readonly只能用于修饰用户定义的数据类型。不要将它们用于任何基本类型、指针或数组。
readonly只能与isptr属性一起使用。不允许使用readonly的其他任何用法。
举例:
enclave {
include "user_types.h" // for typedef void * pBuf; and typedef void const * pBuf2; and typedef int uArray[10];
trusted {
public void test_isptr([in, isptr, size=len] pBuf pBufptr,size_t len);
public void test_isptr_readonly([in, isptr, readonly, size=len] pBuf2 pBuf2ptr,size_t len);
public void test_isary([in, isary] uArray arr);
};
};
不支持的语法:
enclave {
include "user_types.h" //for typedef void const * pBuf2; and typedef int uArray[10];
trusted {
// Cannot use [out] when using [readonly] attribute
void test_isptr_readonly_cant([in, out, isptr, readonly, size=len] pBuf2 pBuf2ptr, size_t len);
// isptr cannot be used for pointers/arrays
public void test_isptr_cant1([in, isptr, size=len] pBuf* pBufptr, size_t len);
public void test_isptr_cant2([in, isptr, size=len] void* pBufptr,
size_t len);
// User-defined array types need "isary"
public void test_miss_isary([in] uArray arr);
// size/count attributes cannot be used for user-defined array types
public void test_isary_cant_size([in, size=len] uArray arr,
size_t len);,
// isary cannot be used for pointers/arrays
public void test_isary_cant([in, isary] uArray arr[4]);
};
};
在 test_isptr_readonly函数中, pBuf2 (typedef void const *pBuf2)是一个用户定义的指针类型,因此isptr用来表示它是一个用户定义的类型。另外, pBuff2ptr是readonly,因此不能使用out属性。
预处理器能力
EDL语言支持宏定义和条件编译指令。为了提供这种功能,sgx_edger8r首先使用编译器预处理器来解析EDL文件。翻译完所有预处理令牌后,sgx_edger8r将结果文件解析为常规的EDL语言。这意味着开发人员可以定义简单的宏并使用条件编译指令来轻松地从产品Enclave中删除调试和测试功能,从而减少Enclave地供给面。参加下面的EDL示例。
#define SGX_DEBUG
enclave {
trusted {
// ECALL definitions
}
untrusted {
// OCALL definitions
#ifdef SGX_DEBUG
void print([in, string] const char * str);
#endif
}
}
当前的sgx_edger8r不会将宏定义从EDL文件传播到生成边缘例程。因此,你需要在EDL文件以及编译器参数或其他源文件中复制宏定义。
建议你只在EDL文件中使用简单的宏定义和条件编译指令。
sgx_edger8r开始搜索Intel编译器地PATH。如果没有找到Intel编译器,那么它将搜索到VS2017编译器。sgx_edger8r使用它找到的第一个编译器。但是,如果sgx_edger8r没有在路径中找到任何编译器,它就会在注册表中查找VS编译器。如果sgx_edger8r仍然找不到编译器,它将生成一条警告消息并直接解析EDL。解析宏和可能存在的条件编译指令到EDL文件中。您可以覆盖默认的搜索行为,甚至可以使用–preprocessor 选项制定不同地预处理器。
不可信的函数可以选择性地接收影响其调用规则和DLL链接的属性。 你可以在该链接查看调用规则的具体情况。
cdel调用约定是C语言标准定义的默认约定。不正确地使用cded、stdcall或fastcall关键字可能会导致链接器错误。
OCALL函数(不可信的)可以在DLL中实现,关键字dllimport用来指定这个属性。不正确地使用dllimport关键字将导致编译警告。
以下关键字是调用约定指定的:
表1 调用约定关键字
值 | 堆栈清理 | 参数传递 |
---|---|---|
cdecl | 调用者 | 将参数推入堆栈(从右至左) |
stdcall | 被调用者 | 将参数推入堆栈(从右至左) |
fastcall | 被调用者 | 存储在寄存器中,然后压入堆栈(从右至左) |
这些调用约定只映像32位的构建。64位版本只有一个调用约定,fastcall。
举例:
可信函数test_calling_convs()可以使用不可信的函数(OCALLs)来使用文件操作等标准函数。
enclave {
include "sgx_stdio_stubs.h" //for FILE and other definitions
trusted {
public void test_calling_convs(void);
};
untrusted {
[cdecl, dllimport] FILE * fopen([in,string] const char * filename, [in,string] const char * mode);
[cdecl, dllimport] int fclose([user_check] FILE * stream);
[cdecl, dllimport] size_t fwrite([in, size=size, count=count] const void * buffer, size_t size, size_t count, [user_check]FILE * stream);
[fastcall] void test_fast_call([in]void* ptr);
[stdcall] void test_std_call(void);
};
}
不支持的语法
enclave {
untrusted {
// Compiler warning without [cdecl,dllimport]
size_t fwrite([in, size=size, count=count] const void* ptr, size_t size, size_t count, [user_check] FILE * stream);
// Compiler error without [stdcall]
// Redefinition due to different type modifiers
void test_std_call(void);
};
};
在OCALLs中传播errno
OCALLs可以使用propagate_errno属性。 当你使用这个属性时,sgx_edger8r会产生稍微不同的边缘历程。Enclave内的errno变量(由可信的标准C语言库提供)在OCALL返回之前被不可信域中的errno值覆盖。无论OCALL是否成功,可信的errno都会在OCALL完成时更新。这不会改变errno的基本行为。失败的函数必须设置errno来指示哪里出错了。成功的函数(在本例中是OCALL)可以更改errno的值。
举例:
enclave {
include "sgx_stdio_stubs.h" //for FILE and other definitions
trusted {
public void test_file_io(void);
};
untrusted {
[cdecl, dllimport] FILE * fopen(
[in,string] const char * filename,
[in,string] const char * mode) propagate_errno;
[cdecl, dllimport] int fclose([user_check] FILE * stream) propagate_errno;
[cdecl, dllimport] size_t fwrite( [in, size=size, count=count] const void * buffer, size_t size, size_t count, [user_check]FILE * stream) propagate_errno;
};
};
你可以在外部可信库中实现导出和导入函数,类似于在不可信域中实现静态库。 要将这些函数添加到Enclave,请使用EDL库导入机制。使用from和import的EDL关键字将库EDLfile添加到Enclave EDL中。
from关键字指定库EDL文件的位置。接收相对路径和绝对路径。相对路径相对于EDL文件的位置。建议使用不同的名称来区分库EDL文件和Enclave EDL文件。
import关键字指定要导入的函数。星号(*)可用于从库导入所有函数。通过写入以逗号分隔的函数名列表,可以导入多个函数。
语法:
from “lib_filename.edl” import func_name, func2_name;
from “lib_filename.edl” import *;
举例:
enclave {
from “secure_comms.edl” import send_email, send_sms;
from "../../sys/other_secure_comms.edl" import *;
};
一个库EDL文件可以导入另一个EDL文件,EDL文件又可以导入另一个文件,从而形成如下层次结构:
// enclave.edl
enclave {
from “other/file_L1.edl” import *; //Import all functions
};
// Trusted library file_L1.edl
enclave {
from "file_L2.edl" import *;
trusted {
public void test_int(int val);
};
};
// Trusted library file_L2.edl
enclave {
from "file_L3.edl" import *;
trusted {
public void test_ptr(int* ptr);
};
};
// Trusted library file_L3.edl
enclave {
trusted {
public void test_float(float flt);
};
};
默认行为是任何不可信的函数都不能调用ECALL函数。
为了使应用程序代码能够直接将ECALL作为根ECALL调用,应该使用public关键字显式地修饰ECALL,使其成为公共ECALL。 如果没有这个关键字,ECALLs将被视为私有ECALLs,并且不能被直接调用为根ECALLs。
语法:
trusted {
public <function prototype>;
};
一个Enclave EDL必须有一个或多个public ECALLs,否则Enclave函数根本不能被调用,在这种情况下,sgx_edger8r将报告一个错误。
若要授予OCALL函数对ECALL函数地访问权限,请使用allow关键字指定此访问权限。public和private ECALLs都可以放在allow list中。
语法:
untrusted {
<function prototype> allow (func_name, func2_name, …);
};
举例:
enclave {
trusted {
public void clear_secret();
public void get_secret([out] secret_t* secret);
void set_secret([in] secret_t* secret);
};
untrusted {
void replace_secret([in] secret_t* new_secret,[out] secret_t* old_secret) allow (set_secret, clear_secret);
};
};
在上面的例子中,不可信的代码被授予ECALLs不同地访问权限。
表2
ECALL | 作为根ECALL被调用 | 从replace_secret被调用 |
---|---|---|
clear_secret | Y | N |
get_secret | Y | N |
set_secret | N | Y |
ECALLs和OCALLs可以使用transition_using_threads属性作为EDL文件中函数声明地后缀。当你使用这个属性时,sgx_edger8r会产生不同的边缘例程。
带有transition_using_threads属性地ECALLs和OCALLs使用Switchless(翻译说是“无切换”的意思)操作模式来服务调用。(具体可以看后面的“使用Switchless调用”)
举例:
enclave {
trusted {
public void ecall_empty(void);
public void ecall_empty_switchless(void) transition_using_
threads;
};
untrusted {
void ocall_empty(void);
void ocall_empty_switchless(void) transition_using_threads;
};
};
如有误,请指正!感激!