文章目录
- 一. 引言
- 二. README
- 2.1 项目目的
- 2.2 构建和执行示例代码的步骤
- 2.3 配置参数解释
- 2.4 配置文件分析
- 2.5 启动令牌初始化
- 三. 重点代码分析
- 3.1 App文件夹
- 3.1.1 App/App.cpp
- 3.1.2 App/Edger8rSyntax文件夹
- 3.1.2.1 App/Edger8rSyntax/Arrays.cpp
- 3.1.2.2 App/Edger8rSyntax/Functions.cpp
- 3.1.2.3 App/Edger8rSyntax/Pointers.cpp
- 3.1.2.4 App/Edger8rSyntax/Types.cpp
- 3.1.3 App/TrustedLibrary文件夹
- 3.1.3.1 App/TrustedLibrary/Libc.cpp
- 3.1.3.2 App/TrustedLibrary/Libcxx.cpp
- 3.1.3.3 App/TrustedLibrary/Thread.cpp
- 3.2 Enclave文件夹
- 3.2.1 Enclave/Enclave.cpp
- 3.2.2 Enclave/Edger8rSyntax文件夹
- 3.2.2.1 Enclave/Edger8rSyntax/Arrays.cpp
- 3.2.2.2 Enclave/Edger8rSyntax/Functions.cpp
- 3.2.2.3 Enclave/Edger8rSyntax/Pointers.cpp
- 3.2.2.4 Enclave/Edger8rSyntax/Types.cpp
- 3.2.3 Enclave/TrustedLibrary文件夹
- 3.2.3.1 Enclave/TrustedLibrary/Libc.cpp
- 3.2.3.2 Enclave/TrustedLibrary/Libcxx.cpp
- 3.2.3.3 Enclave/TrustedLibrary/Thread.cpp
- 四. 感谢支持
一. 引言
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 (int i = 0; i < 4; i++)
assert(arr4[i] == (3 - i)); // 检查arr4的每个元素是否变成3-i
/* isary */
array_t arr5 = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; // 初始化数组arr5
ret = ecall_array_isary(global_eid, arr5); // 调用ecall_array_isary函数
if (ret != SGX_SUCCESS)
abort(); // 如果调用失败,终止程序
/* 确认arr5被修改 */
for (int i = 0; i < 10; i++)
assert(arr5[i] == (9 - i)); // 检查arr5的每个元素是否变成9-i
}
该函数 edger8r_array_attributes
展示了如何通过SGX ECALL
调用将数组传递给enclave进行处理,并在处理后检查数组是否按照预期进行了修改。函数中分别展示了四种不同的数组传递方式(user_check
, in
, out
, in_out
),并通过assert
语句对修改后的数组进行验证。此示例演示了如何在SGX环境中进行数组相关的ECALL调用和数据验证。
3.1.2.2 App/Edger8rSyntax/Functions.cpp
#include "../App.h" // 包含应用程序的头文件
#include "Enclave_u.h" // 包含Enclave生成的头文件,用于与Enclave进行交互
/* edger8r_function_attributes:
* 调用使用调用约定属性声明的ECALL。
* 调用使用[public]声明的ECALL。
*/
void edger8r_function_attributes(void)
{
sgx_status_t ret = SGX_ERROR_UNEXPECTED;
// 调用公开的函数ecall_function_public
ret = ecall_function_public(global_eid);
if (ret != SGX_SUCCESS)
abort(); // 如果调用失败,终止程序
// 用户不应该在这里调用私有函数
int runned = 0;
ret = ecall_function_private(global_eid, &runned);
// 确保返回值是SGX_ERROR_ECALL_NOT_ALLOWED,并且函数未被运行
if (ret != SGX_ERROR_ECALL_NOT_ALLOWED || runned != 0)
abort(); // 如果检查失败,终止程序
}
/* ocall_function_allow:
* 该OCALL调用被[allow]属性批准的edger8r_private。
*/
void ocall_function_allow(void)
{
int runned = 0;
sgx_status_t ret = SGX_ERROR_UNEXPECTED;
// 调用私有函数ecall_function_private
ret = ecall_function_private(global_eid, &runned);
// 确保返回值是SGX_SUCCESS,并且函数已被运行
if (ret != SGX_SUCCESS || runned != 1)
abort(); // 如果检查失败,终止程序
}
这段代码展示了如何调用SGX ECALL时处理不同的函数属性:
edger8r_function_attributes
函数展示了如何调用具有公开属性的ECALL函数,以及检查并防止直接调用私有的ECALL函数。ocall_function_allow
函数展示了如何通过OCALL调用被允许的私有ECALL函数。
通过这些示例,可以清楚地了解如何在SGX环境中管理和调用具有不同属性的ECALL函数,以确保代码的安全性和正确性。
3.1.2.3 App/Edger8rSyntax/Pointers.cpp
#include "../App.h" // 包含应用程序的头文件
#include "Enclave_u.h" // 包含Enclave生成的头文件,用于与Enclave进行交互
/* edger8r_pointer_attributes:
* 调用声明了指针属性的ECALL。
*/
void edger8r_pointer_attributes(void)
{
int val = 0;
sgx_status_t ret = SGX_ERROR_UNEXPECTED;
// 定义字符数组和长度变量
char c[128] = {0};
size_t len = 0;
// 初始化字符数组c
memset(c, 0xe, 128);
ret = ecall_pointer_user_check(global_eid, &len, &c, 128); // 调用ecall_pointer_user_check函数
if (ret != SGX_SUCCESS)
abort(); // 如果调用失败,终止程序
assert(strcmp(c, "SGX_SUCCESS") == 0); // 检查c是否被更改为"SGX_SUCCESS"
// 测试[in]指针属性
val = 1;
ret = ecall_pointer_in(global_eid, &val); // 调用ecall_pointer_in函数
if (ret != SGX_SUCCESS)
abort(); // 如果调用失败,终止程序
assert(val == 1); // 确保val的值未被修改
// 测试[out]指针属性
val = 1;
ret = ecall_pointer_out(global_eid, &val); // 调用ecall_pointer_out函数
if (ret != SGX_SUCCESS)
abort(); // 如果调用失败,终止程序
assert(val == 1234); // 确保val的值被修改为1234
// 测试[in, out]指针属性
val = 1;
ret = ecall_pointer_in_out(global_eid, &val); // 调用ecall_pointer_in_out函数
if (ret != SGX_SUCCESS)
abort(); // 如果调用失败,终止程序
assert(val == 1234); // 确保val的值被修改为1234
ret = ocall_pointer_attr(global_eid); // 调用OCALL函数ocall_pointer_attr
if (ret != SGX_SUCCESS)
abort(); // 如果调用失败,终止程序
// 测试字符串指针
char str1[] = "1234567890";
ret = ecall_pointer_string(global_eid, str1); // 调用ecall_pointer_string函数
if (ret != SGX_SUCCESS)
abort(); // 如果调用失败,终止程序
assert(strlen(str1) == 10 && memcmp(str1, "0987654321", strlen(str1)) == 0); // 确保str1的值被正确修改
// 测试字符串指针常量
const char str2[] = "1234567890";
ret = ecall_pointer_string_const(global_eid, str2); // 调用ecall_pointer_string_const函数
if (ret != SGX_SUCCESS)
abort(); // 如果调用失败,终止程序
assert(strlen(str2) == 10 && memcmp(str2, "1234567890", strlen(str2)) == 0); // 确保str2未被修改
// 测试指针大小
char str3[] = "1234567890";
ret = ecall_pointer_size(global_eid, (void*)str3, strlen(str3)); // 调用ecall_pointer_size函数
if (ret != SGX_SUCCESS)
abort(); // 如果调用失败,终止程序
assert(strlen(str3) == 10 && memcmp(str3, "0987654321", strlen(str3)) == 0); // 确保str3的值被正确修改
// 测试只读指针
char str4[] = "1234567890";
ret = ecall_pointer_isptr_readonly(global_eid, (buffer_t)str4, strlen(str4)); // 调用ecall_pointer_isptr_readonly函数
if (ret != SGX_SUCCESS)
abort(); // 如果调用失败,终止程序
assert(strlen(str4) == 10 && memcmp(str4, "1234567890", strlen(str4)) == 0); // 确保str4未被修改
// 测试带计数的指针
int arr[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
ret = ecall_pointer_count(global_eid, arr, 10); // 调用ecall_pointer_count函数
if (ret != SGX_SUCCESS)
abort(); // 如果调用失败,终止程序
for (int i = 0; i < 10; i++)
assert(arr[i] == (9 - i)); // 确保arr的值被正确修改为9-i
return;
}
/* ocall_pointer_user_check:
* 声明为[user_check]的OCALL。
*/
void ocall_pointer_user_check(int* val)
{
(void)val; // 安静未使用的参数警告
assert(val != NULL); // 确保指针val不为NULL
}
/* ocall_pointer_in:
* 声明为[in]的OCALL。
*/
void ocall_pointer_in(int* val)
{
*val = 1234; // 将指针val指向的值设置为1234
}
/* ocall_pointer_out:
* 声明为[out]的OCALL。
*/
void ocall_pointer_out(int* val)
{
*val = 1234; // 将指针val指向的值设置为1234
}
/* ocall_pointer_in_out:
* 声明为[in, out]的OCALL。
*/
void ocall_pointer_in_out(int* val)
{
*val = 1234; // 将指针val指向的值设置为1234
}
ECALL函数总结
在函数 edger8r_pointer_attributes
中,多个不同的ECALL被调用来测试SGX环境下的指针操作。这些ECALL通过不同的指针属性(如 user_check
, in
, out
, in, out
,以及字符串指针)来演示如何在enclave内部和外部之间传递和操作数据。每次调用都使用 assert 检查数据是否已按预期进行了修改,以验证指针操作的正确性。
OCALL函数总结
- ocall_pointer_user_check
验证传入的指针是否为非空指针,以确保数据完整性。 - ocall_pointer_in
将传入的指针值设置为 1234,用于模拟出站(outbound)数据的例子。 - ocall_pointer_out
将传入的指针值设置为 1234,与 ocall_pointer_in 类似,但用于表示入站(inbound)数据。 - ocall_pointer_in_out
同时设置传入指针值为 1234,展示了入站和出站指针的双向操作。
通过这个文件,SampleEnclave项目展示了用于实现不同指针操作的ECALL和OCALL方法。这些示例确保了在enclave内进行指针操作时的安全性和正确性,同时也增强了对指针的使用和数据传递机制的理解。
3.1.2.4 App/Edger8rSyntax/Types.cpp
#include "../App.h" // 包含应用程序的头文件
#include "Enclave_u.h" // 包含Enclave生成的头文件,用于与Enclave进行交互
/* edger8r_type_attributes:
* 调用声明了基本类型的ECALL。
*/
void edger8r_type_attributes(void)
{
sgx_status_t ret = SGX_ERROR_UNEXPECTED;
// 调用使用char类型的ECALL
ret = ecall_type_char(global_eid, (char)0x12); // 传递一个char类型的值0x12
if (ret != SGX_SUCCESS)
abort(); // 如果调用失败,终止程序
// 调用使用int类型的ECALL
ret = ecall_type_int(global_eid, (int)1234); // 传递一个int类型的值1234
if (ret != SGX_SUCCESS)
abort(); // 如果调用失败,终止程序
// 调用使用float类型的ECALL
ret = ecall_type_float(global_eid, (float)1234.0); // 传递一个float类型的值1234.0
if (ret != SGX_SUCCESS)
abort(); // 如果调用失败,终止程序
// 调用使用double类型的ECALL
ret = ecall_type_double(global_eid, (double)1234.5678); // 传递一个double类型的值1234.5678
if (ret != SGX_SUCCESS)
abort(); // 如果调用失败,终止程序
// 调用使用size_t类型的ECALL
ret = ecall_type_size_t(global_eid, (size_t)12345678); // 传递一个size_t类型的值12345678
if (ret != SGX_SUCCESS)
abort(); // 如果调用失败,终止程序
// 调用使用wchar_t类型的ECALL
ret = ecall_type_wchar_t(global_eid, (wchar_t)0x1234); // 传递一个wchar_t类型的值0x1234
if (ret != SGX_SUCCESS)
abort(); // 如果调用失败,终止程序
// 创建并初始化一个struct_foo_t类型的结构体
struct struct_foo_t g = {1234, 5678};
ret = ecall_type_struct(global_eid, g); // 传递一个结构体g
if (ret != SGX_SUCCESS)
abort(); // 如果调用失败,终止程序
// 创建并初始化一个union_union_foo_t类型的联合体
union union_foo_t val = {0};
ret = ecall_type_enum_union(global_eid, ENUM_FOO_0, &val); // 传递一个枚举值ENUM_FOO_0和一个联合体val
if (ret != SGX_SUCCESS)
abort(); // 如果调用失败,终止程序
// 确认联合体val的值是否被正确修改
assert(val.union_foo_0 == 2);
}
这段代码展示了如何通过SGX调用包含基本类型的ECALL。函数 edger8r_type_attributes
分别演示了如何传递 char
、int
、float
、double
、size_t
、wchar_t
类型的数据,以及如何传递 struct
和 union
类型的数据。通过检查每次ECALL的返回值,代码确保了ECALL调用的正确性,同时也演示了如何在SGX环境中进行各种数据类型的操作。
3.1.3 App/TrustedLibrary文件夹
3.1.3.1 App/TrustedLibrary/Libc.cpp
#include "../App.h" // 包含应用程序的头文件
#include "Enclave_u.h" // 包含Enclave生成的头文件,用于与Enclave进行交互
/* ecall_libc_functions:
* 调用标准C函数。
*/
void ecall_libc_functions(void)
{
sgx_status_t ret = SGX_ERROR_UNEXPECTED;
// 调用ECALL来演示malloc和free的使用
ret = ecall_malloc_free(global_eid); // 调用malloc和free的ECALL
if (ret != SGX_SUCCESS)
abort(); // 如果调用失败,终止程序
// 初始化cpuid数组
int cpuid[4] = {0x0, 0x0, 0x0, 0x0};
// 调用ECALL来获取CPUID信息
ret = ecall_sgx_cpuid(global_eid, cpuid, 0x0); // 调用获取CPUID信息的ECALL
if (ret != SGX_SUCCESS)
abort(); // 如果调用失败,终止程序
}
通过这段代码,SampleEnclave项目展示了如何在SGX enclave中调用标准C库函数。函数 ecall_libc_functions
通过两个ECALL演示了 malloc
和 free
的使用,以及如何获取CPU
的CPUID
信息。每次ECALL调用后都检查其返回值以确保操作的成功。此示例有助于理解如何在SGX环境中使用标准C库函数以及执行基本的内存管理和系统操作。
3.1.3.2 App/TrustedLibrary/Libcxx.cpp
#include <stdio.h> // 包含标准输入输出头文件
#include "../App.h" // 包含应用程序的头文件
#include "Enclave_u.h" // 包含Enclave生成的头文件,用于与Enclave进行交互
/* ecall_libcxx_functions:
* 调用标准C++函数。
*/
void ecall_libcxx_functions(void)
{
sgx_status_t ret = SGX_ERROR_UNEXPECTED;
// 调用ECALL函数来演示C++异常处理
ret = ecall_exception(global_eid); // 调用处理异常的ECALL
if (ret != SGX_SUCCESS)
abort(); // 如果调用失败,终止程序
// 调用ECALL函数来演示C++标准库中的map容器
ret = ecall_map(global_eid); // 调用处理map的ECALL
if (ret != SGX_SUCCESS)
abort(); // 如果调用失败,终止程序
}
通过这段代码,SampleEnclave项目展示了如何在SGX enclave中调用标准C++库函数。函数 ecall_libcxx_functions
通过两个ECALL函数演示了如何在enclave中处理C++异常以及使用C++标准库中的 map 容器。每次ECALL调用后都检查其返回值以确保操作的成功。此示例有助于理解如何在SGX环境中使用标准C++库函数以及执行基本的异常处理和容器操作。
3.1.3.3 App/TrustedLibrary/Thread.cpp
#include <thread> // 包含标准C++线程库头文件
#include <stdio.h> // 包含标准输入输出头文件
using namespace std; // 使用标准命名空间
#include "../App.h" // 包含应用程序的头文件
#include "Enclave_u.h" // 包含Enclave生成的头文件,用于与Enclave进行交互
static size_t counter = 0; // 全局计数器
/* 增加计数器的函数 */
void increase_counter(void)
{
size_t cnr = 0;
sgx_status_t ret = SGX_ERROR_UNEXPECTED;
ret = ecall_increase_counter(global_eid, &cnr); // 调用ECALL增加计数器
if (cnr != 0) counter = cnr; // 如果cnr不为0,更新全局计数器
if (ret != SGX_SUCCESS)
abort(); // 如果调用失败,终止程序
}
/* 数据生产者函数 */
void data_producer(void)
{
sgx_status_t ret = SGX_ERROR_UNEXPECTED;
ret = ecall_producer(global_eid); // 调用ECALL数据生产者
if (ret != SGX_SUCCESS)
abort(); // 如果调用失败,终止程序
}
/* 数据消费者函数 */
void data_consumer(void)
{
sgx_status_t ret = SGX_ERROR_UNEXPECTED;
ret = ecall_consumer(global_eid); // 调用ECALL数据消费者
if (ret != SGX_SUCCESS)
abort(); // 如果调用失败,终止程序
}
/* ecall_thread_functions:
* 调用线程函数,包括互斥锁、条件变量等。
*/
void ecall_thread_functions(void)
{
// 创建和启动四个增加计数器的线程
thread adder1(increase_counter);
thread adder2(increase_counter);
thread adder3(increase_counter);
thread adder4(increase_counter);
// 等待所有线程完成
adder1.join();
adder2.join();
adder3.join();
adder4.join();
// 确认计数器的值是否符合预期
assert(counter == 4 * LOOPS_PER_THREAD);
printf("信息: 正在执行线程同步,请稍候... \n");
// 创建和启动消费和生产线程
thread consumer1(data_consumer);
thread producer0(data_producer);
thread consumer2(data_consumer);
thread consumer3(data_consumer);
thread consumer4(data_consumer);
// 等待所有线程完成
consumer1.join();
consumer2.join();
consumer3.join();
consumer4.join();
producer0.join();
}
这段代码展示了如何在SGX enclave中通过ECALL实现基本的线程操作,包括线程同步、互斥锁和条件变量等。通过创建多个线程并调用相应的ECALL函数,该示例演示了如何在enclave中进行并发操作,并验证了全局计数器的正确性。同时,该代码还演示了数据生产者和消费者的模式,以展示更复杂的线程同步机制。每个ECALL后都检查其返回值,以确保操作的成功。
3.2 Enclave文件夹
3.2.1 Enclave/Enclave.cpp
/*
* printf:
* Invokes OCALL to display the enclave buffer to the terminal.
*/
int printf(const char* fmt, ...)
{
char buf[BUFSIZ] = { '\0' };
va_list ap;
va_start(ap, fmt);
vsnprintf(buf, BUFSIZ, fmt, ap);
va_end(ap);
ocall_print_string(buf);
return (int)strnlen(buf, BUFSIZ - 1) + 1;
}
在Enclave中的主函数Enclave.cpp主要提供printf的OCALL函数,也就是将enclave通过该函数在REE中打印出来。
3.2.2 Enclave/Edger8rSyntax文件夹
3.2.2.1 Enclave/Edger8rSyntax/Arrays.cpp
/* 测试数组属性 */
#include "sgx_trts.h" // 包含SGX运行时库的头文件
#include "../Enclave.h" // 包含Enclave自定义头文件
#include "Enclave_t.h" // 包含由Edger8r工具生成的头文件,用于定义ECALL和OCALL接口
#include <string.h> // 包含标准C库的字符串操作头文件
/* ecall_array_user_check:
* [user_check] 参数不会执行拷贝操作。
*/
void ecall_array_user_check(int arr[4])
{
// 检查数组是否位于Enclave外部
if (sgx_is_outside_enclave(arr, 4 * sizeof(int)) != 1)
abort();
// 遍历数组并修改其值
for (int i = 0; i < 4; i++) {
assert(arr[i] == i); // 确认数组元素的值是否为预期
// 以下代码执行arr[i] = (3 - i)
// 它将4个字节写入不受信任的内存,非8字节对齐。
// 因此出于安全考虑需要使用memcpy_verw()
int tmp = 3 - i;
memcpy_verw(&arr[i], &tmp, sizeof(int));
}
}
/* ecall_array_in:
* arr[] 被拷贝到受信任域中,但修改后的结果不会反映到不受信任的一侧。
*/
void ecall_array_in(int arr[4])
{
// 遍历数组并修改其值
for (int i = 0; i < 4; i++) {
assert(arr[i] == i); // 确认数组元素的值是否为预期
arr[i] = (3 - i); // 修改数组元素的值
}
}
/* ecall_array_out:
* arr[] 在Enclave内部分配,并且会被拷贝到不受信任的一侧。
*/
void ecall_array_out(int arr[4])
{
// 遍历数组并修改其值
for (int i = 0; i < 4; i++) {
/* arr 不是从应用程序中拷贝过来的 */
assert(arr[i] == 0); // 确认数组元素的初始值为0
arr[i] = (3 - i); // 修改数组元素的值
}
}
/* ecall_array_in_out:
* arr[] 会在Enclave内部分配,其内容也会被拷贝。
* ECALL返回后,结果会被拷贝到外部。
*/
void ecall_array_in_out(int arr[4])
{
// 遍历数组并修改其值
for (int i = 0; i < 4; i++) {
assert(arr[i] == i); // 确认数组元素的值是否为预期
arr[i] = (3 - i); // 修改数组元素的值
}
}
/* ecall_array_isary:
* [isary] 告诉Edger8r用户定义的 'array_t' 是一个数组类型。
*/
void ecall_array_isary(array_t arr)
{
// 检查数组是否位于Enclave外部
if (sgx_is_outside_enclave(arr, sizeof(array_t)) != 1)
abort();
int n = sizeof(array_t) / sizeof(arr[0]); // 计算数组元素的个数
// 遍历数组并修改其值
for (int i = 0; i < n; i++) {
assert(arr[i] == i); // 确认数组元素的值是否为预期
// 以下代码执行arr[i] = (n - 1 - i);
// 它将4个字节写入不受信任的内存,非8字节对齐。
// 因此出于安全考虑需要使用memcpy_verw()
int tmp = n - 1 - i;
memcpy_verw(&arr[i], &tmp, sizeof(int));
}
}
这段代码展示了多个ECALL函数,演示了如何在Enclave中处理不同的数组操作,包括传入、传出和双向传输数组。每个函数都通过遍历数组元素,执行各种检查和修改操作,并考虑到内存访问安全性的因素。尤其是在需要写入不受信任内存时,使用 memcpy_verw
来确保安全性。这些示例有助于理解如何在SGX环境中安全地处理数组数据。
- Arrays.edl
/* Arrays.edl - Samples for array attributes. */
enclave {
/*
* Only for fixed-size array (size is explicitly specified).
*/
trusted {
/*
* []: can be used to declare an array.
* [user_check]:
* pointer of the array won't be verified, and the buffer pointed by 'arr'
* is not copied into the enclave either. But enclave can modify the memory outside.
*/
public void ecall_array_user_check([user_check] int arr[4]);
/*
* [in]:
* buffer for the array will be allocated inside the enclave,
* content of the array will be copied into the new allocated memory inside.
* Any changes performed inside the enclave will not affect the array outside.
*/
public void ecall_array_in([in] int arr[4]);
/*
* [out]:
* buffer for the array will be allocated inside the enclave,
* but the content of the array won't be copied. After ECALL returns,
* the buffer inside the enclave will copied into outside array.
*/
public void ecall_array_out([out] int arr[4]);
/*
* [in, out]:
* buffer for the array will be allocated inside the enclave,
* the content of the array will be copied either. After ECALL returns,
* the buffer inside the enclave will by copied into outside array again.
*/
public void ecall_array_in_out([in, out] int arr[4]);
/*
* [isary]:
* tells Edger8r the user defined 'array_t' is an array type, 'arr' will be
* treated as a pointer, no memory copied either due to [user_check].
* For OCALLs, 'arr' shall point to the memory outside the enclave.
*/
public void ecall_array_isary([user_check, isary] array_t arr);
};
untrusted {
/*
* [user_check|in|out|in,out|isary] can also be used in OCALLs, refer to the "User Guide" for details.
*/
};
};
这个 Arrays.edl 文件通过定义多个ECALL接口,详细展示了如何在SGX Enclave中处理数组数据以及不同的数组属性(如 [user_check], [in], [out], [in, out], [isary])。每种属性都影响了数组数据在Enclave中的操作和在Enclave外部的反映方式。这个文件提供了丰富的示例,有助于理解在Enclave环境中如何安全且灵活地操作数组数据。
3.2.2.2 Enclave/Edger8rSyntax/Functions.cpp
/* 测试调用约定 */
#include <string.h> // 包含标准C库的字符串操作头文件
#include <stdio.h> // 包含标准输入输出头文件
#include "../Enclave.h" // 包含Enclave自定义头文件
#include "Enclave_t.h" // 包含由Edger8r工具生成的头文件,用于定义ECALL和OCALL接口
/* ecall_function_public:
* 一个公开的ECALL,调用OCALL 'ocall_function_allow'。
*/
void ecall_function_public(void)
{
sgx_status_t ret = SGX_ERROR_UNEXPECTED;
// 调用OCALL函数'ocall_function_allow'
ret = ocall_function_allow();
if (ret != SGX_SUCCESS)
abort(); // 如果调用失败,终止程序
return;
}
/* ecall_function_private:
* 一个私有的ECALL,仅能在OCALL 'ocall_function_allow'中被调用。
*/
int ecall_function_private(void)
{
return 1; // 返回值1表示函数成功调用
}
这段代码展示了如何在SGX Enclave中处理不同调用类型(公开 or 私有)的函数。函数 ecall_function_public
是一个公开的ECALL
,它调用了一个OCALL
函数 ocall_function_allow
,并且检查调用是否成功;如果失败则终止程序。而 ecall_function_private
是一个私有的ECALL
,仅能在OCALL ocall_function_allow
中被调用,返回一个整数 1 表示成功。
这个示例有助于理解在SGX环境中如何区分和处理公开与私有的ECALL函数,以及如何通过OCALL来调用这些ECALL函数。
- Functions.edl
/* Functions.edl - Samples for function attributes. */
enclave {
/*
* Following keywords/attributes are supported for untrusted functions:
* cdecl, stdcall, fastcall, dllimport (only for Windows).
* [public] is only supported for the trusted functions.
* Trusted function will be treated as [private] w/o the [public].
*/
trusted {
/*
* [public]:
* public ECALL can be called directly in App.
*/
public void ecall_function_public(void);
/*
* [private]:
* private ECALL cannot be called directly in App.
*/
int ecall_function_private(void);
};
untrusted {
/*
* [allow]:
* OCALL 'ocall_function_allow' can invoke ECALL 'ecall_function_private' in App side.
*
* Note: No ECALL can be called in OCALL w/o [allow].
*/
void ocall_function_allow(void) allow(ecall_function_private);
};
};
这个Functions.edl文件定义了两个ECALL和一个OCALL,同时展示了如何使用函数属性来控制它们的调用权限。
- 公开ECALL:ecall_function_public 使用 [public] 属性,可以直接在应用程序中调用,没有限制。
- 私有ECALL:ecall_function_private 没有使用 [public] 属性,因此被视为私有的,不能直接在应用程序中调用。只能在被授权的OCALL中调用。
- OCALL与授权:ocall_function_allow 是一个OCALL,使用 [allow] 属性,授权该OCALL调用特定的私有ECALL ecall_function_private。这种机制确保了只有特定的OCALL可以调用私有的ECALL,从而增强了安全性和控制。
这个示例有助于理解在SGX环境中如何使用EDL来定义和控制不同类型的函数调用,以及如何通过函数属性来管理这些调用的权限。
3.2.2.3 Enclave/Edger8rSyntax/Pointers.cpp
#include <string.h>
#include <sys/types.h>
#include "../Enclave.h"
#include "Enclave_t.h"
#include "sgx_lfence.h"
#include "sgx_trts.h"
/* checksum_internal:
* 计算输入缓冲区和长度的简单校验和
*/
int32_t checksum_internal(char* buf, size_t count)
{
register int32_t sum = 0;
int16_t* ptr = (int16_t*)buf;
/* 主循环求和 */
while (count > 1) {
sum = sum + *ptr++;
count = count - 2;
}
/* 如果有剩余字节,加上它 */
if (count > 0) {
sum = sum + *((char*)ptr);
}
return ~sum;
}
/* ecall_pointer_user_check, ecall_pointer_in, ecall_pointer_out, ecall_pointer_in_out:
* 测试[in]、[out]、[user_check]属性的根ECALL。
*/
size_t ecall_pointer_user_check(void* val, size_t sz)
{
/* 检查缓冲区是否在enclave外分配 */
if (sgx_is_outside_enclave(val, sz) != 1)
abort();
/* 在sgx_is_outside_enclave检查后插入fence */
sgx_lfence();
char tmp[100] = { 0 };
size_t len = sz > 100 ? 100 : sz;
/* 将内存复制到enclave中,以确保在checksum_internal()中不更改'val' */
memcpy(tmp, val, len);
int32_t sum = checksum_internal((char*)tmp, len);
printf("Checksum(0x%p, %zu) = 0x%x\n",
val, len, (unsigned int)sum);
/* 直接修改外部内存 */
memcpy_verw(val, "SGX_SUCCESS", len > 12 ? 12 : len);
return len;
}
/* ecall_pointer_in:
* val的缓冲区被复制到enclave中。
*/
void ecall_pointer_in(int* val)
{
if (sgx_is_within_enclave(val, sizeof(int)) != 1)
abort();
assert(*val == 1);
*val = 1234;
}
/* ecall_pointer_out:
* val的缓冲区被复制到不可信的一侧。
*/
void ecall_pointer_out(int* val)
{
if (sgx_is_within_enclave(val, sizeof(int)) != 1)
abort();
assert(*val == 0);
*val = 1234;
}
/* ecall_pointer_in_out:
* val的缓冲区被双重复制。
*/
void ecall_pointer_in_out(int* val)
{
if (sgx_is_within_enclave(val, sizeof(int)) != 1)
abort();
assert(*val == 1);
*val = 1234;
}
/* ocall_pointer_attr:
* 测试OCALL [in]、[out]、[user_check]的根ECALL。
*/
void ocall_pointer_attr(void)
{
sgx_status_t ret = SGX_ERROR_UNEXPECTED;
int val = 0;
ret = ocall_pointer_user_check(&val);
if (ret != SGX_SUCCESS)
abort();
val = 0;
ret = ocall_pointer_in(&val);
if (ret != SGX_SUCCESS)
abort();
assert(val == 0);
val = 0;
ret = ocall_pointer_out(&val);
if (ret != SGX_SUCCESS)
abort();
assert(val == 1234);
val = 0;
ret = ocall_pointer_in_out(&val);
if (ret != SGX_SUCCESS)
abort();
assert(val == 1234);
return;
}
/* ecall_pointer_string:
* [string]定义一个字符串。
*/
void ecall_pointer_string(char* str)
{
strncpy(str, "0987654321", strlen(str));
}
/* ecall_pointer_string_const:
* const [string]定义一个不能修改的字符串。
*/
void ecall_pointer_string_const(const char* str)
{
char* temp = new char[strlen(str)];
strncpy(temp, str, strlen(str));
delete[] temp;
}
/* ecall_pointer_size:
* 'len'需要指定以告诉Edger8r 'str'的长度。
*/
void ecall_pointer_size(void* ptr, size_t len)
{
strncpy((char*)ptr, "0987654321", len);
}
/* ecall_pointer_count:
* 'cnt'需要指定以告诉Edger8r 'arr'中的元素数量。
*/
void ecall_pointer_count(int* arr, size_t count)
{
int cnt = (int)count;
for (int i = (cnt - 1); i >= 0; i--)
arr[i] = (cnt - 1 - i);
}
/* ecall_pointer_isptr_readonly:
* 'buf'是用户定义类型,应标记为[isptr]。
* 如果它不可写,应指定[readonly]。
*/
void ecall_pointer_isptr_readonly(buffer_t buf, size_t len)
{
strncpy((char*)buf, "0987654321", len);
}
代码功能分析
这段代码展示了如何在Intel SGX中处理指针属性的示例,包括[in]、[out]、[user_check]、[string]、[size]、[count]和[readonly]等属性。以下是主要功能和步骤的详细分析:
- 校验和计算:
checksum_internal函数计算输入缓冲区的简单校验和。 - 用户检查指针:
ecall_pointer_user_check函数检查指针是否在enclave外部分配,并计算校验和。 - 指针属性测试:
ecall_pointer_in函数验证指针在enclave内并修改其值。
ecall_pointer_out函数验证指针在enclave内并修改其值。
ecall_pointer_in_out函数验证指针在enclave内并修改其值。 - OCALL指针属性测试:
ocall_pointer_attr函数测试OCALL中的指针属性,包括用户检查、输入和输出指针。 - 字符串指针:
ecall_pointer_string函数处理可修改的字符串指针。
ecall_pointer_string_const函数处理不可修改的字符串指针。 - 指针大小和计数:
ecall_pointer_size函数处理指定长度的指针。
ecall_pointer_count函数处理指定元素数量的指针数组。 - 只读指针:
ecall_pointer_isptr_readonly函数处理只读指针。
- Pointers.edl
/* Pointers.edl - Samples for pointer attributes. */
enclave {
/*
* Following keywords/attributes are supported for pointers in Edger8r:
* in, out, user_check,
* string, wstring,
* const, size, count, isptr, readonly
*/
trusted {
/*
* [user_check]:
* the pointer won't be validated, and the buffer pointed by
* 'val' is not copied into the enclave either. But Enclave
* can modify the memory pointed by 'val'.
*/
public size_t ecall_pointer_user_check([user_check] void *val, size_t sz);
/*
* [in]:
* buffer with the same size will be allocated inside the enclave,
* content pointed by 'val' will be copied into the new allocated
* memory inside. Any changes performed inside the enclave will not
* affect the buffer outside.
*/
public void ecall_pointer_in([in] int *val);
/*
* [out]:
* buffer with the same size will be allocated inside the enclave,
* but the content pointed by 'val' won't be copied. But after return,
* the buffer inside the enclave will copied into outside 'val'.
*/
public void ecall_pointer_out([out] int *val);
/*
* [in, out]:
* buffer with the same size will be allocated inside the enclave,
* the content pointed by 'val' will be copied either. After return,
* the buffer inside the enclave will by copied into outside 'val' again.
*/
public void ecall_pointer_in_out([in, out] int *val);
/*
* [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, out, string] char *str);
/*
* [const]:
* the attribute tells Edger8r the buffer pointed by 'str' cannot be modified,
* so users cannot decorate 'str' with [out] attribute anymore.
*/
public void ecall_pointer_string_const([in, string] const char *str);
/*
* [size]:
* the attribute tells Edger8r the length of buffer in byte pointed by 'ptr'
* (shall be copied or not).
* Note: Users shall not specify [size] on [string] parameters.
*/
public void ecall_pointer_size([in, out, size=len] void *ptr, size_t len);
/*
* [count]:
* the attribute tells Edger8r the number of integers to be copied from 'arr'.
*/
public void ecall_pointer_count([in, out, count=cnt] int *arr, size_t cnt);
/*
* [isptr]:
* tells Edger8r the user defined type is a pointer;
* [readonly]:
* forbids the buffer allocated inside the enclave to be copied back to App
* (cannot use with [out]).
*/
public void ecall_pointer_isptr_readonly([in, isptr, readonly, size=len] buffer_t buf, size_t len);
};
/*
* Users can define multiple trusted/untrusted blocks,
* edger8r will merged them into one trusted/untrusted block.
*/
trusted {
/*
* Test pointer attributes in OCALLs
*/
public void ocall_pointer_attr(void);
};
untrusted {
/*
* [user_check]:
* the pointer won't be verified, and the buffer pointed by 'val' is not
* copied to outside buffer either. Besides 'App' cannot modify the memory
* pointer by 'val'.
*/
void ocall_pointer_user_check([user_check] int *val);
/*
* [in]:
* buffer with the same size will be allocated in 'App' side, the content
* pointed by 'val' will be copied into the new allocated memory outside.
* Any changes performed by 'App' will not affect the buffer pointed by 'val'.
*/
void ocall_pointer_in([in] int *val);
/*
* [out]:
* buffer with the same size will be allocated in 'App' side, the content
* pointed by 'val' won't be copied. But after return, the buffer outside
* will be copied into the enclave.
*/
void ocall_pointer_out([out] int *val);
/*
* [in, out]:
* buffer with the same size will be allocated in 'App' side, the content
* pointed by 'val' will be copied either. After return, the buffer outside
* will copied into the enclave.
*/
void ocall_pointer_in_out([in, out] int *val);
};
};
3.2.2.4 Enclave/Edger8rSyntax/Types.cpp
#include "sgx_trts.h"
#include "../Enclave.h"
#include "Enclave_t.h"
#include <limits>
#include <cmath>
/* 用于消除“未使用变量”警告 */
#define UNUSED(val) (void)(val)
#define ULP 2
/* 用于比较double变量以避免编译警告 */
bool almost_equal(double x, double y)
{
/* 机器epsilon必须根据较大值的量级进行缩放
并乘以所需的ULP(最后一位的单位)精度 */
return std::abs(x-y) <= std::numeric_limits<double>::epsilon() * std::abs(x+y) * ULP;
}
/* 用于比较float变量以避免编译警告 */
bool almost_equal(float x, float y)
{
/* 机器epsilon必须根据较大值的量级进行缩放
并乘以所需的ULP(最后一位的单位)精度 */
return std::abs(x-y) <= std::numeric_limits<float>::epsilon() * std::abs(x+y) * ULP;
}
/* ecall_type_char:
* [char]值由App传递。
*/
void ecall_type_char(char val)
{
assert(val == 0x12);
#ifndef DEBUG
UNUSED(val);
#endif
}
/* ecall_type_int:
* [int]值由App传递。
*/
void ecall_type_int(int val)
{
assert(val == 1234);
#ifndef DEBUG
UNUSED(val);
#endif
}
/* ecall_type_float:
* [float]值由App传递。
*/
void ecall_type_float(float val)
{
assert(almost_equal(val, (float)1234.0));
#ifndef DEBUG
UNUSED(val);
#endif
}
/* ecall_type_double:
* [double]值由App传递。
*/
void ecall_type_double(double val)
{
assert(almost_equal(val, (double)1234.5678));
#ifndef DEBUG
UNUSED(val);
#endif
}
/* ecall_type_size_t:
* [size_t]值由App传递。
*/
void ecall_type_size_t(size_t val)
{
assert(val == (size_t)12345678);
#ifndef DEBUG
UNUSED(val);
#endif
}
/* ecall_type_wchar_t:
* [wchar_t]值由App传递。
*/
void ecall_type_wchar_t(wchar_t val)
{
assert(val == (wchar_t)0x1234);
#ifndef DEBUG
UNUSED(val);
#endif
}
/* ecall_type_struct:
* struct_foo_t在EDL中定义,可以在ECALL中使用。
*/
void ecall_type_struct(struct struct_foo_t val)
{
assert(val.struct_foo_0 == 1234);
assert(val.struct_foo_1 == 5678);
#ifndef DEBUG
UNUSED(val);
#endif
}
/*
* ecall_type_enum_union:
* enum_foo_t/union_foo_t在EDL中定义
* 并且可以在ECALL中使用。
*/
void ecall_type_enum_union(enum enum_foo_t val1, union union_foo_t *val2)
{
if (sgx_is_outside_enclave(val2, sizeof(union union_foo_t)) != 1)
abort();
val2->union_foo_0 = 1;
val2->union_foo_1 = 2; /* 覆盖union_foo_0 */
assert(val1 == ENUM_FOO_0);
#ifndef DEBUG
UNUSED(val1);
#endif
}
这段代码展示了如何在SGX中处理各种基本类型,包括浮点数、整数、结构体、枚举和联合体。通过详细的注释和步骤分析,读者可以更好地理解SGX中基本类型的工作原理和实现细节。代码中涵盖了如何接收和验证不同类型的值,以及如何处理浮点数比较等操作。
- Types.edl
/* Types.edl - Samples for basic types. */
enclave {
/*
* Following types can be supported in Edger8r:
* char, short, int, float, double, void,
* int8_t, int16_t, int32_t, int64_t,
* size_t, wchar_t,
* uint8_t, uint16_t, uint32_t, uint64_t,
* unsigned, struct, enum, union.
*/
/*
* We will demo few types in ECALL functions, data
* types in OCALL functions can be handled either.
*/
/* structure definition */
struct struct_foo_t {
/* Basic types can be used in structure. */
uint32_t struct_foo_0;
uint64_t struct_foo_1;
};
/* enum definition */
enum enum_foo_t {
ENUM_FOO_0 = 0,
ENUM_FOO_1 = 1
};
/* union definition */
union union_foo_t {
uint32_t union_foo_0;
uint32_t union_foo_1;
uint64_t union_foo_3;
};
trusted {
public void ecall_type_char(char val);
public void ecall_type_int(int val);
public void ecall_type_float(float val);
public void ecall_type_double(double val);
public void ecall_type_size_t(size_t val);
public void ecall_type_wchar_t(wchar_t val);
public void ecall_type_struct(struct struct_foo_t val);
public void ecall_type_enum_union(enum enum_foo_t val1, [user_check] union union_foo_t *val2);
/* for using user defined types, please refer to Pointers.edl, Arrays.edl. */
};
};
3.2.3 Enclave/TrustedLibrary文件夹
3.2.3.1 Enclave/TrustedLibrary/Libc.cpp
#include <string.h>
#include "sgx_cpuid.h"
#include "sgx_trts.h"
#include "../Enclave.h"
#include "Enclave_t.h"
/* ecall_malloc_free:
* 使用malloc/free分配/释放受信任的内存。
*/
void ecall_malloc_free(void)
{
void *ptr = malloc(100); // 分配100字节的受信任内存
assert(ptr != NULL); // 确保内存分配成功
memset(ptr, 0x0, 100); // 将分配的内存设置为0
free(ptr); // 释放内存
}
/* ecall_sgx_cpuid:
* 使用sgx_cpuid获取CPU特性和类型。
*/
void ecall_sgx_cpuid(int cpuinfo[4], int leaf)
{
sgx_status_t ret = sgx_cpuid(cpuinfo, leaf); // 获取指定leaf的CPU信息
if (ret != SGX_SUCCESS) // 如果获取失败,则终止执行
abort();
}
这段代码展示了如何在Intel SGX中使用malloc/free
分配和释放受信任的内存,以及如何使用sgx_cpuid
获取CPU特性和类型。以下是主要功能和步骤的详细分析:
- 内存分配和释放:
ecall_malloc_free
函数使用malloc
分配100字节的受信任内存,并确保内存分配成功。
使用memset
将分配的内存设置为0。
使用free
释放内存。 - 获取CPU特性和类型:
ecall_sgx_cpuid
函数使用sgx_cpuid
获取指定leaf的CPU信息。
如果获取CPU信息失败,则终止执行。
3.2.3.2 Enclave/TrustedLibrary/Libcxx.cpp
#include <cstdlib>
#include <string>
#include "../Enclave.h"
#include "Enclave_t.h"
/*
* ecall_exception:
* 在enclave内部抛出/捕获C++异常。
*/
void ecall_exception(void)
{
std::string foo = "foo";
try {
throw std::runtime_error(foo); // 抛出runtime_error异常
}
catch (std::runtime_error const& e) {
assert(foo == e.what()); // 捕获runtime_error异常,并验证异常信息
std::runtime_error clone(""); // 创建一个空的runtime_error异常
clone = e; // 复制异常
assert(foo == clone.what()); // 验证复制的异常信息
}
catch (...) {
assert(false); // 捕获所有其他异常,并断言失败
}
}
#include <map>
#include <algorithm>
using namespace std;
/*
* ecall_map:
* 在enclave中使用STL <map>。
*/
void ecall_map(void)
{
typedef map<char, int, less<char> > map_t; // 定义一个字符到整数的映射类型
typedef map_t::value_type map_value; // 定义映射的值类型
map_t m; // 创建一个映射实例
m.insert(map_value('a', 1)); // 插入键值对('a', 1)
m.insert(map_value('b', 2)); // 插入键值对('b', 2)
m.insert(map_value('c', 3)); // 插入键值对('c', 3)
m.insert(map_value('d', 4)); // 插入键值对('d', 4)
assert(m['a'] == 1); // 验证键'a'的值为1
assert(m['b'] == 2); // 验证键'b'的值为2
assert(m['c'] == 3); // 验证键'c'的值为3
assert(m['d'] == 4); // 验证键'd'的值为4
assert(m.find('e') == m.end()); // 验证键'e'不存在于映射中
return;
}
这段代码展示了在Intel SGX中使用C++异常处理和STL容器(如std::map)的示例。以下是主要功能和步骤的详细分析:
- 异常处理:
ecall_exception
函数在enclave内部抛出和捕获C++异常。
使用try-catch
块抛出std::runtime_error
异常,并在catch
块中捕获异常。
验证捕获的异常信息是否与预期值一致。
创建并复制异常对象,验证复制的异常信息是否与原始异常一致。 - STL容器
std::map
的使用:
ecall_map
函数在enclave内部使用STL容器std::map
。
定义一个字符到整数的映射类型,并插入一些键值对。
验证插入的键值对是否正确。
检查某个键是否存在于映射中。
3.2.3.3 Enclave/TrustedLibrary/Thread.cpp
#include "../Enclave.h"
#include "Enclave_t.h"
#include "sgx_thread.h"
static size_t global_counter = 0; // 全局计数器
static sgx_thread_mutex_t global_mutex = SGX_THREAD_MUTEX_INITIALIZER; // 全局互斥锁
#define BUFFER_SIZE 50 // 缓冲区大小
typedef struct {
int buf[BUFFER_SIZE]; // 缓冲区数组
int occupied; // 已占用的缓冲区数量
int nextin; // 下一个写入位置
int nextout; // 下一个读取位置
sgx_thread_mutex_t mutex; // 互斥锁
sgx_thread_cond_t more; // 条件变量,表示有更多数据
sgx_thread_cond_t less; // 条件变量,表示有更少数据
} cond_buffer_t;
static cond_buffer_t buffer = {{0, 0, 0, 0, 0, 0}, 0, 0, 0,
SGX_THREAD_MUTEX_INITIALIZER, SGX_THREAD_COND_INITIALIZER, SGX_THREAD_COND_INITIALIZER}; // 初始化缓冲区
/*
* ecall_increase_counter:
* 在enclave中使用线程API。
*/
size_t ecall_increase_counter(void)
{
size_t ret = 0;
for (int i = 0; i < LOOPS_PER_THREAD; i++) {
sgx_thread_mutex_lock(&global_mutex); // 加锁
/* 互斥地增加计数器 */
size_t tmp = global_counter;
global_counter = ++tmp;
if (4*LOOPS_PER_THREAD == global_counter)
ret = global_counter;
sgx_thread_mutex_unlock(&global_mutex); // 解锁
}
return ret;
}
void ecall_producer(void)
{
for (int i = 0; i < 4*LOOPS_PER_THREAD; i++) {
cond_buffer_t *b = &buffer;
sgx_thread_mutex_lock(&b->mutex); // 加锁
while (b->occupied >= BUFFER_SIZE)
sgx_thread_cond_wait(&b->less, &b->mutex); // 等待缓冲区有空闲空间
b->buf[b->nextin] = b->nextin; // 写入数据
b->nextin++;
b->nextin %= BUFFER_SIZE; // 循环使用缓冲区
b->occupied++; // 增加占用计数
sgx_thread_cond_signal(&b->more); // 通知消费者有更多数据
sgx_thread_mutex_unlock(&b->mutex); // 解锁
}
}
void ecall_consumer(void)
{
for (int i = 0; i < LOOPS_PER_THREAD; i++) {
cond_buffer_t *b = &buffer;
sgx_thread_mutex_lock(&b->mutex); // 加锁
while(b->occupied <= 0)
sgx_thread_cond_wait(&b->more, &b->mutex); // 等待缓冲区有数据
b->buf[b->nextout++] = 0; // 读取数据
b->nextout %= BUFFER_SIZE; // 循环使用缓冲区
b->occupied--; // 减少占用计数
sgx_thread_cond_signal(&b->less); // 通知生产者有空闲空间
sgx_thread_mutex_unlock(&b->mutex); // 解锁
}
}
这段代码展示了如何在Intel SGX中使用线程同步原语(如互斥锁和条件变量)进行多线程编程。以下是主要功能和步骤的详细分析:
- 全局计数器的增加:
ecall_increase_counter
函数使用互斥锁global_mutex
来保护全局计数器global_counter
。
在循环中,函数锁定互斥锁,安全地增加计数器,然后解锁互斥锁。 - 生产者-消费者模型:
- 使用
cond_buffer_t
结构体定义一个带有互斥锁和条件变量的缓冲区。 ecall_producer
函数实现生产者逻辑:
在循环中,函数锁定缓冲区的互斥锁。
如果缓冲区已满,等待less条件变量。
向缓冲区写入数据,更新写入位置和占用计数。
发出more条件变量信号,通知消费者有更多数据。
解锁互斥锁。ecall_consumer
函数实现消费者逻辑:
在循环中,函数锁定缓冲区的互斥锁。
如果缓冲区为空,等待more条件变量。
从缓冲区读取数据,更新读取位置和占用计数。
发出less条件变量信号,通知生产者有空闲空间。
解锁互斥锁。
四. 感谢支持
完结撒花!后续将持续输出,形成Intel SGX的系列教程,并结合密码学算法设计更复杂功能。希望看到这里的小伙伴能点个关注,也欢迎志同道合的小伙伴一起广泛交流。
码字实属不易,如果本文对你有10分帮助,就赏个10分把,感谢各位大佬支持!