文章目录
一. 引言
SampleEnclave作为enclave开发的基础示例,主要包括enclave的一些基础用法介绍,本文将结合这个示例从中学习SGX的基本使用方法。关于 SGX 开发运行环境的搭建可参考之前的一篇博客:【SGX系列教程】(一)。
二. README
------------------------
Purpose of SampleEnclave
------------------------
The project demonstrates several fundamental usages of Intel(R) Software Guard
Extensions (Intel(R) SGX) SDK:
- Initializing and destroying an enclave
- Creating ECALLs or OCALLs
- Calling trusted libraries inside the enclave
------------------------------------
How to Build/Execute the Sample Code
------------------------------------
1. Install Intel(R) SGX SDK for Linux* OS
2. Enclave test key(two options):
a. Install openssl first, then the project will generate a test key<Enclave_private_test.pem> automatically when you build the project.
b. Rename your test key(3072-bit RSA private key) to <Enclave_private_test.pem> and put it under the <Enclave> folder.
3. Make sure your environment is set:
$ source ${sgx-sdk-install-path}/environment
4. Build the project with the prepared Makefile:
a. Hardware Mode, Debug build:
1) Enclave with no mitigation:
$ make
2) Enclave with mitigations for indirects and returns only:
$ make MITIGATION-CVE-2020-0551=CF
3) Enclave with full mitigation:
$ make MITIGATION-CVE-2020-0551=LOAD
b. Hardware Mode, Pre-release build:
1) Enclave with no mitigation:
$ make SGX_PRERELEASE=1 SGX_DEBUG=0
2) Enclave with mitigations for indirects and returns only:
$ make SGX_PRERELEASE=1 SGX_DEBUG=0 MITIGATION-CVE-2020-0551=CF
3) Enclave with full mitigation:
$ make SGX_PRERELEASE=1 SGX_DEBUG=0 MITIGATION-CVE-2020-0551=LOAD
c. Hardware Mode, Release build:
1) Enclave with no mitigation:
$ make SGX_DEBUG=0
2) Enclave with mitigations for indirects and returns only:
$ make SGX_DEBUG=0 MITIGATION-CVE-2020-0551=CF
3) Enclave with full mitigation:
$ make SGX_DEBUG=0 MITIGATION-CVE-2020-0551=LOAD
d. Simulation Mode, Debug build:
$ make SGX_MODE=SIM
e. Simulation Mode, Pre-release build:
$ make SGX_MODE=SIM SGX_PRERELEASE=1 SGX_DEBUG=0
f. Simulation Mode, Release build:
$ make SGX_MODE=SIM SGX_DEBUG=0
5. Execute the binary directly:
$ ./app
6. Remember to "make clean" before switching build mode
------------------------------------------
Explanation about Configuration Parameters
------------------------------------------
TCSMaxNum, TCSNum, TCSMinPool
These three parameters will determine whether a thread will be created
dynamically when there is no available thread to do the work.
StackMaxSize, StackMinSize
For a dynamically created thread, StackMinSize is the amount of stack available
once the thread is created and StackMaxSize is the total amount of stack that
thread can use. The gap between StackMinSize and StackMaxSize is the stack
dynamically expanded as necessary at runtime.
For a static thread, only StackMaxSize is relevant which specifies the total
amount of stack available to the thread.
HeapMaxSize, HeapInitSize, HeapMinSize
HeapMinSize is the amount of heap available once the enclave is initialized.
HeapMaxSize is the total amount of heap an enclave can use. The gap between
HeapMinSize and HeapMaxSize is the heap dynamically expanded as necessary
at runtime.
HeapInitSize is here for compatibility.
-------------------------------------------------
Sample configuration files for the Sample Enclave
-------------------------------------------------
With below configurations, if the signed enclave is launched on a SGX2 platform
with SGX2 supported kernel, it will be loaded with EDMM enabled. Otherwise, it
will behave in way of SGX1.
config.01.xml: There is no dynamic thread, no dynamic heap expansion.
config.02.xml: There is no dynamic thread. But dynamic heap expansion can happen.
config.03.xml: There are dynamic threads. For a dynamic thread, there's no stack expansion.
config.04.xml: There are dynamic threads. For a dynamic thread, stack will expanded as necessary.
Below configuration is only workable on a SGX2 platform with SGX2 supported kernel:
config.05.xml: There is a user region where users could operate on.
-------------------------------------------------
Launch token initialization
-------------------------------------------------
If using libsgx-enclave-common or sgxpsw under version 2.4, an initialized variable launch_token needs to be passed as the 3rd parameter of API sgx_create_enclave. For example,
sgx_launch_token_t launch_token = {
0};
sgx_create_enclave(ENCLAVE_FILENAME, SGX_DEBUG_FLAG, launch_token, NULL, &global_eid, NULL);
根据上面的README,我们可以分析出SampleEnclave项目的目的和构建/执行步骤,有助于理解项目的实现流程。
2.1 项目目的
SampleEnclave项目演示了Intel® SGX SDK的一些基本使用方法,其主要目的包括:
- 初始化和销毁enclave;
- 创建
ECALLs(Enclave Calls)
和OCALLs(Outside Calls)
; - 调用enclave内的受信任库
2.2 构建和执行示例代码的步骤
- 安装Intel® SGX SDK for Linux OS
这是项目构建和运行的基本前提,需要确保已正确安装SGX SDK
,具体安装教程参考第一篇博客。 - 准备Enclave测试密钥(两种方式)
a. 安装openssl
,然后在构建项目时会自动生成一个测试密钥Enclave_private_test.pem
。
b. 使用你自己的测试密钥(3072位RSA私钥),将其重命名为Enclave_private_test.pem
并放入文件夹中。 - 设置环境变量:
source ${sgx-sdk-install-path}/environment
。 - 构建项目,根据不同的模式和配置进行构建,具体参考上述README。
- 直接执行生成的二进制文件
./app
- 切换构建模式前请记得清理构建
make clean
2.3 配置参数解释
- 线程配置参数
TCSMaxNum
,TCSNum
,TCSMinPool
:这三个参数决定是否在没有可用线程工作时动态创建一个线程。 - 栈配置参数
StackMaxSize
,StackMinSize
:对于动态创建的线程,StackMinSize
是线程创建时的栈大小,StackMaxSize
是线程可以使用的最大栈空间。对于静态线程,只需要设置StackMaxSize
。 - 堆配置参数
HeapMaxSize
,HeapInitSize
,HeapMinSize
:HeapMinSize 是enclave初始化时的堆大小,HeapMaxSize 是enclave可以使用的最大堆空间,HeapInitSize 是为了兼容而设置。
2.4 配置文件分析
对于不同的SGX平台(SGX1和SGX2)和配置,enclave的行为会有所不同:
- SGX1平台的行为:
config.01.xml
:无动态线程,无动态堆扩展。
config.02.xml
:无动态线程,但动态堆扩展可以发生。
config.03.xml
:有动态线程,但没有栈扩展。
config.04.xml
:有动态线程,栈可以根据需要扩展。 - 仅在SGX2平台上有效的配置:
config.05.xml
:有一个用户区域,用户可以对其进行操作。
2.5 启动令牌初始化
如果使用版本低于2.4(最新安装的都不会)的libsgx-enclave-common
或sgxpsw
,需要传递一个初始化的启动令牌变量作为第三个参数给API sgx_create_enclave
,例如:
sgx_launch_token_t launch_token = {
0};
sgx_create_enclave(ENCLAVE_FILENAME, SGX_DEBUG_FLAG, launch_token, NULL, &global_eid, NULL);
总结
SampleEnclave
项目展示了如何使用Intel® SGX SDK进行基本的enclave操作,包括初始化和销毁enclave、创建ECALL和OCALL、以及调用enclave内的受信任库。通过详细的构建和执行步骤、配置参数解释及各种配置文件的分析,可以帮助用户更好地理解SGX的基本使用方法和配置技巧。
三. 重点代码分析
文件目录如下图,App侧与Enclave侧的程序一一对应,其中App表示REE侧应用程序,Enclave表示enclave侧Ecall程序。
3.1 App文件夹
3.1.1 App/App.cpp
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <unistd.h>
#include <pwd.h>
#define MAX_PATH FILENAME_MAX
#include "sgx_urts.h" // 包含SGX User Runtime库
#include "App.h" // 包含应用程序自定义头文件
#include "Enclave_u.h" // 包含Enclave生成的头文件,用于与Enclave进行交互
/* 全局EID由多个线程共享 */
sgx_enclave_id_t global_eid = 0;
/* 定义一个错误列表,用于记录sgx_create_enclave返回的错误码 */
typedef struct _sgx_errlist_t {
sgx_status_t err;
const char *msg; // 错误信息
const char *sug; // 建议
} sgx_errlist_t;
/* sgx_create_enclave可能返回的错误码及其对应信息 */
static sgx_errlist_t sgx_errlist[] = {
{
SGX_ERROR_UNEXPECTED, "Unexpected error occurred.", NULL },
{
SGX_ERROR_INVALID_PARAMETER, "Invalid parameter.", NULL },
{
SGX_ERROR_OUT_OF_MEMORY, "Out of memory.", NULL },
{
SGX_ERROR_ENCLAVE_LOST, "Power transition occurred.", "Please refer to the sample \"PowerTransition\" for details." },
{
SGX_ERROR_INVALID_ENCLAVE, "Invalid enclave image.", NULL },
{
SGX_ERROR_INVALID_ENCLAVE_ID, "Invalid enclave identification.", NULL },
{
SGX_ERROR_INVALID_SIGNATURE, "Invalid enclave signature.", NULL },
{
SGX_ERROR_OUT_OF_EPC, "Out of EPC memory.", NULL },
{
SGX_ERROR_NO_DEVICE, "Invalid SGX device.", "Please make sure SGX module is enabled in the BIOS, and install SGX driver afterwards." },
{
SGX_ERROR_MEMORY_MAP_CONFLICT, "Memory map conflicted.", NULL },
{
SGX_ERROR_INVALID_METADATA, "Invalid enclave metadata.", NULL },
{
SGX_ERROR_DEVICE_BUSY, "SGX device was busy.", NULL },
{
SGX_ERROR_INVALID_VERSION, "Enclave version was invalid.", NULL },
{
SGX_ERROR_INVALID_ATTRIBUTE, "Enclave was not authorized.", NULL },
{
SGX_ERROR_ENCLAVE_FILE_ACCESS, "Can't open enclave file.", NULL },
{
SGX_ERROR_MEMORY_MAP_FAILURE, "Failed to reserve memory for the enclave.", NULL },
};
/* 检查加载enclave的错误条件 */
void print_error_message(sgx_status_t ret)
{
size_t idx = 0;
size_t ttl = sizeof sgx_errlist/sizeof sgx_errlist[0];
for (idx = 0; idx < ttl; idx++) {
if(ret == sgx_errlist[idx].err) {
if(NULL != sgx_errlist[idx].sug)
printf("信息: %s\n", sgx_errlist[idx].sug);
printf("错误: %s\n", sgx_errlist[idx].msg);
break;
}
}
if (idx == ttl)
printf("错误代码是 0x%X. 请参考 \"Intel SGX SDK Developer Reference\" 获取更多详情。\n", ret);
}
/* 初始化enclave:
* 调用sgx_create_enclave来初始化一个enclave实例
*/
int initialize_enclave(void)
{
sgx_status_t ret = SGX_ERROR_UNEXPECTED;
/* 调用sgx_create_enclave来初始化一个enclave实例 */
/* 调试支持: 将第二个参数设置为1 */
ret = sgx_create_enclave(ENCLAVE_FILENAME, SGX_DEBUG_FLAG, NULL, NULL, &global_eid, NULL);
if (ret != SGX_SUCCESS) {
print_error_message(ret);
return -1;
}
return 0;
}
/* OCall函数 */
void ocall_print_string(const char *str)
{
/* 代理将会检查字符串的长度并在结尾添加空字符,防止缓冲区溢出 */
printf("%s", str);
}
/* 应用程序入口 */
int SGX_CDECL main(int argc, char *argv[])
{
(void)(argc);
(void)(argv);
/* 初始化enclave */
if(initialize_enclave() < 0){
printf("在退出前按下任意键 ...\n");
getchar();
return -1;
}
/* 使用edger8r生成的属性调用 */
edger8r_array_attributes();
edger8r_pointer_attributes();
edger8r_type_attributes();
edger8r_function_attributes();
/* 使用受信任的库函数 */
ecall_libc_functions();
ecall_libcxx_functions();
ecall_thread_functions();
/* 销毁enclave */
sgx_destroy_enclave(global_eid);
printf("信息: SampleEnclave成功返回。\n");
printf("在退出前按下任意键 ...\n");
getchar();
return 0;
}
代码功能分析
- 初始化与配置
头文件与库的引用:
#include “sgx_urts.h”:包含SGX User Runtime库的头文件。
#include “App.h”:包含应用程序自定义头文件。
#include “Enclave_u.h”:包含enclave生成的头文件,用于与enclave进行交互。
全局变量:
sgx_enclave_id_t global_eid:用于存储enclave的全局ID,由多个线程共享。
错误处理:
sgx_errlist_t:定义错误列表结构,用于存储错误码、错误信息和建议。
sgx_errlist:包含sgx_create_enclave可能返回的错误码及其对应的错误信息和建议。
print_error_message:根据错误码打印相应的错误信息和建议。 - 初始化enclave
initialize_enclave:
调用 sgx_create_enclave 函数创建enclave实例,并将得到的enclave ID存储在 global_eid 中。
如果创建enclave失败,则调用 print_error_message 打印错误信息,并返回-1。 - OCall函数
ocall_print_string:
将传入的字符串打印到标准输出。(在OCall过程中,代理会检查字符串长度并在字符串结尾添加空字符,以防止缓冲区溢出)。 - 应用程序入口
main函数:
初始化enclave:调用 initialize_enclave 函数初始化enclave。如果失败则打印错误信息并等待用户输入。
调用edger8r生成的属性和函数:
edger8r_array_attributes
edger8r_pointer_attributes
edger8r_type_attributes
edger8r_function_attributes
调用受信任的库函数:
ecall_libc_functions
ecall_libcxx_functions
ecall_thread_functions
销毁enclave:调用 sgx_destroy_enclave 函数销毁enclave实例。
程序结束提示:显示提示信息,用户按任意键后退出程序。
总结
通过这段代码,实现了SampleEnclave项目的核心功能,包括初始化和销毁enclave、调用ECALL和OCALL、以及使用enclave内的受信任库。每一步操作都有详细的错误处理和用户提示,确保了代码的鲁棒性和易用性。
3.1.2 App/Edger8rSyntax文件夹
Edger8r
是SGX的一部分,是可信和不可信部分的边界层,用来提供一些在不可信的应用和enclave之间的一些边界路径。Edger8r在编译的时候自动执行但是一些高级的enclave开发人员会手动调用Edger8r。
3.1.2.1 App/Edger8rSyntax/Arrays.cpp
#include "../App.h" // 包含应用程序的头文件
#include "Enclave_u.h" // 包含Enclave生成的头文件,用于与Enclave进行交互
/* edger8r_array_attributes:
* 调用声明了数组属性的ECALL
*/
void edger8r_array_attributes(void)
{
sgx_status_t ret = SGX_ERROR_UNEXPECTED;
/* user_check */
int arr1[4] = {
0, 1, 2, 3}; // 初始化数组arr1
ret = ecall_array_user_check(global_eid, arr1); // 调用ecall_array_user_check函数
if (ret != SGX_SUCCESS)
abort(); // 如果调用失败,终止程序
/* 确认arr1被修改 */
for (int i = 0; i < 4; i++)
assert(arr1[i] == (3 - i)); // 检查arr1的每个元素是否变成3-i
/* in */
int arr2[4] = {
0, 1, 2, 3}; // 初始化数组arr2
ret = ecall_array_in(global_eid, arr2); // 调用ecall_array_in函数
if (ret != SGX_SUCCESS)
abort(); // 如果调用失败,终止程序
/* 确认arr2未被修改 */
for (int i = 0; i < 4; i++)
assert(arr2[i] == i); // 检查arr2的每个元素是否未改变
/* out */
int arr3[4] = {
0, 1, 2, 3}; // 初始化数组arr3
ret = ecall_array_out(global_eid, arr3); // 调用ecall_array_out函数
if (ret != SGX_SUCCESS)
abort(); // 如果调用失败,终止程序
/* 确认arr3被修改 */
for (int i = 0; i < 4; i++)
assert(arr3[i] == (3 - i)); // 检查arr3的每个元素是否变成3-i
/* in, out */
int arr4[4] = {
0, 1, 2, 3}; // 初始化数组arr4
ret = ecall_array_in_out(global_eid, arr4); // 调用ecall_array_in_out函数
if (ret != SGX_SUCCESS)
abort(); // 如果调用失败,终止程序
/* 确认arr4被修改 */
for</