本文将向大家展示如何基于 Intel SGX SDK 开发一个最简单 SGX 应用:HelloWorld,这个程序在可信区生产 "Hello world"并传递给不可信代码(缓冲区)打印输出到终端。
虽然 Intel SGX SDK 安装目录中默认提供了数个 Sample,但每个 Sample 对于初学者来说非常复杂和难以理解。
前提条件
-
[必须] 你的开发环境必须安装了 Intel SGX SDK。
- 默认安装到
/opt/intel/sgxsdk
。
- 默认安装到
- [可选] 开发环境主机 CPU 支持 SGX;若不支持,可采用模拟器编译运行。
SGX IDE
目前只有微软 Visual Studio 2012 或 2013 专业版支持 SGX,且只支持 Windows; VS 提供了 SGX 项目模板以及 编译和调试相关功能。
基本原理
在演示代码之前,有必要先了解下 SGX 程序最基本的原理:
-
SGX应用由两部分组成:
-
untrusted 不可信区
- 代码和数据运行在普通非加密内存区域,程序
main
入口必须在非可信区;上图中的main()
和bar()
函数均在非可信区。
- 代码和数据运行在普通非加密内存区域,程序
-
truested 可信区
- 代码和数据运行在硬件加密内存区域,此区域由 CPU 创建的且只有CPU有权限访问; 上图中的
helloworld()
和foo()
函数运行在可信区。
- 代码和数据运行在硬件加密内存区域,此区域由 CPU 创建的且只有CPU有权限访问; 上图中的
-
- 非可信区只能通过 ECALL 函数调用可信区内的函数。
- 可信区只能通过 OCALL 函数调用非可信区的函数。
- ECALL 函数和 OCALL 函数通过 EDL 文件声明。
第一个 SGX 程序: HelloWorld
目录结构
HelloWorld/
├── App
│ ├── App.cpp
│ └── App.h
├── Enclave
│ ├── Enclave.config.xml
│ ├── Enclave.cpp
│ ├── Enclave.edl
│ ├── Enclave.h
│ ├── Enclave.lds
│ └── Enclave_private.pem
├── Include
└── Makefile
上面目录结构仿照了 sgxsdk/SampleCode
目录下示例代码目录:
- App 目录内为不可信区域代码,包括
main
入口、OCALL 函数内具体逻辑代码等等。 -
Enclave 目录为可信区域代码,包括 ECALL 函数内具体逻辑代码实现。
-
Enclave.lds
- EDL(Enclave Description Language) 文件。
-
Enclave.lds
- Enclave linker script。
-
Enclave_private.pem
- enclave.so 的签名私钥。
-
Enclave.config.xml
- Enclave 配置文件,如堆栈大小、是否客气 Debug 等。
-
Enclave.h & Enclave.cpp
- 应用安全区代码实现。
-
- Include 目录是不可信代码和可信代码共享的头文件。
编译 & 运行
比较简单,直接在项目目录下执行 make
,在项目根目录下会生成一个名为 app
的 Binary,运行这个 app
:
$ make
GEN => App/Enclave_u.h
CC <= App/Enclave_u.c
CXX <= App/App.cpp
LINK => app
GEN => Enclave/Enclave_t.h
CC <= Enclave/Enclave_t.c
CXX <= Enclave/Enclave.cpp
LINK => enclave.so
<EnclaveConfiguration>
<ProdID>0</ProdID>
<ISVSVN>0</ISVSVN>
<StackMaxSize>0x40000</StackMaxSize>
<HeapMaxSize>0x100000</HeapMaxSize>
<TCSNum>10</TCSNum>
<TCSPolicy>1</TCSPolicy>
<!-- Recommend changing 'DisableDebug' to 1 to make the enclave undebuggable for enclave release -->
<DisableDebug>0</DisableDebug>
<MiscSelect>0</MiscSelect>
<MiscMask>0xFFFFFFFF</MiscMask>
</EnclaveConfiguration>
tcs_num 10, tcs_max_num 10, tcs_min_pool 1
The required memory is 3960832B.
The required memory is 0x3c7000, 3868 KB.
Succeed.
SIGN => enclave.signed.so
The project has been built in debug hardware mode.
$ ./app
Hello world
Info: SampleEnclave successfully returned.
Enter a character before exit ...
编译基本流程(Makefile):
- 通过
sgx_edger8r
工具在App/
目录下生成不可信代码(Enclave_u.c 和 Enclave_u.h),这部分生成代码主要会调用 ECALL (sgx_ecall); - 编译不可信部分 Binary:
app
; - 通过
sgx_edger8r
工具在Enclave/
目录下生成可信代码(Enclave_t.c 和 Enclave_t.h); - 编译可信动态链接库(enclave.so);
- 通过
sgx_sing
工具签名可信动态链接库(enclave.signed.so); - 结束。
编译后的代码目录结构:
HelloWorld
├── app
├── App
│ ├── App.cpp
│ ├── App.h
│ ├── App.o #[generated]
│ ├── Enclave_u.c #[generated]
│ ├── Enclave_u.h #[generated]
│ └── Enclave_u.o #[generated]
├── Enclave
│ ├── Enclave.config.xml
│ ├── Enclave.cpp
│ ├── Enclave.edl
│ ├── Enclave.h
│ ├── Enclave.lds
│ ├── Enclave.o #[generated]
│ ├── Enclave_private.pem
│ ├── Enclave_t.c #[generated]
│ ├── Enclave_t.h #[generated]
│ └── Enclave_t.o #[generated]
├── enclave.signed.so #[generated]
├── enclave.so #[generated]
├── Include
└── Makefile
代码文件
- Encalve/Enclave.edl
enclave {
trusted {
public void ecall_hello_from_enclave([out, size=len] char* buf, size_t len);
};
};
EDL 中声明了一个公共 ECALL 函数,每个 SGX 应用的 EDL 必须至少声明一个 public
类型的 ECALL 函数。 trusted {...}
内声明 ECALL 函数, untrusted {...}
内申明 OCALL 函数,由于本例中安全区不需要向非安全区调用(OCALL),所以只声明了一个 ECALL 函数 ecall_hello_from_enclave
,这个 ECALL 函数目的是在安全区创建一个 Buffer 并填充 "Hello world",然后这个 Buffer 的内容拷贝到非安全的 Buffer 中,非安全区调用 printf
打印这个非安全 Buffer 内容。
- Enclave/Enclave.lds
enclave.so
{
global:
g_global_data_sim;
g_global_data;
enclave_entry;
g_peak_heap_used;
local:
*;
};
- Enclave/Enclave.config.xml
<EnclaveConfiguration>
<ProdID>0</ProdID>
<ISVSVN>0</ISVSVN>
<StackMaxSize>0x40000</StackMaxSize>
<HeapMaxSize>0x100000</HeapMaxSize>
<TCSNum>10</TCSNum>
<TCSPolicy>1</TCSPolicy>
<!-- Recommend changing 'DisableDebug' to 1 to make the enclave undebuggable for enclave release -->
<DisableDebug>0</DisableDebug>
<MiscSelect>0</MiscSelect>
<MiscMask>0xFFFFFFFF</MiscMask>
</EnclaveConfiguration>
- Enclave/Enclave.h
这个头文件内容基本为空的。
#ifndef _ENCLAVE_H_
#define _ENCLAVE_H_
#endif
- Enclave/Enclave.cpp
#include "Enclave.h"
#include "Enclave_t.h" /* print_string */
#include <string.h>
void ecall_hello_from_enclave(char *buf, size_t len)
{
const char *hello = "Hello world";
size_t size = len;
if(strlen(hello) < len)
{
size = strlen(hello) + 1;
}
memcpy(buf, hello, size - 1);
buf[size-1] = '\0';
}
- Enclave/Enclave_private.pem
生成签名秘钥:
$ openssl genrsa -out Enclave/Enclave_private.pem -3 3072
- App/App.h
#ifndef _APP_H_
#define _APP_H_
#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include "sgx_error.h" /* sgx_status_t */
#include "sgx_eid.h" /* sgx_enclave_id_t */
#ifndef TRUE
# define TRUE 1
#endif
#ifndef FALSE
# define FALSE 0
#endif
# define TOKEN_FILENAME "enclave.token"
# define ENCLAVE_FILENAME "enclave.signed.so"
extern sgx_enclave_id_t global_eid; /* global enclave id */
#if defined(__cplusplus)
extern "C" {
#endif
#if defined(__cplusplus)
}
#endif
#endif /* !_APP_H_ */
- App/App.h
#include <stdio.h>
#include <string.h>
#include <assert.h>
# include <unistd.h>
# include <pwd.h>
# define MAX_PATH FILENAME_MAX
#include "sgx_urts.h"
#include "App.h"
#include "Enclave_u.h"
/* Global EID shared by multiple threads */
sgx_enclave_id_t global_eid = 0;
int initialize_enclave(void)
{
sgx_status_t ret = SGX_ERROR_UNEXPECTED;
/* 调用 sgx_create_enclave 创建一个 Enclave 实例 */
/* Debug Support: set 2nd parameter to 1 */
ret = sgx_create_enclave(ENCLAVE_FILENAME, SGX_DEBUG_FLAG, NULL, NULL, &global_eid, NULL);
if (ret != SGX_SUCCESS) {
printf("Failed to create enclave, ret code: %d\n", ret);
return -1;
}
return 0;
}
/* 应用程序入口 */
int SGX_CDECL main(int argc, char *argv[])
{
(void)(argc);
(void)(argv);
const size_t max_buf_len = 100;
char buffer[max_buf_len] = {0};
/* 创建并初始化 Enclave */
if(initialize_enclave() < 0){
printf("Enter a character before exit ...\n");
getchar();
return -1;
}
/* ECALL 调用 */
ecall_hello_from_enclave(global_eid, buffer, max_buf_len);
printf("%s\n", buffer);
/* 销毁 Enclave */
sgx_destroy_enclave(global_eid);
printf("Info: SampleEnclave successfully returned.\n");
printf("Enter a character before exit ...\n");
getchar();
return 0;
}
总结
即便最简单的 SGX HelloWold 也比较复杂,当然“安全性”和“成本”(技术壁垒门槛、开发成本、维护成本、物料成本等)总是成正比的,和“效率”成反比的。希望这篇文章对那些想入门开发 SGX 应用的用户有所帮助。
参考资料
-
知乎专栏
-
了解Intel SGX
-
飞地开发基础
-
飞地开发基础(一)
-
飞地开发基础(二)
-
飞地开发基础(三)
-
示例-SampleEnclave
-
PowerTransition
-
-
开发准备
-
Intel SGX 官方的入门教程和中英双语的视频
-
SGX应用开发入门
-
SGX 及 SDK 应用开发环境简介及搭建 — 第1部分
-
第一个 SGX 应用 “Hello World” — 第2部分
-
SGX应用开发进阶篇— 第3部分
-