07_OPTEE-OS_系统集成之(五)REE侧上层软件
carloscn opened this issue · 0 comments
07_OPTEE-OS_系统集成之(五)REE侧上层软件
OP-TEE在REE侧的上层软件包括libteec库和tee_supplicant。
- libteec库提供CA程序运行时的基本接口;
- tee_supplicant处理来自TEE侧的RPC请求。
libteec库和tee_supplicant属于REE侧用户空间的功能,属于OP-TEE架构中的重要组成部分。
1. OP-TEE软件架构
OP-TEE的软件分为REE侧部分和TEE侧部分,分别包括:
- CA (用户空间)
- REE侧接口库(libteec)
- 常驻进程 (tee_supplicant)
- OP-TEE驱动
- OP-TEE OS、
- TA等部分。
使用OP-TEE来实现特定的安全功能需要开发者根据实际需求开发特定的CA和TA程序并集成到OP-TEE中。CA端负责在REE侧实现该新功能在用户空间的对外接口,TA端的代码则是在OP-TEE OS的用户空间负责实现具体的安全功能,例如使用何种算法组合来对数据进行安全处理、对处理后的数据的安全保存、解密加密数据等功能。在OP-TEE中划分了安全世界和正常世界,两个世界通过session进行通信,通过COMMAND确定执行对象,通过shared memory来进行大量的数据交换。如图所示1:
ARM在硬件上将处理器分为两种模式(trustzone结构)如图所示:
对应的OPTEE在软件栈上需要协同trustzone:
借助OP-TEE来实现特定安全需求时,一次完整的功能调用一般是起源于CA,TA实现具体功能并返回结果数据给CA。整个过程需要经过OP-TEE的客户端接口、OP-TEE在Linux内核端的驱动、 Monitor模式/EL3下安全监控模式调用(smc)的处理、OP-TEE OS的线程处理、OP-TEE中的TA程序运行、OP-TEE端底层库或者硬件资源支持等几个阶段。当TA执行完具体请求之后会按照原路径将数据返回给CA。
不同厂商对具体API的具体实现不一样,但是其功能和对外接口都是遵循GP(Global Platform)的规范来进行封装。例如,海思和Mstar在实现CA端的API的方案不相同,海思在添加TA和CA时,在驱动层和TEE侧都会对调用TEE服务的进程或者线程做权限检查,建立类似白名单机制,在海思的TEE中添加TA和CA时必须注意将调用CA端接口的进程注册到TEE中。
由于当前所有厂商的TEE方案都会遵循GP标准,OP-TEE也遵循GP规范,本书中涉及的API的实现以OP-TEE中的源代码为准。
2. REE侧libteec库
CA使用libteec库中提供的接口来实现对TEE侧TA中具体命令的调用。libteec库是OP-TEE提供给用户在Linux用户空间使用的接口的实现,对于该部分每家芯片厂商可能不一样,但对外的接口都遵循GP规范中CA的接口进行定义。本章将以OP-TEE的实现方法为例进行介绍。
libteec库的所有源代码存放在 optee_client/libteec目录下,OP-TEE提供给Linux端使用的接口源代码的实现存放在optee_client/libteec/src/tee_client_api.c
文件中。
libteec库提供给上层用户使用的API一共有10个,都按照GP标准进行定义,使用这10个API能够满足用户在Linux用户空间的需求,在系统中这部分会被编译成libteec库,保存在REE侧的文件系统中以备上层使用。上述10个函数的功能和实现说明如下:
TEEC_Result TEEC_InitializeContext(const char *name, TEEC_Context *context);
void TEEC_FinalizeContext(TEEC_Context *context);
TEEC_Result TEEC_OpenSession(TEEC_Context *context,
TEEC_Session *session,
const TEEC_UUID *destination,
uint32_t connectionMethod,
const void *connectionData,
TEEC_Operation *operation,
uint32_t *returnOrigin);
void TEEC_CloseSession(TEEC_Session *session);
TEEC_Result TEEC_InvokeCommand(TEEC_Session *session,
uint32_t commandID,
TEEC_Operation *operation,
uint32_t *returnOrigin);
TEEC_Result TEEC_RegisterSharedMemoryFileDescriptor(TEEC_Context *ctx, TEEC_SharedMemory *shm,int fd);
TEEC_Result TEEC_RegisterSharedMemory(TEEC_Context *context,
TEEC_SharedMemory *sharedMem);
TEEC_Result TEEC_AllocateSharedMemory(TEEC_Context *context,
TEEC_SharedMemory *sharedMem);
void TEEC_RequestCancellation(TEEC_Operation *operation);
2.1 APIs
2.1.1 TEEC_InitializeContext
【定义】:TEEC_Result TEEC_InitializeContext(const char *name, TEEC_Context *context);
【解释】:初始化一个TEEC_Context变量,该变量用于CA和TEE之间建立联系。其中参数name用来定义TEE的身份,如果该参数为NULL,则CA将会选择默认的TEE方案来建立联系。该API必须是CA调用的第一个libteec库的API,且该API不会触发TA的执行。
2.1.2 TEEC_FinalizeContext
【定义】:void TEEC_FinalizeContext(TEEC_Context *context);
【解释】:释放一个已经被初始化的类型为TEEC_Context的变量,关闭CA与TEE之间的连接。在调用该函数之前必须确保打开的session已经被关闭。
2.1.3 TEEC_OpenSession
【定义】:
TEEC_Result TEEC_OpenSession(TEEC_Context *context,
TEEC_Session *session,
const TEEC_UUID *destination,
uint32_t connectionMethod,
const void *connectionData,
TEEC_Operation *operation,
uint32_t *returnOrigin);
【解释】:打开一个CA与对应TA之间的一个session,该session用于CA与对应TA之间的联系,CA需要连接的TA是由UUID指定的。session具有不同的打开和连接方式,根据不同的打开和连接方式CA可以在执行打开session时传递数据给TA,以便TA对打开操作进行权限检查。各种打开方式说明如下:
打开方式 | 说明 |
---|---|
TEEC_LOGIN_PUBLIC | 不需要提供,即 connectionData的值必须为NULL。 |
TEEC_LOGIN_USER | 提示用户链接, connectionData的值必须为NULL。 |
TEEC_LOGIN_GROUP | CA以组的方式打开session。connectionData的值必须指向一个类型为 uint32_t的数据,其包含某一组的特定信息。在TA 端将会对connectionData的数据进行检查,判定CA 是否真属于该组。 |
TEEC_LOGIN_APPLICATION | 以application的方式连接,connectionData的值必须为NULL。 |
TEEC_LOGIN_USER_APPLICATION | 以用户程序的方式连接,connectionData的值必须为 NULL。 |
TEEC_LOGIN_GROUP_APPLICATION | 以组应用程序的方式连接,其中connectionData需要指向 一个uint32_t类型的变量。在TA端将会对 connectionData的数据进行权限检查,查看连接是否 合法。 |
【传递参数】:
参数 | 说明 |
---|---|
context | 指向一个类型为TEEC_Context的变量,该变量用于CA与TA之间的连接和通信,调用TEEC_InitializeContext函数进行初始化; |
session | 存放session内存的变量; |
destination | 指向存放需要连接TA的UUID的值的变量; |
connectionMethod | CA与TA的连接方式,详细可参考函数描述中的说明; |
connectionData | 指向需要在打开session时传递给TA的数据; |
operation | 指向TEEC_Operation结构体的变量,变量中包含了一系列用于与TA进行交互使用的buffer或者其他变量。如果在打开session时CA和 TA不需要交互数据,则可以将该变量指向NULL; |
returnOrigin | 用于存放从TA端返回的结果的变量。如果不需要返回值,则可以将该变量指向NULL。 |
相应地,TEEC_CloseSession和opensession成对出现。
opensession内部使用了ioctl的接口,还有一些内存共享的实现。
https://github.com/OP-TEE/optee_client/blob/master/libteec/src/tee_client_api.c#L594
2.1.4 TEEC_InvokeCommand
【定义】:
TEEC_Result TEEC_InvokeCommand(TEEC_Session *session,
uint32_t commandID,
TEEC_Operation *operation,
uint32_t *returnOrigin);
【解释】:通过cmd_id和打开的session来通知session对应的TA执行cmd_id指定的操作。
2.1.5 TEEC_RequestCancellation
【原型】:void TEEC_RequestCancellation(TEEC_Operation *operation);
【解释】:取消某个CA与TA之间的操作,该接口只能由除执行TEEC_OpenSession和TEEC_InvokeCommand的线程之外的其他线程进行调用,而TA端或者TEE OS可以选择并不响应该请求。只有当operation中的 started域被设置成0之后,该操作方可有效。
2.1.6 TEEC_RegisterShareMemory
TEEC_Result TEEC_RegisterSharedMemory(TEEC_Context *context,
TEEC_SharedMemory *sharedMem);
注册一块在CA端的内存作为CA与TA之间的共享内存。shareMemory结构体中的三个成员如下:
- buffer:指向作为共享内存的起始地址;
- size:共享内存的大小;
- flags:表示CA与TA之间的数据流方向。
2.1.7 TEEC_RegisterShareMemoryFileDescriptor
TEEC_Result TEEC_RegisterSharedMemoryFileDescriptor(TEEC_Context *ctx, TEEC_SharedMemory *shm,int fd
注册一个在CA与TA之间的共享文件,在CA端会将文件的描述符fd传递给OP-TEE,其内容被存放到shm中。
2.1.8 TEEC_AllocateSharedMemory
TEEC_Result TEEC_AllocateSharedMemory(TEEC_Context *context,
TEEC_SharedMemory *sharedMem);
分配一块共享内存,共享内存是由OP-TEE分配的,OP-TEE分配了共享内存之后将会返回该内存块的fd给CA,CA将fd映射到系统内存,然后将地址保存到shm中。
除此之外还有对应的TEEC_ReleaseSharedMemory来释放分配的内存。
2.2 CA调用libteec库接口流程
CA在使用libteec库中的接口来实现调用TA的操作时,一般过程是需要先建立context,然后建立与需要调用的TA之间的session,再通过执行invoke操作向TA发送command ID来实现具体的操作需求,待TA中command ID的内容执行完成之后,如果后续也不需要再次调用TA时,可以通过close session和final context来释放资源,完全关闭该CA与TA之间的联系。一次完整的操作过程如图所示。
- (1)调用CA接口初始化context;
- (2)打开session(此时CA已经向TA发送请求)
- (3)初始化TEEC_PARAM_TYPES 初始化参数和缓存使用invokeCommand向TA发送命令;
- (4)关闭session;
- (5)释放资源,返回结果给REE。
3. REE守护进程tee_supplicant
tee_supplicant是常驻在Linux内核中的一个进程,主要作用是使OP-TEE能够通过tee_supplicant来访问REE侧的资源。例如加载存放在文件系统中的TA镜像到TEE中,对REE侧数据库的操作,对EMMC中RPMB分区的操作,提供socket通信等。 其源代码在optee_client/tee-supplicant目录中。编译之后会生成一个名为tee_supplicant的可执行文件, 该可执行文件在REE启动时会作为一个后台守护程序被自动启动。
3.1 tee_supplicant编译生成和自启动
tee_supplicant会在编译optee-client目标时被编译生成一个可执行文件。tee_supplicant可执行文件在Linux启动时会被作为后台程序启动。启动的动 作存放在build/init.d.optee文件中,其内容如下:
在编译时,init.d.optee文件将会被打包到根文件系统中并以optee名字存放在/etc/init.d目录中,而 且会被链接到/etc/rc.d/S09_optee
文件。这些操作是在编译生成rootfs时进行的,详细情况可查看build/common.mk文件中filelist-tee-common目标的内容。系统启动tee_supplicant的过程如图所示:
在最后的设备端的REE上,有这个文件,内部启动supplicant:
3.2 tee_supplicant入口函数
tee_supplicant作为Linux中的一个守护进程,起到处理RPC请求的服务器端的作用,通过类似于C/S的方式,为OP-TEE提供对REE侧资源进行操作 的实现。该可执行文件的入口函数存放在 optee_client/tee-supplicant/src/tee_supplicant.c
文件中。其入口函数内容如下:
https://github.com/OP-TEE/optee_client/blob/master/tee-supplicant/src/tee_supplicant.c#L790
3.3 tee_supplicant存放RPC请求的结构体
在tee_supplicant中用于接收和发送请求的数据都存放在类型为tee_rpc_invoke的结构体变量中。该结构体内容如下:
tee_rpc_invoke结构体中的数据展开之后的组成如图所示:
3.4 tee_supplicant的监听程序
tee_supplicant启动后最终会进入一个无限循环,调用process_one_request函数来监控、接收、 处理、回复OP-TEE的请求。如图所示为tee_supplicant处理RPC请求过程:
参考: https://github.com/OP-TEE/optee_client/blob/master/tee-supplicant/src/tee_supplicant.c#L613
tee_supplicant获取TA的RPC请求。tee_supplicant通过read_request接收来自TA端的请求。该函数会阻塞tee驱动层面,其内容如下:
static bool read_request(int fd, union tee_rpc_invoke *request)
{
struct tee_ioctl_buf_data data;
memset(&data, 0, sizeof(data));
data.buf_ptr = (uintptr_t)request;
data.buf_len = sizeof(*request);
if (ioctl(fd, TEE_IOC_SUPPL_RECV, &data)) {
EMSG("TEE_IOC_SUPPL_RECV: %s", strerror(errno));
return false;
}
return true;
}
在OP-TEE驱动中ioctl的TEE_IOC_SUPPL_RECV操作将会阻塞,直到接收 到来自TA的请求。
当tee_supplicant解析出RPC请求的功能ID,并根据该ID找到对应的处理函数,完成TEE请求操作后,tee_supplicant通过调用write_response函数将处 理结果和数据返回给TA。该函数的内容和解释如下:
static bool write_response(int fd, union tee_rpc_invoke *request)
{
struct tee_ioctl_buf_data data;
memset(&data, 0, sizeof(data));
data.buf_ptr = (uintptr_t)&request->send;
data.buf_len = sizeof(struct tee_iocl_supp_send_arg) +
sizeof(struct tee_ioctl_param) *
(__u64)request->send.num_params;
if (ioctl(fd, TEE_IOC_SUPPL_SEND, &data)) {
EMSG("TEE_IOC_SUPPL_SEND: %s", strerror(errno));
return false;
}
return true;
}
3.5 RPC的请求处理
当解析完来自TA的RPC请求,获取到具体参数后,在process_one_request函数中会根据请求的功能ID来决定具体执行什么操作。这些操作包括:
- 从文件系统中读取TA的镜像保存在共享内存 中;
- 对文件系统中的节点进行读/写/打开/关闭/移 除等操作;
- 执行RPMB(EMMC中的RPMB分区)相关操 作;
- 分配共享内存;
- 释放共享内存;
- 处理gprof请求;
- 执行网络socket请求。
4. RPC请求
tee_supplicant获取到远程过程调用(Remote Procedure Call,RPC)请求后会解析出功能ID,然后根据该ID值来命中tee_supplicant提供的具体操作。当请求处理完成后会将处理结果和数据发送给OP-TEE驱动,OP-TEE驱动最终会触发安全监控模式调用(smc)将数据传递给OP-TEE。
4.1 加载TA镜像
请求加载TA镜像的功能ID为RPC_CMD_LOAD_TA
。执行该功能时, tee_supplicant会到文件系统中将TA镜像的内容读取到共享内存中。该操作是通过调用load_ta函数来实现的,该函数定义在tee_supplicant.c文件中,在 REE侧加载TA镜像文件的整体流程如图所示。
static uint32_t load_ta(size_t num_params, struct tee_ioctl_param *params)
{
int ta_found = 0;
size_t size = 0;
struct param_value *val_cmd = NULL;
TEEC_UUID uuid;
TEEC_SharedMemory shm_ta;
memset(&uuid, 0, sizeof(uuid));
memset(&shm_ta, 0, sizeof(shm_ta));
if (num_params != 2 || get_value(num_params, params, 0, &val_cmd) ||
get_param(num_params, params, 1, &shm_ta))
return TEEC_ERROR_BAD_PARAMETERS;
uuid_from_octets(&uuid, (void *)val_cmd);
size = shm_ta.size;
ta_found = TEECI_LoadSecureModule(supplicant_params.ta_dir, &uuid, shm_ta.buffer, &size);
if (ta_found != TA_BINARY_FOUND) {
EMSG(" TA not found");
return TEEC_ERROR_ITEM_NOT_FOUND;
}
MEMREF_SIZE(params + 1) = size;
/*
* If a buffer wasn't provided, just tell which size it should be.
* If it was provided but isn't big enough, report an error.
*/
if (shm_ta.buffer && size > shm_ta.size)
return TEEC_ERROR_SHORT_BUFFER;
return TEEC_SUCCESS;
}
当load_ta执行完成并正确读取了TA镜像文件的信息之后,最终会将读取到的数据通过调用write_response函数,将数据发送给OP-TEE驱动,由驱动来完成将数据发送给OP-TEE的操作。OP-TEE会对接收到的TA镜像的合法性进行校验,主要是验证TA镜像文件的电子签名是否合法。
4.2 操作REE侧文件系统
当功能ID为RPC_CMD_FS时,tee_supplicant会根据TA请求调用tee_supp_fs_process函数来完成对 文件系统的具体操作,包括常规的文件和目录的打开、关闭、读取、写入、重命名、删除等。其内容如下:
https://github.com/OP-TEE/optee_client/blob/master/tee-supplicant/src/tee_supp_fs.c
TEEC_Result tee_supp_fs_process(size_t num_params,
struct tee_ioctl_param *params)
{
if (!num_params || !tee_supp_param_is_value(params))
return TEEC_ERROR_BAD_PARAMETERS;
if (strlen(tee_fs_root) == 0) {
if (tee_supp_fs_init() != 0) {
EMSG("error tee_supp_fs_init: failed to create %s/",
tee_fs_root);
memset(tee_fs_root, 0, sizeof(tee_fs_root));
return TEEC_ERROR_STORAGE_NOT_AVAILABLE;
}
}
switch (params->a) {
case OPTEE_MRF_OPEN:
return ree_fs_new_open(num_params, params);
case OPTEE_MRF_CREATE:
return ree_fs_new_create(num_params, params);
case OPTEE_MRF_CLOSE:
return ree_fs_new_close(num_params, params);
case OPTEE_MRF_READ:
return ree_fs_new_read(num_params, params);
case OPTEE_MRF_WRITE:
return ree_fs_new_write(num_params, params);
case OPTEE_MRF_TRUNCATE:
return ree_fs_new_truncate(num_params, params);
case OPTEE_MRF_REMOVE:
return ree_fs_new_remove(num_params, params);
case OPTEE_MRF_RENAME:
return ree_fs_new_rename(num_params, params);
case OPTEE_MRF_OPENDIR:
return ree_fs_new_opendir(num_params, params);
case OPTEE_MRF_CLOSEDIR:
return ree_fs_new_closedir(num_params, params);
case OPTEE_MRF_READDIR:
return ree_fs_new_readdir(num_params, params);
default:
return TEEC_ERROR_BAD_PARAMETERS;
}
}
tee_supp_fs_process函数主要是对REE侧文件系统进行操作。如果执行的是open、create操作则会返回文件的操作句柄fd值给OP-TEE;如果是write操作则会将需要写的内容写到具体的文件中。
4.3 操作RPMB
当功能ID为RPC_CMD_RPMB时,tee_supplicant会根据TA请求调用process_rpmb函数 来完成对EMMC中rmpb分区的操作。EMMC中的RPMB分区,在读写过程中会执行验签和加解密的操作。其内容如下:
https://github.com/OP-TEE/optee_client/blob/master/tee-supplicant/src/rpmb.c
uint32_t rpmb_process_request(void *req, size_t req_size, void *rsp, size_t rsp_size) {
uint32_t res = 0;
tee_supp_mutex_lock(&rpmb_mutex);
res = rpmb_process_request_unlocked(req, req_size, rsp, rsp_size);
tee_supp_mutex_unlock(&rpmb_mutex);
return res;
}
4.4 分配释放共享内存
当功能ID为RPC_CMD_SHM_ALLOC时,tee_supplicant会根据TA请求调用process_alloc函数来分配TA与tee_supplicant之间的共享内存。其内容如下:
static uint32_t process_alloc(struct thread_arg *arg, size_t num_params,
struct tee_ioctl_param *params)
{
struct param_value *val = NULL;
struct tee_shm *shm = NULL;
if (num_params != 1 || get_value(num_params, params, 0, &val))
return TEEC_ERROR_BAD_PARAMETERS;
if (arg->gen_caps & TEE_GEN_CAP_REG_MEM)
shm = register_local_shm(arg->fd, val->b);
else
shm = alloc_shm(arg->fd, val->b);
if (!shm)
return TEEC_ERROR_OUT_OF_MEMORY;
shm->size = val->b;
val->c = shm->id;
push_tshm(shm);
return TEEC_SUCCESS;
}
当功能ID为RPC_CMD_SHM_FREE时,tee_supplicant会根据TA请求调用process_free函数来释放TA与tee_supplicant之间的共享内存。
4.5 socket
当功能ID为OPTEE_MSG_RPC_CMD_SOCKET时, tee_supplicant会根据TA请求调用tee_socket_process 函数来完成网络套接字(socket)的相关操作,包括网络套接字的建立、发送、接收和ioctl操作。其内容如下:
https://github.com/OP-TEE/optee_client/blob/master/tee-supplicant/src/tee_socket.c
TEEC_Result tee_socket_process(size_t num_params,
struct tee_ioctl_param *params)
{
if (!num_params || !tee_supp_param_is_value(params))
return TEEC_ERROR_BAD_PARAMETERS;
switch (params->a) {
case OPTEE_MRC_SOCKET_OPEN:
return tee_socket_open(num_params, params);
case OPTEE_MRC_SOCKET_CLOSE:
return tee_socket_close(num_params, params);
case OPTEE_MRC_SOCKET_CLOSE_ALL:
return tee_socket_close_all(num_params, params);
case OPTEE_MRC_SOCKET_SEND:
return tee_socket_send(num_params, params);
case OPTEE_MRC_SOCKET_RECV:
return tee_socket_recv(num_params, params);
case OPTEE_MRC_SOCKET_IOCTL:
return tee_socket_ioctl(num_params, params);
default:
return TEEC_ERROR_BAD_PARAMETERS;
}
}
supplicant给TA提供服务,TA可以通过这些功能远程对REE测一些资源进行操作。