在这之前,一直想学习关于SGX实操性的、应用相关的内容,我浏览了相关资料,总觉得跟不上那些作者的脚步,学到的都是些零零碎碎的知识,收获都不是很大,一边阅读还在一边质疑资料的可读性,所以我决定返朴归真,静下心来好好看一看官网的手册,并作一些笔记方便自己的日后学习。
手册下载地址: Intel® Software Guard Extensions (Intel® SGX) SDK for Linux* OS
Enclave Development Basics
本篇主要介绍enclave开发的基础知识:
- Enclave函数编写
- Enclave内部调用函数
- Enclave外部调用函数
- Enclave与库连接
- 链接应用程序与不受信任的库
- Enclave定义语言语法
- 装载和卸载Enclave
标准enclave发展过程包括以下的几个步骤:
- 在EDL文件中定义enclave和不可信应用程序的接口。
- 实现应用程序和enclave函数。
- 建立应用程序和enclave。在创建的工程中,Edger8r Tool生成可信和不可信的代理/桥 函数。Enclave Signing Tool为enclave生成元数据和签名。
- 在模拟和硬件模式下运行和调试应用程序。
- 准备释放应用程序和enclave。
Writing Enclave Functions(Enclave函数编写)
Enclave函数是具有一些限制的普通C/C++函数。用户可以用C和C++编写enclave函数(仅限本地),不支持其他语言。
Enclave函数可以依赖特殊版本的C/C++运行时库、STL、synchronization和其他几个可信库,他们是Intel SGX SDK的一部分,这些受信任的库被专门设计使用在enclave内部。
用户可以编写或使用其他受信任的库,确保库遵循与内部enclave函数相同的规则:
- enclave不能使用所有可用的32位或64位指令。要检查enclave内的非法指令集。
- Enclave函数只在用户模式下运行。使用需要CPU特权的指令将导致Enclave出错。
- 如果被调用的函数是被静态链接到enclave,则可以在enclave内调用函数(该函数位于enclave映像文件中),不支持Linux*共享对象。
Table 8 Summary of Intel® SGX Rules and Limitations(Intel®SGX规则和限制)
| 特征 | 是否支持 | 说明 |
|---|---|---|
| 语言 | 部分的 | 本机C / C++。Enclave接口函数仅限于C(没有c++) |
| C/C++调用其他共享对象 | 不支持 | 可以通过显式的外部调用(OCALLs)完成。 |
| C/ c++调用系统提供的C/ C++ /STL标准库 | 不支持 | SDK提供了这些库的可信版本,它们可以代替。 |
| 操作系统API调用 | 不支持 | 可以通过显式的外部调用(OCALLs)完成。 |
| C++框架 | 不支持 | 包含MFC*,QT*,Boost*(部分的 — 只要不使用Boost运行) ? |
| 调用C++类方法 | 支持 | 包括C++类,静态的和inline函数 |
| 线程支持 | 部分的 | 不支持在enclave内部创建线程。在enclave中运行的线程是在(不受信任的)应用程序中创建的。自旋锁、可信互斥锁和条件变量API可以用于enclave内部的线程同步。 |
| 动态内存分配 | 支持 | Enclave内存是一种有限的资源。在创建enclave时设置最大堆大小。 |
| Signals? | 不支持 | enclave内不支持Signal。 |
Calling Functions inside the Enclave (Enclave内部调用函数)
- 在enclave被加载成功后,你得到一个enclave ID,该ID在执行ECALLs时作为参数提供。
- OCALLs可以在ECALLs中被执行。
举个例子:假设你需要在enclave内计算一些保密数据,EDL文件可能如下所示:
//demo.edl
enclave{
//在这添加你的保密数据的定义
trusted{
public void get_secret([out] secret_t* secret);
};
untrusted{
//这个OCALL仅是为了演示用,他不应该被一个真正的enclave使用,除非是在开发阶段为了调试而使用。
void dump_secret([in] const secret_t* secret);
};
};
使用上述EDL,sgx_edger8r将为ECALL生成一个不受信任的代理函数,为OCALL生成一个受信任的代理函数:
不受信任的代理函数(被应用程序调用):
sgx_status_t get_secret(sgx_enclave_id_t eid,secret_t* secret);
受信任的代理函数(被enclave调用):
sgx_status_t dump_secret(const secret_t* secret);
生成的不可信代理函数将自动调用enclave,并将参数传递给enclave内部真正的可信函数get_secret,在应用程序中发起一个ECALL:
//enclave调用(ECALL)将发生在这: //App.cpp
secret_t secret;
sgx_status_t status = get_secret(eid,&secret);
enclave中受信任函数可以选择执行一个OCALL调用,通过受信任代理dump_secret转储秘密数据。它将带着(被真正不可信任函数dump_secret接收的)给定参数自动离开enclave,真正的不可信任函数需要由开发人员实现,并与应用程序链接。
检查返回值
受信任和不受信任的代理函数返回 sgx_status_t 。如果代理函数运行成功,它将返回SGX_SUCCESS。否则,它指示出错误码章节中描述的特定错误。
Calling Functions outside the Enclave(Enclave外部调用函数)
在某些情况下,enclave内的代码需要调用驻留在不受保护内存的外部函数去使用enclave外的操作系统能力,例如系统调用、I/O操作等等。这样的函数调用称为OCALL。
enclave映像的加载方式与Linux* OS加载共享对象的方式十分相似。应用程序的函数地址空间与enclave共享,因此enclave代码可以间接调用与创建enclave的应用程序链接的函数。直接调用应用程序的函数是不允许的,并将在运行时发生异常。
CAUTION:
- 因为外部函数不能访问受保护的(enclave)内存,包装器函数将参数从受保护的内存复制到不受保护的内存,特别是,将OCALL参数复制到不受信任的堆栈中。根据OCALL参数的数量,可能会导致不可信域中的堆栈溢出,使用调试器可以轻松的检测到异常。
- 包装器函数将复制缓冲区[由指针引用的内存],只有当这些指针在EDL文件中被分配特殊属性时。
- Intel SGX SDK发布的特定可信任库提供了可以OCALLs调用的API。目前,来自 libsgx_tstdc.a 库的Intel SGX互斥锁、读写锁、条件变量和CPUID APIs可以OCALLs调用。同样的,可信任的库 libsgx_tservice.a提供来自Platform services Enclave的服务也可以做OCALLs调用。使用这些API的开发者必须先从相应的EDL文件中引入需要的OCALL函数。
OCALL函数有以下限制/规则:
- OCALL函数必须是C语言函数,或者是带有C链接的C++函数。
- 引用enclave内数据的指针必须在EDL文件中使用指针属性进行注释。包装器函数将对这些 指针执行浅拷贝。
- 在enclave内异常不会被捕捉,用户必须在不可信包装器函数中处理他们。
- OCALLs 在 原型里不能有 ellipse (…) 或 va_list。(OCALLs cannot have an ellipse (…) or a va_list in their prototype.)
Example1:一个简单OCALL函数的定义
Step 1 - 在EDL文件中添加foo声明
//foo.del
enclave{
untrusted{
[cdecl] void foo(int param);
};
};
Step 2 - 编写一个可信的、用户友好的的包装器。这个函数是enclave的可信代码的一部分。(可选但强烈推荐)
包装器函数ocall_foo函数看起来像:
//enclave's trusted code
//App.cpp
#include "foo_t.h"
void ocall_foo(int param)
{
//有必要去检查foo()的返回值
if(foo(param)!=SGX_SUCCESS)
abort();
};
Step 3 - 编写一个不可信的foo函数
//enclave.cpp
// untrusted code
void foo(int param)
{
// the implementation of foo
}
sgx_edger8r将生成一个不受信任桥函数,它将自动调用不受信任的函数foo。这个不受信任桥和不受信函数是应用程序的一部分,而不是enclave。
Library Development for Enclaves(Enclave库的发展)
可信库是一个术语,指被设计为与enclave连接的静态库。下面的列表描述了可信库的特征:
- 可信库是基于Intel®sgx的解决方案的组件。它们通常要经历比常规静态库更严格的威胁评估和审查过程。
- 开发(或移植)可信库的特定目的是在enclave中使用。因此,它不应该包含Intel®SGX架构不支持的指令。
- 可信库API的子集也可能是enclave接口的一部分。可以公开给不可信域的可信库接口在EDL文件中定义。如果存在,则此EDL文件是可信库的关键组件。
- 可信库必须与不可信库一起提供。受信任库中的函数可以调用enclave之外的函数。如果平台上可用的库没有提供可信库使用的外部函数,则可信库将需要一个不可信的支持库。
总之,一个受信任的库除了包含受信任代码和数据的 .a文件,还可以包含 .edl文件和不受信任的 .a文件。
Linking Enclave with Libraries(Enclave与库连接)
这一部分介绍如何将enclave与以下类型的库链接:
- 动态库
- 静态库
- 仿真库
动态库:
enclave共享对象不能以任何方式依赖于任何动态链接的库。 enclave加载器被有意的设计为禁止enclave内库的动态链接。enclave的保护依赖于在加载时获得对放置在enclave中的所有代码和数据的准确测量;因此,动态链接会增加复杂性,而不会比静态链接提供任何好处。
CAUTION:如果enclave文件有任何 未解析的依赖关系?,则enclave映像签名进程将失败。
静态库:
您可以链接静态库,只要它们没有任何依赖项。Intel®SGX SDK提供以下可信库集合。
Table 9 Trusted Libraries included in the Intel® SGX SDK
| Name | Description | Comment |
|---|---|---|
| libsgx_trts.a | Intel SGX运行时库 | 在HW硬件模式下,必须运行时链接 |
| libsgx_trts_sim.a | Intel SGX 运行时库(模拟模式) | 在模拟模式下,必须运行时链接 |
| libsgx_tstdc.a | C标准库 | 必须链接 |
| libsgx_tcxx.a | 标准C++库 | 可选 |
| libsgx_tservice.a | 数据密封/非密封(加密) | 当使用硬件模式时必须链接 |
| libsgx_tservice_sim.a | 数据密封/非密封(加密) | 当使用模拟模式时必须链接 |
| libsgx_tcrypto.a | 密码库 | 必须链接 |
| libsgx_tkey_exchange.a | 可信密钥交换库 | 可选的 |
| libsgx_tprotected.a | 受保护文件系统库 | 可选的 |
| libsgx_tswitchless.a | Switchless Enclave函数调用 | 可选的 |
| libsgx_pcl.a | 启用Intel®SGX保护代码加载器,以保护飞地代码机密性 | 可选的 |
| libsgx_pclsim.a | 使Intel®SGX保护代码加载器enclave代码保密(模拟模式) | 可选的 |
仿真库
Intel®SGX SDK提供模拟库,以模拟模式运行应用程序enclave (Intel®SGX硬件不需要)。他们是受信任模拟库和不受信任模拟库。不可信模拟库提供了不可信运行时库管理enclave所需的功能(与可信模拟库链接的enclave)。不可信模拟库提供了不可信运行时库管理与可信模拟库链接的enclave所需的功能,包括模拟在enclave外执行的Intel®SGX指令:ECREATE、EADD、EEXTEND、EINIT、erremove和EENTER。可信模拟库主要负责模拟可以在enclave内执行的Intel®SGX指令:EEXIT、EGETKEY和EREPORT。
Linking Application with Untrusted Libraries(链接应用程序与不受信任的库)
Intel®Software Guard Extensions SDK提供了以下不可信库的集合。
Table 10 Untrusted Libraries included in the Intel® SGX SDK
| Name | Description | Comment |
|---|---|---|
| libsgx_urts.so | 为应用程序提供管理enclave的功能 | 在HW模式下,运行时必须连接。libsgx_urts.so包含在Intel®SGX PSW(平台软件) |
| libsgx_urts_sim.so | uRTS库用于仿真模式,提供enclave和不受信任的应用程序对AEs提供的服务的访问 | 动态链接,在HW模式下运行时必须连接。包含在Intel®SGX PSW(平台软件) |
| libsgx_uae_service_sim.so | 在仿真模式,使用的不可信AE支持库 | 动态链接 |
| libsgx_ukey_exchange.a | 不可信的密钥交换库 | 可选的 |
| libsgx_uae_service.so | 提供enclave和不受信任的应用程序(对AEs提供服务)的访问 | 在HW模式下运行时必须连接。这个库正在逐步淘汰。我们推荐链接下面的库和根据应用程序需要的服务对应的头文件。 |
| libsgx_launch.so | 提供应用程序访问去发起由AE提供的服务 | 在HW模式下,运行时必须连接。 |
| libsgx_epid.so | 为应用程序提供对(AEs提供的EPID发放服务)的访问。在HW模式下,运行时必须连接。 | |
| libsgx_platform.so | 提供应用程序对平台服务(可信时间和单调计数器)的访问。 | 在HW模式下,运行时必须连接。 |
| libsgx_quote_ex.so | 在HW模式下,运行时必须连接。 |
Enclave Definition Language Syntax(Enclave定义语言语法)
Enclave定义语言(EDL)文件用于描述在函数原型中使用的Enclave受信任和不受信任的函数和类型。Edger8r Tool使用这个文件为enclave的导出(ECALL)和引入(OCALL)创建C包装函数。
EDL模板
enclave{
// Include files 引入文件
// Import other edl files 引入其他的edl文件
//为在edl文件中被用做函数原型参数做数据结构声明
trusted {
// Include header files if any,will be includedd in enclave_t.h
//Trusted function prototypes
};
untrusted {
// Include header files if any,will be included in enclave_u.h
// Untrusted function prototypes
};
};
每个EDL文件遵循这种通用格式:
enclave {
// An EDL file can optionally import functions from other EDL files
from "other/file.edl" import foo, bar; // 选择导入
from "another/file.edl" import *; // 引入所有函数
// Include C headers, these headers will be included in the generated files for both trusted and untrusted routines 包含C头文件,这些头文件将被包含在可信和不可信例程的生成文件中
include "string.h"
include "mytypes.h"
// Type definitions (struct, union, enum), 类型定义,可选
struct mysecret {
int key;
const char* text;
};
enum boolean { FALSE = 0, TRUE = 1 };
// Export functions (ECALLs), optional for library EDLs;导出函数(ECALLs),库edl可选
trusted {
//Include header files if any
//Will be included in enclave_t.h
//Trusted function prototypes
public void set_secret([in] struct mysecret* psecret);
void some_private_func(enum boolean b); // private ECALL(non-root ECALL).
};
// Import functions (OCALLs), optional
untrusted {
//Include header files if any
//Will be included in enclave_u.h
//Will be inserted in untrusted header file “untrusted.h”
//Untrusted function prototypes
// 这个OCALL不能进行其他的ECALL调用
void ocall_print();
// 这个OCALL可以ECALL调用函数“some_private_func”
int another_ocall([in] struct mysecret* psecret)
allow(some_private_func);
};
};
引入头文件
引入定义类型(C structs,unions,typedefs等等)的c语言头文件,否则如果这些类型在EDL中提及,自动生成的代码将不会被编译。引入的头文件可以是全局的也可以只属于可信函数或不可信函数。
一个全局引入的头文件不能意味着enclave和不可信应用程序代码包含着同样的头文件。在下面的例子中,enclave将使用来自Intel SGX SDK的stdio.h,而应用程序代码将使用主机编译器的stdio.h。
Example
enclave {
include “stdio.h” // global headers
include “../../util.h”
trusted {
include “foo.h” // for trusted functions only
};
untrusted {
include “bar.h” // for untrusted functions only
};
};
当开发人员将现有代码迁移到Intel SGX技术时,使用include指令很方便,因为在 case?中已经定义了数据类型。类似于其他接口语言,用户可以在EDL文件中定义数据类型,sgx_edger8r将生成一个包含数据类型定义的C头文件。有关EDL中受支持的数据类型列表,请参见基本类型。
Keywords
CAUTION
本主题中解释的指针属性只适用于ECALL和OCALL函数的形参,而不适用于ECALL或OCALL函数返回的指针。因此,边缘例程不会检查ECALL或OCALL函数返回的指针,而必须由enclave和应用程序代码验证。
Pointer Handling
[in] [out]作为方向属性。
- [in] — 当[in]被指定为指针参数时,参数将从调用过程传递给被调用过程。对于一个ECALL, in参数从应用程序传递到enclave;对于一个OCALL,参数从enclave传递到应用程序。
- [out] — 当[out]被指定为指针参数时,该参数将从被调用过程返回给调用过程。在ECALL函数中,一个out参数从enclave传递到应用程序,而一个OCALL函数将它从应用程序传递到enclave。
- [in]和[out]属性可以组合。在本例中,参数在两个方向上传递。
direction方向属性指示出可信边缘例程(可信桥和可信代理)复制指针指向的缓冲区。为了复制缓存区内容,可信边缘历程必须知道需要复制多少数据。由于这个原因,方向属性通常接着size或count的修饰符。如果这两者都没有被提供或指针为NULL,可信边缘例程假定计数为1。==当缓冲区被复制时,受信任的桥必须必须避免覆盖ECALL中的内存,受信任的代理必须避免在OCALL中泄露机密。==为了实现这个目标,作为ECALL的参数传递的指针必须指向不受信任的内存,而作为OCALL参数传递的指针必须指向受信任的内存。如果这些条件不满足,可信桥和可信代理将分别在运行时报告错误,并且不会执行ECALL和OCALL函数。
您也许可以使用direction属性以性能换取保护。否则,您必须使用下面描述的user_check属性,并在使用它之前通过指针验证从不可信内存中获得的数据,因为指针所指向的内存可能会发生意外变化,因为它存储在不可信内存中。但是,direction属性对包含指针的结构没有帮助。在这种情况下,必须亲自验证和复制缓冲区内容,如果需要,还必须递归地复制。或者,您可以定义一个可以深度复制的结构。有关更多信息,请参阅结构深度复制。
下表总结了包装器函数在使用in/out属性时的行为:
Table 12 Behavior of wrapper functions when using the in/out attributes
| - | ECALL | OCALL |
|---|---|---|
| user_check | 指针不被选中。用户必须进行检查和/或复制。 | 指针不被选中。用户必须进行检查和/或复制。 |
| in | 缓冲区从应用程序复制到enclave,之后,改变将只影响enclave的缓冲区。安全但是缓慢。 | 缓冲区从enclave到应用程序, 如果指针指向enclave数据必须被使用?。 |
| out | 受信任的包装器函数将分配一个缓冲区供enclave使用。在返回时,这个缓冲区将被复制到原始缓冲区。 | 不受信任的缓冲区将由受信任的包装器函数复制到enclave中。安全,但缓慢。 |
| in,out | 结合in和out的行为,数据被来回复制。 | 结合in和out的行为,数据被来回复制。 |
EDL不能分析C头文件中的C类型定义和宏。如果指针类型别名为没有星号(*)的类型/宏,EDL解析器可能会报告错误或没有正确地复制指针的数据。在这种情况下,使用[isptr]属性声明类型,以表明它是一个指针类型。
Example
//错误,PVOID不是EDL中的指针
void foo([in,size=4] PVOID buffer);
//正确
void foo([in,size=4] void* buffer);
//正确,“isptr”表明“PVOID”是指针类型
void foo([in, isptr, size=4] PVOID buffer);
// OK, opaque type, copy by value ???不明白
// Actual address must be in untrusted memory
void foo(HWND hWnd);
ECALLs中的指针控制
在ECALLs中,可信桥检查这个封送的数据结构没有与enclave内存重叠,并自动地在可信堆栈上分配空间去保存结构的拷贝。然后,他检查指针参数的全部范围是否与enclave内存重叠。当带有in属性的指向不受信任内存的指针传递给enclave时,可信桥在enclave内分配内存,并且将指针指向的内存从外部拷贝到enclave内存。当带有out属性的指向不受信任内存的指针传递到enclave时,受信任桥会在受信任内存中分配一个缓冲区,将缓冲区内容置零以清除任何以前的数据,并传递一个指向缓冲区的指针到受信任函数,在可信函数返回后,可信桥接器将可信缓冲区的内容复制到不可信内存中。当in和out属性组合在一起时,可信桥接器在enclave内分配内存,在可信函数被调用之前在可信内存中复制缓冲区,一旦可信函数返回,可信桥接器将可信缓冲区的内容复制到不可信内存。复制出的数据量与复制进来的数据量相同。
OCALLs中的指针控制
对于OCALLs,可信代理在外部堆栈上分配内存,以传递封送结构,并检查指针参数的全部范围在enclave里。当带有in属性的指向受信任内存的指针从enclave中被传递时,受信任代理将在enclave外部分配内存,并将指针指向的内存从enclave内部复制到不受信任内存。当一个指向带有out属性的可信内存的指针从一个enclave中被传递时,可信代理将在不可信堆栈上分配一个缓冲区,并将这个缓冲区的指针传递给不可信函数。在不可信函数返回后,可信代理将不可信的缓冲区的内容复制到可信内存中。在in和out属性结合时,信任代理在enclave外分配内存,在调用不可信函数之前在不可信内存中复制缓冲区,在不可信函数返回后,可信代理将不可信缓冲区的内容复制到可信内存中。复制出的数据量与复制进来的数据量相同。
Attribute: user_check
在某些情况下,direction(in/out)属性施加的限制可能不支持应用程序跨越enclave边界进行数据通信的需求。例如,缓冲区可能太大,无法装入enclave内存,需要将缓冲区分成更小的块,然后在一系列ECALLs中处理这些块,或者应用程序可能需要将指针作为ECALL参数传递给可信内存enclave。
为了支持这些特定的场景,EDL语言提供了user_ check属性。使用user_check属性声明的参数不会经历[in]和[out]属性所描述的任何检查。 (不进行检验,但缓冲区太大还是装不进enclave,为什么说user_check能支持上述场景呢)?但是,您必须理解将指针传入和传递出enclave(一般来说)的风险,特别是user_check属性。您必须确保所有指针检查和数据复制都正确完成,否则可能会危及飞地机密。
缓冲区大小计算
使用以下属性计算缓冲区大小的通用公式:总字节数 = count * size
- 当count和size都指定时,上面的公式成立。
- 如果指针参数没有指定count,则假定count等于1,即count=1。那么总字节数等于size。
- 如果没有指定size,则使用上面的公式计算缓冲区大小,其中size为sizeof(指针所指向的元素)。
属性:size
size属性依赖方向属性,size可以用于以字节为单位表明用于复制的缓冲区的大小(当没有指定count时)。方向属性是必须的,因为 可信桥接器需要知道作为指针传送的缓冲区的整个范围,以确保他不会与enclave内存重叠?,并根据方向属性从不可信内存复制内容到可信内存,或从可信内存复制内容到不可信内存。size可以是整型常量,也可以是函数的参数之一。size属性通常用于void指针。
Example
enclave{
trusted {
// Copies '100' bytes
public void test_size1([in, size=100] void* ptr, size_t len);
// Copies ‘len’ bytes
public void test_size2([in, size=len] void* ptr, size_t len);
};
};
Unsupported Syntax
enclave{
trusted {
// size /count属性必须伴随使用指针方向属性([in, out])
void test_attribute_cant([size=len] void* ptr, size_t len);
};
};
属性:count
count属性用于表明依赖方向属性进行复制的指针指向的sizeof元素块,该块以字节为单位。受信任桥或受信任代理复制的字节数是count和参数所指向的数据类型size的乘积。count可以是整型常量,也可以是函数的参数之一。
Example
enclave{
trusted {
// Copies cnt * sizeof(int) bytes (size为sizeof(指针所指向的元素),sizeof是什么呢?)
public void test_count([in, count=cnt] int* ptr, unsigned cnt);
// Copies cnt * len bytes
public void test_count_size([in, count=cnt, size=len] int*ptr, unsigned cnt, size_t len);
};
};
属性:Strings
string和wstring属性分别表示该参数是一个NULL结尾的C字符串或是一个NULL结尾的wchar_t字符串。为了防止 “先检查后使用”类型的攻击?,可信边例程首先在不可信内存中操作,以确定字符串的长度。一旦将字符串复制到enclave中,可信桥显式地终止该字符串。在可信内存中分配的缓冲区大小决定了第一步中确定的长度以及字符串终止字符的大小。
string和wstring属性的使用有一些限制:
- string和wstring不能与其他修饰符组合,如size或者count。
- string和wstring不能与out单独使用。然而,string和wstring与in和out一起使用是被允许的。
- string只用被用于char指针,而wstring只能被用于wchar_t指针。
Example
enclave{
trusted{
//string/wstring与[out]不能单独使用,使用[in]或者[in,out]是允许的
public void test_string([in,out,string] char* str);
public void test_wstring([in,out,wstring] char* wstr);
public void test_const_string([in,string] const char* str);
};
};
Unsupported Syntax:
enclave {
include "user_types.h" //for typedef void const * pBuf2;
trusted {
// string/wstring 必须与方向属性一起使用
void test_string_cant([string] char* ptr);
void test_string_cant_usercheck([user_check, string] char* ptr);
// string/wstring 属性不能只与[out] 属性使用
void test_string_out([out, string] char* str);
// string/wstring属性必须被用于char/wchar_t指针
void test_string_out([in, string] void* str);
};
};
Structures,Enums and Unions
基本类型和用户定义的数据类型可以在结构/联合内部使用,除了它在以下方面与标准不同:
Unsupported Syntax:
enclave{
// 1. 结构的每个成员必须分别定义
struct data_def_t{
int a, b, c; // 不允许, 它必须是 int a; int b; int c;
};
// 2位域在结构/联合中是不允许的。
struct bitfields_t{
short i : 3;
short j : 6;
short k : 7;
};
//3.不允许嵌套结构定义
struct my_struct_t{
int out_val;
float out_fval;
struct inner_struct_t{
int in_val;
float in_fval;
};
};
};
Valid Syntax:
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_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);
};
};
结构深拷贝
结构中的成员指针可以用缓冲区大小属性size或count去表明深度拷贝而不是浅拷贝。当可信边缘例程拷贝指向结构指针的缓冲区时,他们也复制由结构指针的方向属性指示的结构成员指针所指向的缓冲区(也复制其指向结构成员的指针指向的缓冲区)。成员指针值也会相应的修改。
该结构的缓冲区大小必须是结构大小的倍数,并且该缓冲区作为结构数组进行深度拷贝。由于按值调用的函数进行浅拷贝,所以不允许按值调用结构深拷贝。深拷贝结构指针的方向属性可以是[in]和[in,out]。如果成员指针不是基本类型,可信边缘例程不会递归地深度拷贝它。
Example
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[]在不信任域的地址/指针)
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}
NOTE:
当在OCALL中深度复制带有in属性的指针参数时,结构中的指针(即可信域的地址)会短暂地复制到不可信域。如果地址是敏感数据,请避免这种情况。

被折叠的 条评论
为什么被折叠?



