蓬莱 TEE 介绍
蓬莱 TEE 论文
蓬莱 TEE 文档
蓬莱 TEE 项目
蓬莱TEE满足不同的场景需求:
为了实现软件可信的通用性,蓬莱TEE架构在安全监控器和具体的硬件原语间,设计了一层“安全原语”接口。 可信环境实例的管理逻辑将实现在这层通用的接口上,而不需要关心具体的硬件隔离和保护机制。
PengLai的新颖特征
各大厂商对TEE的重视
近年来,各大CPU厂商都纷纷推出了安全和隔离的特性。
Intel在近6年推出的安全特性数量超过了之前46年的总和。
最近发布的ARMv9也把安全作为与AI、性能并重的三大特性之一,其重点CCA(Confidential Computing Architecture),就是TEE技术之一。
TEE是什么?
TEE就是将操作系统的部分功能下沉到CPU硬件,由硬件直接提供一个隔离的黑盒子执行环境,软件无法干预。
手机端使用指纹解锁、人脸支付等功能的时候,生物特征数据的采集和比对就是在这个黑盒子里完成的,使用的技术是ARM TrustZone。
在服务器端,TEE用来实现更通用的数据保护,比如加密数据库(微软Azure、蚂蚁金服等采用)或加密虚拟机(Google Cloud采用),保证即便是云管理员也无法窃取或篡改云租户的数据。
如果能够保证数据只在黑盒子里运行,那么把这些黑盒子连在一起,就形成了一个可靠的管道,从而允许数据的安全流动。这也是为什么ARM、AMD、Intel、Redhat、Facebook、Google、华为、阿里云、腾讯、百度、字节跳动等公司要在2019年成立机密计算联盟(Confidential Computing Consortium)的原因之一。这里的主要支撑技术之一,就是TEE。
现存的问题推动蓬莱的发展
问:RISC-V平台不是已经有keystone了么,为什么还要重新造轮子?
答:需要有中国自己的TEE
蓬莱-TVM:通过受保护的页表(参考论文)的纯软件实现实现了一个可扩展的飞地系统,蓬莱TVM可以实现可扩展的内存保护、零拷贝通信和安全区快速启动。
蓬莱-PMP:利用 PMP/sPMP 来保护安全区内存。蓬莱-PMP版本更轻巧,可用于物联网设备。
主机应用程序是运行在主机内核中的不受信任的部分,可以创建或运行安全区。Enclave 应用,是在 enclave 中运行的受信任部分。
主机操作飞地生命周期
为基本主机应用程序提供的操作 enclave 的 API:PLenclave_create,PLenclave_run。
创建:使用PLenclave_create
接口来创建飞地。Monitor 将实例化一个 enclave 实例并返回相应的eid ,使用不同的参数,主机可以创建不同类型的 enclave:影子 enclave、服务器 enclave 等。
PLenclave_create(enclave, enclaveFile, params)
运行:创建后,enclave 不会运行,直到调用PLenclave_run,enclave才运行
result = PLenclave_run(enclave)
结束:enclave 线程不会返回到主机, 除非:(1) Enclave 已完成并退出。(2) Enclave 执行 ocall,需要返回用户级别进行处理。如果成功完成 enclave,则返回结果(不是返回值)将保持为零。 enclave 的返回值存储在返回参数enclave->user_param.retval
中。
主机操作飞地的结构体
有三种基本结构可以操纵飞地,其初始化如下:
elf_args_init(enclaveFile, eappfile)
//记录给定 enclave 的 ELF 文件
PLenclave_init(enclave)
//主机用于创建、运行 enclave 的主要 enclave 结构
enclave_args_init(params)
//存储所有 enclave 参数,这些参数将用于 enclave 创建、运行等。
enclave应用
hello-world 在 enclave 中运行,只需要对原始程序进行微小的修改。
#include "eapp.h"
//需要在 enclave 源文件中包含 enclave 头文件"eapp.h"
#include
int hello(unsigned long * args)
{
printf("hello world!\n");
EAPP_RETURN(0);
}
int EAPP_ENTRY main(){
unsigned long * args;
EAPP_RESERVE_REG;
//需要在跳转到真正的 enclave main 函数之前调用宏EAPP_RESERVE_REG来保留 enclave 上下文。
hello(args);
}
有两种方法可以实现IPC:共享内存,所有权转移。
主机分配共享内存
提供以下接口来为主机分配共享内存:
host 和 enclave 可以同时使用共享内存。
创建enclave 前,host需要设置与共享内存相关的参数
enclave_params->shmid = shmid;
enclave_params->shm_offset = 0;
enclave_params->shm_size = shm_size;
主机进行所有权转移
将所有权转移的内存命名为 for host 和 for enclave,提供以下接口来为主机分配schrodinger page。
schrodinger page只能映射在主机或飞地中。当分配schrodinger page时,它们首先被映射到主机空间中。所以主机可以把这些页面读/写作为普通内存。 当 enclave 运行时,monitor 将保证所有schrodinger page在主机中取消映射,并重新映射到 enclave。所以 enclave 可以读取/写入这些页面。schrodinger page用于防御TOCTTOU(Time-Of-Check-To-Time-Of-Use)攻击,可实现零拷贝通信。
主机可以使用PLenclave_set_mem_arg(enclave, mm_arg_id, 0, mm_arg_size)
函数将schrodinger page与给定的 enclave 绑定。
飞地IPC
enclave可以在相应的寄存器中获取共享内存和schrodinger page,a0和a1保留,用于存放共享内存的基地址和大小;a3和a4保留,用于存放schrodinger page的基地址和大小。
类似于上面所述的host-enclave IPC,Enclave-Enclave IPC也可以使用这两种方法,主机可以将单个共享内存分配给多个 enclave。因此,这些飞地可以共享相同的内存。在call_enclave_arg_t结构中,定义了请求和响应参数。
struct call_enclave_arg_t
{
unsigned long req_arg;
unsigned long resp_val;
unsigned long req_vaddr;
unsigned long req_size;
unsigned long resp_vaddr;
unsigned long resp_size;
};
请求参数可以在寄存器或传输的内存中传递。 响应参数的格式与请求参数类似,支持返回寄存器和传输的内存。可以通过req_vaddr = eapp_mmap(NULL, size)
接口分配enclave中所有权转移页面。
还在 eapp 库中定义了 enclave 调用,它可以支持服务器 enclave 的同步 IPC 调用。 调用方 enclave 将等到被调用方返回。 IPC 结构是调用参数,可以在调用方和被调用方 enclave 之间传输。 IPC 结构中定义的内存将更改其所有权并重新映射到目标飞地,此机制可确保只有一个 enclave 可以访问此内存范围。
影子 enclave 是一个干净的模板,另一个 enclave 可以fork影子 enclave 来实现快速启动。 影子 enclave 适用于需要使用单个源代码自动扩展多个 enclave 实例的场景。
创建影子 enclave 与创建普通 enclave 类似,只需将 creation 参数中的 enclave type 设置为 SHADOW_ENCLAVE
, 创建影子飞地后,它将返回其 eid。fork的 enclave 可以使用此影子 enclave eid 来初始化新的 enclave 实例。
struct elf_args* enclaveFile = malloc(sizeof(struct elf_args));
char * eappfile = argv[1];
elf_args_init(enclaveFile, eappfile);
if(!elf_valid(enclaveFile))
{
printf("error when initializing enclaveFile\n");
goto out;
}
struct PLenclave* enclave = malloc(sizeof(struct PLenclave));
struct enclave_args* params = malloc(sizeof(struct enclave_args));
PLenclave_init(enclave);
enclave_args_init(params);
//Mark the enclave as a shaodw enclave
params->type = SHADOW_ENCLAVE;
if(PLenclave_create(enclave, enclaveFile, params) < 0 )
{
printf("host: failed to create enclave\n");
}
主机可以从单个影子 enclave fork多个 enclave 实例。每个 enclave 实例不会相互干扰,有自己的堆栈和堆。这样可以减少测量开销,适用于快速启动和自动缩放的场景。
服务器 enclave 是一种特殊类型的 enclave,它不会运行,直到其他 enclave 调用它。 通过服务器 enclave,可以在蓬莱实现一个 enclave 链。服务器安全区可以充当单独的库或操作系统服务器等。
服务器 enclave 的创建类似于普通 enclave。要创建服务器 enclave,只需将 enclave 创建参数中的 enclave 类型设置为SERVER_ENCLAVE
,并为服务器 enclave 分配一个唯一的ID。 在调用此服务器 enclave 时,enclave 名称用作标识。
主机不需要运行服务器 enclave,服务器 enclave 可以由其他 enclave 或服务器 enclave 调用,并重用其主机上下文。目前,服务器 enclave 只能按顺序调用,稍后将支持并发服务器 enclave 调用。
当不再需要服务器 enclave 时,主机可以使用PLenclave_destroy(server1_enclave)
API 销毁服务器 enclave
监视器返回二点证明报告,包括 enclave 和安全监视器的度量。
主机可以调用PLenclave_attest
函数来获取 enclave 证明报告。证明报告由设备公钥、安全监视器报告和enclave报告组成。 主机可以使用证明报告对合法 enclave 进行身份验证。设备公钥和安全监控公钥用于验证相应的签名。 证明中需要 nonce,以确保其他人无法重用过时的证明报告。
struct sm_report_t
{
unsigned char hash[HASH_SIZE];
unsigned char signature[SIGNATURE_SIZE];
unsigned char sm_pub_key[PUBLIC_KEY_SIZE];
};
struct enclave_report_t
{
unsigned char hash[HASH_SIZE];
unsigned char signature[SIGNATURE_SIZE];
uintptr_t nonce;
};
struct report_t
{
struct sm_report_t sm;
struct enclave_report_t enclave;
unsigned char dev_pub_key[PUBLIC_KEY_SIZE];
};
int PLenclave_attest(struct PLenclave *PLenclave, uintptr_t nonce);
Enclave 可以使用get_report()
获取证明报告。证明报告的结构与主机使用的报告相同。 如果证明的 enclave 名称设置为 NULL,它将返回当前 enclave 的证明报告,否则,它将返回具有给定服务器 enclave 名称的证明报告。 在调用服务器 enclave 之前,最好使用get_report验证服务器 enclave 测量值。
int get_report(char* name, struct report_t *report, unsigned long nonce);
get_report(NULL, report, 1);
get_report("server-enclave", report, 1);
可以基于本地证明实现远程认证。
当 enclave 运行时,另一个主机线程可以使用PLenclave_stop
停止正在运行的 enclave。正在运行的 enclave 将被中断,监视器将存储其上下文。
主机可以使用PLenclave_resume
恢复已停止的 enclave,该 enclave 将继续从最后一个停止点运行。
主机可以使用PLenclave_destory
销毁 enclave 并回收其资源。 主机无法销毁已退出的 enclave。
PLenclave_destroy
还可以销毁服务器 enclave 和 shadow enclave。在销毁这些特殊类型的 enclave 之前,需要确保以后永远不会调用服务器 enclave 或影子 enclave。PLenclave_stop
和PLenclave_resume
并且不能用于停止/恢复服务器 enclave 或影子 enclave,因为它们不是可运行的实例。
// Stop a running enclave
int PLenclave_stop(struct PLenclave *PLenclave);
// Resume a stopped enclave
int PLenclave_resume(struct PLenclave *PLenclave);
// Destroy an enclave
int PLenclave_destroy(struct PLenclave *PLenclave);
蓬莱TVM是RISC-V上的可扩展飞地系统,实现了细粒度、可扩展的内存管理。利用 RISC-V 功能:陷阱虚拟内存 (TVM) 通过纯软件设计实现保护页表。
.
├── conf // The configuration for Linux and Buildroot
├── copy-files // Copy the files into ramfs
├── docker_cmd.sh // Docker command file
├── docs // Docs for Penglai
├── LICENSE // License for Penglai
├── Makefile // Makefile
├── penglai-buildroot // Penglai buildroot
├── Penglai-Linux-TVM // Penglai Linux kernel (5.10.2)
├── Penglai-Opensbi-TVM // Penglai Opensbi, including secure monitor and crypto lib
├── penglai-qemu // RISC-V QEMU suppoet sPMP extension
├── Penglai-sdk-TVM // Penglai sdk, demo and kernel driver
├── README.md // Penglai README
├── scripts // Scripts for building , running qemu
└── work // Build target file, including linux kernel and buildroot
蓬莱有三个关键的子模块:Linux(支持保护页表)、sdk(主机、飞地库和飞地驱动)和Opensbi(包括安全监控)。
int PLenclave_init(struct PLenclave *PLenclave)
int PLenclave_create(struct PLenclave* PLenclave, struct elf_args* u_elffile, struct enclave_args* u_param)
int PLenclave_run(struct PLenclave *PLenclave)
int PLenclave_attest(struct PLenclave *PLenclave, uintptr_t nonce)
int PLenclave_stop(struct PLenclave *PLenclave)
int PLenclave_resume(struct PLenclave *PLenclave)
int PLenclave_destroy(struct PLenclave *PLenclave)
int PLenclave_destruct(struct PLenclave *PLenclave)
void elf_args_init(struct elf_args* elf_args, char *filename)
void elf_args_destroy(struct elf_args* elf_args)
void enclave_args_init(struct enclave_args* enclave_args)
int PLenclave_set_shm(struct PLenclave *enclave, int shmid, uintptr_t offset, uintptr_t size)
描述:在enclave中配置共享内存,创建参数。
参数:
int PLenclave_set_mem_arg(struct PLenclave *enclave, int id, uintptr_t offset, uintptr_t size)
int PLenclave_set_rerun_arg(struct PLenclave *enclave, int rerun_reason)
Pint PLenclave_shmget(unsigned long size)
void* PLenclave_shmat(int shmid, void* addr)
int PLenclave_shmdt(int shmid, void* addr)
int PLenclave_shmctl(int shmid)
int PLenclave_schrodinger_get(unsigned long size)
void* PLenclave_schrodinger_at(int id, void* addr)
int PLenclave_schrodinger_dt(int id, void* addr)
int PLenclave_schrodinger_ctl(int id)
Libc支持
集成到飞地端库musl libc
中,它可以支持几个未修改的 libc 接口:
其他接口(如memset()
、memcpy()
等)与内核没有交互,蓬莱 enclave 也支持这些接口。
特定于 Enclave 的接口
void EAPP_RETURN(unsigned long retval) __attribute__((noreturn))
unsigned long get_enclave_id()
void* eapp_mmap(void* vaddr, unsigned long size)
int eapp_unmap(void* vaddr, unsigned long size)
unsigned long acquire_enclave(char* name)
int call_enclave(unsigned long handle, struct call_enclave_arg_t* arg)
int asyn_enclave_call(char* name, struct call_enclave_arg_t *arg)
void SERVER_RETURN(struct call_enclave_arg_t *arg) __attribute__((noreturn))
int EAPP_GET_REPORT(char * name, struct report_t *report, unsigned long nonce)
Enclave 内存布局
/* default layout of enclave */
/*
#####################
# reserved for #
# s mode #
##################### 0xffffffe000000000 //the start address of kernel's image
# hole #
##################### 0x0000004000000000
# shared mem #
# with host #
##################### 0x0000003900000000
# #
# host mm arg #
# #
##################### 0x0000003800000000
# #
# stack #
# #
##################### 0x0000003000000000
# mmap #
# #
##################### brk
# #
# heap #
# #
##################### 0x0000001000000000
# #
# text/code/bss #
# #
##################### 0x0000000000001000 //not fixed, depends on enclave's lds
# hole #
##################### 0x0
*/
Enclave 内存布局在/Penglai-Opensbi-TVM/include/sm/enclave_vm.h
ENCLAVE_DEFAULT_STACK_SIZE
ENCLAVE_DEFAULT_STACK_BASE
ENCLAVE_DEFAULT_KBUFFER
ENCLAVE_DEFAULT_KBUFFER_SIZE
ENCLAVE_DEFAULT_SHM_BASE
ENCLAVE_DEFAULT_MM_ARG_BASE
ENCLAVE_DEFAULT_MMAP_BASE
ENCLAVE_DEFAULT_HEAP_BASE
ENCLAVE_DEFAULT_TEXT_BASE
DEFAULT_SHADOW_ENCLAVE_ORDER
DEFAULT_SECURE_PAGES_ORDER
DEFAULT_SCHRODINGER_ORDER