不管是学习哪种编程语言,入门都喜欢写一个Hello World程序。这里就看看怎么写一个SGX的Hello World。对于SGX来说,首先程序在逻辑上要分为安全区和非安全区。
在这个示例程序中,安全区生成一段Hello World字符串,非安全区通过ECALL调用非安全区代码,获得字符串然后打印到界面上。
文件组织如下
├── App
│ ├── App.cpp # 创建
│ ├── App.h # 创建
│ ├── App.o
│ ├── Enclave_u.c # 胶合代码
│ ├── Enclave_u.h # 胶合代码
│ └── Enclave_u.o
├── Enclave
│ ├── Enclave.config.xml # 创建
│ ├── Enclave.cpp # 创建
│ ├── Enclave.edl # 创建
│ ├── Enclave.h # 创建
│ ├── Enclave.lds # 创建
│ ├── Enclave.o
│ ├── Enclave_private.pem # 创建
│ ├── Enclave_t.c # 胶合代码
│ ├── Enclave_t.h # 胶合代码
│ └── Enclave_t.o
├── enclave.signed.so
├── enclave.so
├── hello_world
└── Makefile # 创建
```
标注创建的文件是需要自己创建的,标注胶合代码是通过sgx_edger8r工具生成的接口代码。App下包含的是untrusted的代码,Enclave下包含的是trusted的代码。
Makefile从其它示例代码中拷贝过来即可用(结构、文件名不变即可)
EDL
首先要创建一个edl文件(Enclave Description Language),它用于描述trusted和untrusted的接口。这个Hello world只定义了一个ECALL:
enclave {
trusted {
public void ecall_hello_from_enclave([out, size=len] char* buf, size_t len);
};
};
edl有自己的描述格式。
有了edl以后,就可以通过sgx_edger8r工具生成接口描述文件,比较像Wrapper代码这种东西。
$ sgx_edger8r --trusted --trusted-dir Enclave/ Enclave/Enclave.edl
$ sgx_edger8r --untrusted --untrusted-dir App/ Enclave/Enclave.edl
Enclave_t.h
就是一个函数声明。
#ifndef ENCLAVE_T_H__
#define ENCLAVE_T_H__
#include <stdint.h>
#include <wchar.h>
#include <stddef.h>
#include "sgx_edger8r.h" /* for sgx_ocall etc. */
#include <stdlib.h> /* for size_t */
#define SGX_CAST(type, item) ((type)(item))
#ifdef __cplusplus
extern "C" {
#endif
void ecall_hello_from_enclave(char* buf, size_t len);
#ifdef __cplusplus
}
#endif /* __cplusplus */
#endif
Enclave_t.c
生成了一些框架代码和宏,最终调用的是Enclave.cpp里的我们需要实现的函数。
#include "Enclave_t.h"
#include "sgx_trts.h" /* for sgx_ocalloc, sgx_is_outside_enclave */
#include "sgx_lfence.h" /* for sgx_lfence */
#include <errno.h>
#include <mbusafecrt.h> /* for memcpy_s etc */
#include <stdlib.h> /* for malloc/free etc */
#define CHECK_REF_POINTER(ptr, siz) do { \
if (!(ptr) || ! sgx_is_outside_enclave((ptr), (siz))) \
return SGX_ERROR_INVALID_PARAMETER;\
} while (0)
#define CHECK_UNIQUE_POINTER(ptr, siz) do { \
if ((ptr) && ! sgx_is_outside_enclave((ptr), (siz))) \
return SGX_ERROR_INVALID_PARAMETER;\
} while (0)
#define CHECK_ENCLAVE_POINTER(ptr, siz) do { \
if ((ptr) && ! sgx_is_within_enclave((ptr), (siz))) \
return SGX_ERROR_INVALID_PARAMETER;\
} while (0)
#define ADD_ASSIGN_OVERFLOW(a, b) ( \
((a) += (b)) < (b) \
)
typedef struct ms_ecall_hello_from_enclave_t {
char* ms_buf;
size_t ms_len;
} ms_ecall_hello_from_enclave_t;
static sgx_status_t SGX_CDECL sgx_ecall_hello_from_enclave(void* pms)
{
CHECK_REF_POINTER(pms, sizeof(ms_ecall_hello_from_enclave_t));
//
// fence after pointer checks
//
sgx_lfence();
ms_ecall_hello_from_enclave_t* ms = SGX_CAST(ms_ecall_hello_from_enclave_t*, pms);
sgx_status_t status = SGX_SUCCESS;
char* _tmp_buf = ms->ms_buf;
size_t _tmp_len = ms->ms_len;
size_t _len_buf = _tmp_len;
char* _in_buf = NULL;
CHECK_UNIQUE_POINTER(_tmp_buf, _len_buf);
//
// fence after pointer checks
//
sgx_lfence();
if (_tmp_buf != NULL && _len_buf != 0) {
if ( _len_buf % sizeof(*_tmp_buf) != 0)
{
status = SGX_ERROR_INVALID_PARAMETER;
goto err;
}
if ((_in_buf = (char*)malloc(_len_buf)) == NULL) {
status = SGX_ERROR_OUT_OF_MEMORY;
goto err;
}
memset((void*)_in_buf, 0, _len_buf);
}
ecall_hello_from_enclave(_in_buf, _tmp_len); // 重要
if (_in_buf) {
if (memcpy_s(_tmp_buf, _len_buf, _in_buf, _len_buf)) {
status = SGX_ERROR_UNEXPECTED;
goto err;
}
}
err:
if (_in_buf) free(_in_buf);
return status;
}
SGX_EXTERNC const struct {
size_t nr_ecall;
struct {void* ecall_addr; uint8_t is_priv; uint8_t is_switchless;} ecall_table[1];
} g_ecall_table = {
1,
{
{(void*)(uintptr_t)sgx_ecall_hello_from_enclave, 0, 0},
}
};
SGX_EXTERNC const struct {
size_t nr_ocall;
} g_dyn_entry_table = {
0,
};
Enclave.cpp
安全区的实现,把Hello World字符串拷到缓冲区里。
#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.h是空,就不贴了
Enclave_u.cpp
Enclave_u.cpp封装了安全区的接口调用。
#include "Enclave_u.h"
#include <errno.h>
typedef struct ms_ecall_hello_from_enclave_t {
char* ms_buf;
size_t ms_len;
} ms_ecall_hello_from_enclave_t;
static const struct {
size_t nr_ocall;
void * table[1];
} ocall_table_Enclave = {
0,
{ NULL },
};
sgx_status_t ecall_hello_from_enclave(sgx_enclave_id_t eid, char* buf, size_t len)
{
sgx_status_t status;
ms_ecall_hello_from_enclave_t ms;
ms.ms_buf = buf;
ms.ms_len = len;
status = sgx_ecall(eid, 0, &ocall_table_Enclave, &ms);
return status;
}
App.cpp
App.cpp是非安全区的业务代码,从代码来看,先要创建一个enclave,加载的是一个签名过的动态库。
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <time.h>
#include <ctime>
# 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;
char enclavefile[256];
getcwd(enclavefile, sizeof(enclavefile));
strcat(enclavefile, "/enclave.signed.so"); // so文件名
/* Call sgx_create_enclave to initialize an enclave instance */
/* Debug Support: set 2nd parameter to 1 */
ret = sgx_create_enclave(enclavefile, SGX_DEBUG_FLAG, NULL, NULL, &global_eid, NULL); // 重要
if (ret != SGX_SUCCESS) {
printf("Failed to create enclave, ret code: %d, enclave file: %s\n", ret, enclavefile);
return -1;
}
return 0;
}
tm* get_time() {
time_t rawtime;
struct tm * timeinfo;
time ( &rawtime );
timeinfo = localtime ( &rawtime );
return timeinfo;
}
/* Application entry */
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};
/* Initialize the enclave */
if(initialize_enclave() < 0){
printf("Enter a character before exit ...\n");
getchar();
return -1;
}
/* Enclave calls */
while(1) {
ecall_hello_from_enclave(global_eid, buffer, max_buf_len); // 重要
printf("%s%s\n", asctime(get_time()), buffer);
fflush(stdout);
sleep(1);
}
/* Destroy the enclave */
sgx_destroy_enclave(global_eid);
printf("Info: SampleEnclave successfully returned.\n");
printf("Enter a character before exit ...\n");
getchar();
return 0;
}
Enclave.config.xml/Enclave.lds/Enclave_private.pem
要让程序可编译,还需要生成几个文件,这个XML是enclave的配置文件,包含堆大小,栈大小,是否开启调试等。
<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.lds是链接脚本。
enclave.so
{
global:
g_global_data_sim;
g_global_data;
enclave_entry;
g_peak_heap_used;
local:
*;
};
Enclave_private.pem就是一个普通的RSA密钥,示例是用 openssl genrsa -out Enclave_private.pem -3 3072 生成,Enclave.signed.so就是用该密钥签名的。
编译和运行
make执行编译,sdk支持模拟方式。
make SGX_MODE=SIM 可以在不支持SGX的系统上编译并运行SGX程序。
SDK示范代码的README中有几种编译模式的详细描述。
3. Build the project with the prepared Makefile:
a. Hardware Mode, Debug build:
$ make
b. Hardware Mode, Pre-release build:
$ make SGX_PRERELEASE=1 SGX_DEBUG=0
c. Hardware Mode, Release build:
$ make SGX_DEBUG=0
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
enclave的库是静态的
$ ldd enclave.signed.so
statically linked
app侧会有一些sgx的依赖
$ ldd hello_world
linux-vdso.so.1 (0x00007ffd7effe000)
libsgx_urts_sim.so => /home/kaifeng/intel/sgxsdk/sdk_libs/libsgx_urts_sim.so (0x00007f6461149000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f6460965000)
libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007f6460761000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f6460542000)
libcrypto.so.1.1 => /usr/lib/x86_64-linux-gnu/libcrypto.so.1.1 (0x00007f6460077000)
libsgx_uae_service_sim.so => /home/kaifeng/intel/sgxsdk/sdk_libs/libsgx_uae_service_sim.so (0x00007f6461121000)
libstdc++.so.6 => /usr/lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007f645fcee000)
libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007f645fad6000)
/lib64/ld-linux-x86-64.so.2 (0x00007f6460f59000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f645f738000)
运行可以得到预期行为
$ ./hello_world
Tue Dec 28 16:26:59 2021
Hello world
Tue Dec 28 16:27:00 2021
Hello world
Tue Dec 28 16:27:01 2021
Hello world
Tue Dec 28 16:27:02 2021
Hello world
Tue Dec 28 16:27:03 2021
Hello world
^C
以Hardware方式编译的程序,在开启SGX的系统上能够运行。
目标系统也需要安装sdk的库,这些需要集成到标准路径下。