Linux TEE子系统:TEE子系统、OPTEE驱动、tee-supplicant
Linux TEE(以RISC-V为例)解决方案大致如下:
- TEE中运行TOS,比如OPTEE OS,及运行于其上的TA(Trusted Application)。
- SecureMonitor,比如OpenSBI,负责安全和非安全切换,以及安全任务分发。
- Linux kernel中TEE子系统,负责将CA安全请求转达到SecureMonitor。
- CA是TOS的Client Application,负责发起安全请求。
Linux侧OPTEE的实现包括:
- tee-supplicant基于libteec.so,打开/dev/teepriv0和Kernel OPT-EE驱动交互。
- OPT-EE CA基于libteec.so,打开/dev/tee0和Kernel OPTEE驱动交互。
- Linux kernel OPTEE驱动,
下面重点分析Linux kernel中的TEE子系统。
Linux TEE子系统主要处理TEE驱动注册、LInux和TEE之间共享内存管理、提供一套用于TEE的标准API。
Linux内核中的TEE子系统可以分为两部分:
- TEE框架:包括总线、Classh、TEE内核API、TEE设备属性、TEE文件操作函数集等。
- TEE驱动:特定TEE OS对应的Linux驱动,基于TEE框架创建设备,提供CA和TEE OS的交互。本文以OP-TEE为例。
1. TEE子系统
1.1 TEE子系统初始化
TEE框架初始化通过tee_init()进行,主要创建tee_class、TEE字符设备号范围、注册TEE总线。
tee_init
class_register
alloc_chrdev_region--分配tee字符设备号,最多32个子设备。
bus_register--注册tee总线。
tee_exit
bus_unregister
unregister_chrdev_region
class_unregister
TEE总线负责TEE驱动(比如OP-TEE驱动)和TEE设备直接的匹配以及TEE uevent事件上报操作。
static int tee_client_device_match(struct device *dev, struct device_driver *drv) { const struct tee_client_device_id *id_table; struct tee_client_device *tee_device; id_table = to_tee_client_driver(drv)->id_table; tee_device = to_tee_client_device(dev); while (!uuid_is_null(&id_table->uuid)) { if (uuid_equal(&tee_device->id.uuid, &id_table->uuid))------------遍历id_table,判断是否匹配tee_device->id.uuid。如匹配则表示设备和驱动匹配成功。 return 1; id_table++; } return 0; } static int tee_client_device_uevent(struct device *dev, struct kobj_uevent_env *env) { uuid_t *dev_id = &to_tee_client_device(dev)->id.uuid; return add_uevent_var(env, "MODALIAS=tee:%pUb", dev_id);-------------通知用于空间TEE设备dev_id产生。 } struct bus_type tee_bus_type = { .name = "tee", .match = tee_client_device_match, .uevent = tee_client_device_uevent, }; EXPORT_SYMBOL_GPL(tee_bus_type);
1.2 TEE子系统API
TEE子系统提供的API包括:
- TEE设备:
- tee_device_alloc:用于分配并初始化一个新的TEE设备结构体。
- tee_device_register:注册一个TEE设备。
- tee_device_unregister:注销一个TEE设备。
- teedev_open:打开一个TEE设备上下文。
- teedev_close_context:关闭一个TEE上下文。
- TEE shm:
- tee_shm_pool_alloc_res_mem:分配一个共享内存池,用于TEE设备之间的内存共享。
- tee_shm_pool_free:释放一个共享内存池。
struct tee_device *tee_device_alloc(const struct tee_desc *teedesc, struct device *dev, struct tee_shm_pool *pool, void *driver_data); int tee_device_register(struct tee_device *teedev); void tee_device_unregister(struct tee_device *teedev); struct tee_context *teedev_open(struct tee_device *teedev); void teedev_close_context(struct tee_context *ctx); struct tee_shm_pool *tee_shm_pool_alloc_res_mem(unsigned long vaddr, phys_addr_t paddr, size_t size, int min_alloc_order); static inline void tee_shm_pool_free(struct tee_shm_pool *pool) struct tee_shm *tee_shm_alloc_priv_buf(struct tee_context *ctx, size_t size); int tee_dyn_shm_alloc_helper(struct tee_shm *shm, size_t size, size_t align, int (*shm_register)(struct tee_context *ctx, struct tee_shm *shm, struct page **pages, size_t num_pages, unsigned long start)); void tee_dyn_shm_free_helper(struct tee_shm *shm, int (*shm_unregister)(struct tee_context *ctx, struct tee_shm *shm));
tee_device_alloc接受一个TEE设备描述符(`teedesc`),一个设备结构体(`dev`),一个共享内存池(`pool`),以及一个指向驱动数据的指针(`driver_data`)。返回值是一个指向新分配的`tee_device`结构:
tee_device_alloc
--分配sturct tee_device,并初始化名称层。
cdev_init--初始化tee字符设备,操作函数集为tee_fops。
device_initialize
init_completion
mutex_init
idr_init
tee_fops是tee设备的操作函数集:
static const struct file_operations tee_fops = { .owner = THIS_MODULE, .open = tee_open, .release = tee_release, .unlocked_ioctl = tee_ioctl, .compat_ioctl = compat_ptr_ioctl, };
重点在tee_ioctl对ioctl命令的处理:
- TEE_IOC_VERSION:用于获取TEE驱动的版本信息和能力。这个命令允许用户空间程序查询TEE驱动的实现ID和能力,以确保兼容性。
- TEE_IOC_SHM_ALLOC:分配共享内存。此命令用于在TEE客户端和TEE环境之间分配一块共享内存,以便它们可以交换数据。
- TEE_IOC_OPEN_SESSION:打开会话。此命令用于在TEE环境中打开一个会话,以便与特定的可信应用(TA)进行交互。
- TEE_IOC_INVOKE:调用函数。此命令用于在TEE环境中的可信应用上调用一个函数,执行特定的操作。
- TEE_IOC_CANCEL:取消请求。此命令用于取消正在进行的会话打开或函数调用。
- TEE_IOC_CLOSE_SESSION:关闭会话。此命令用于关闭与TEE环境中的可信应用的会话。
- TEE_IOC_SUPPL_RECV:接收供应商特定的数据。此命令用于从TEE环境接收供应商特定的数据或响应。
- TEE_IOC_SUPPL_SEND: 发送供应商特定的数据。此命令用于向TEE环境发送供应商特定的数据或命令。
- TEE_IOC_SHM_REGISTER:注册共享内存。此命令允许用户空间程序注册已经分配的共享内存,使其可以被TEE环境访问。
2. TEE驱动:OPTEE
2.1 OPTEE设备
optee驱动设备:
optee: optee { compatible = "linaro,optee-tz"; method = "smc"; };
RISC-V用于TEE通信的channel配置:
sbi-mpxy-opteed { opensbi-domain-instance = <&tdomain>; riscv,sbi-mpxy-channel-id = <0x02>; compatible = "riscv,sbi-mpxy-opteed"; };
2.2 OPTEE驱动
OP-TEE的配置如下:
static const struct of_device_id optee_dt_match[] = { { .compatible = "linaro,optee-tz" }, {}, }; MODULE_DEVICE_TABLE(of, optee_dt_match); static struct platform_driver optee_driver = { .probe = optee_probe, .remove_new = optee_smc_remove, .shutdown = optee_shutdown, .driver = { .name = "optee", .of_match_table = optee_dt_match, }, };
OP-TEE驱动初始化:
optee_core_init
optee_smc_abi_register
platform_driver_register
optee_driver
当dts匹配后optee_probe()进行:
- 进行RISCV特有的通道初始化。
- 根据配置获取invoke函数。
- 加载OPTEE固件。
- 检查OPTEE能力和兼容性等。
- 分配/dev/teeX和/dev/teeprivX等相关资源。
static int optee_probe(struct platform_device *pdev)
sbi_mpxy_tee_probe--RISCV特有通道初始化。
get_invoke_func--获取TEE调用触发函数,如果SMC则对应optee_smccc_smc。
optee_load_fw--用于加载OP-TEE固件。
optee_msg_api_uid_is_optee_api--用于检查API的UID是否匹配OP-TEE API。 optee_msg_get_os_revision--用于获取OPTEE操作系统的版本信息。
optee_msg_api_revision_is_compatible--检查API的版本信息是否兼容。
optee_msg_get_thread_count
optee_msg_exchange_capabilities--用于交换TEE的能力和特性信息。
tee_device_alloc--分配/dev/teeX和/dev/teeprivX设备对应的TEE结构体。
tee_device_register--分别注册/dev/teeX和/dev/teeprivX设备。
optee_cq_init--初始化OP-TEE的命令队列。
optee_supp_init--supplicant相关初始化。
optee_shm_arg_cache_init
teedev_open
optee_notif_init
optee_disable_unmapped_shm_cache
optee_enumerate_devices
struct tee_driver_ops是TEE设备驱动的操作函数集合:
struct tee_driver_ops { void (*get_version)(struct tee_device *teedev, struct tee_ioctl_version_data *vers);--返回驱动版本信息。 int (*open)(struct tee_context *ctx);--当设备文件被打开时被调用。 void (*release)(struct tee_context *ctx);--释放此打开的文件。 int (*open_session)(struct tee_context *ctx, struct tee_ioctl_open_session_arg *arg, struct tee_param *param);--打开一个新的会话。 int (*close_session)(struct tee_context *ctx, u32 session);--关闭一个会话。 int (*system_session)(struct tee_context *ctx, u32 session);--声明会话为系统会话。 int (*invoke_func)(struct tee_context *ctx, struct tee_ioctl_invoke_arg *arg, struct tee_param *param);--调用一个TEE函数。 int (*cancel_req)(struct tee_context *ctx, u32 cancel_id, u32 session);--请求取消正在进行的调用或打开。 int (*supp_recv)(struct tee_context *ctx, u32 *func, u32 *num_params, struct tee_param *param);--供supplicant获取命令时被调用。 int (*supp_send)(struct tee_context *ctx, u32 ret, u32 num_params, struct tee_param *param);--供supplicant发送响应时被调用。 int (*shm_register)(struct tee_context *ctx, struct tee_shm *shm, struct page **pages, size_t num_pages, unsigned long start);--在TEE中注册共享内存缓冲区。 int (*shm_unregister)(struct tee_context *ctx, struct tee_shm *shm);--在TEE中注销共享内存缓冲区。 };
TEE最多32个设备,/dev/teeX使用前16个,/dev/teeprivX使用后16个。
static const struct tee_driver_ops optee_clnt_ops = { .get_version = optee_get_version, .open = optee_smc_open, .release = optee_release, .open_session = optee_open_session, .close_session = optee_close_session, .system_session = optee_system_session, .invoke_func = optee_invoke_func, .cancel_req = optee_cancel_req, .shm_register = optee_shm_register, .shm_unregister = optee_shm_unregister, }; static const struct tee_desc optee_clnt_desc = { .name = DRIVER_NAME "-clnt", .ops = &optee_clnt_ops, .owner = THIS_MODULE, }; static const struct tee_driver_ops optee_supp_ops = { .get_version = optee_get_version, .open = optee_smc_open, .release = optee_release_supp, .supp_recv = optee_supp_recv, .supp_send = optee_supp_send, .shm_register = optee_shm_register_supp, .shm_unregister = optee_shm_unregister_supp, }; static const struct tee_desc optee_supp_desc = { .name = DRIVER_NAME "-supp", .ops = &optee_supp_ops, .owner = THIS_MODULE, .flags = TEE_DESC_PRIVILEGED, };
3 TEE用户空间程序
3.1 tee-supplicant
/etc/init.d/S30tee-supplicant启动tee-supplicant守护进程。
tee-supplicant是一个用户空间守护进程,它与 OP-TEE 操作系统通信,处理来自 OP-TEE OS 的请求,例如访问文件系统、RPMB 或网络资源。
/dev/teepriv0是Linux 内核中的一个设备节点,由 OP-TEE 驱动创建。当tee-supplicant需要执行相关操作时,操作的就是OP-TEE驱动的 /dev/teepriv0 设备。tee-supplicant 执行相关操作时,首先会调用到tee_fops中的成员函数,tee_fops中的成员函数再会去调用到optee_supp_ops中对应的成员函数来完成对/dev/teepriv0设备的实际操作 。
总结来说,tee-supplicant通过/dev/teepriv0与OP-TEE 驱动通信,处理OP-TEE OS的请求,而/dev/teepriv0是OP-TEE 驱动在 Linux 内核中创建的设备节点,用于tee-supplicant与OP-TEE驱动之间的交互 。
#!/bin/sh DAEMON="tee-supplicant" PIDFILE="/var/run/$DAEMON.pid" DAEMON_ARGS="-d /dev/teepriv0" start() { printf 'Starting %s: ' "$DAEMON" # shellcheck disable=SC2086 # we need the word splitting start-stop-daemon -S -q -p "$PIDFILE" -x "/usr/sbin/$DAEMON" \ -- $DAEMON_ARGS ... } stop() { ... } ... case "$1" in start|stop|restart) "$1";; reload) # Restart, since there is no true "reload" feature (does not # reconfigure/restart on SIGHUP, just closes all open files). restart;; *) echo "Usage: $0 {start|stop|restart|reload}" exit 1 esac
3.2 OPTEE CA
基于libteec.so编写OPTEE CA程序,以optee-example-hello-world为例:
#include <err.h> #include <stdio.h> #include <string.h> /* OP-TEE TEE client API (built by optee_client) */ #include <tee_client_api.h> /* For the UUID (found in the TA's h-file(s)) */ #include <hello_world_ta.h> int main(void) { TEEC_Result res; TEEC_Context ctx; TEEC_Session sess; TEEC_Operation op; TEEC_UUID uuid = TA_HELLO_WORLD_UUID; uint32_t err_origin; /* Initialize a context connecting us to the TEE */ res = TEEC_InitializeContext(NULL, &ctx);--初始化TEE会话上下文。 if (res != TEEC_SUCCESS) errx(1, "TEEC_InitializeContext failed with code 0x%x", res); res = TEEC_OpenSession(&ctx, &sess, &uuid, TEEC_LOGIN_PUBLIC, NULL, NULL, &err_origin);--打开TEE会话。 if (res != TEEC_SUCCESS) errx(1, "TEEC_Opensession failed with code 0x%x origin 0x%x", res, err_origin); memset(&op, 0, sizeof(op)); op.paramTypes = TEEC_PARAM_TYPES(TEEC_VALUE_INOUT, TEEC_NONE, TEEC_NONE, TEEC_NONE);--配置TEE参数。 op.params[0].value.a = 42; printf("Invoking TA to increment %d\n", op.params[0].value.a); res = TEEC_InvokeCommand(&sess, TA_HELLO_WORLD_CMD_INC_VALUE, &op, &err_origin);--调用TA函数,并返回结果到op。 if (res != TEEC_SUCCESS) errx(1, "TEEC_InvokeCommand failed with code 0x%x origin 0x%x", res, err_origin); printf("TA incremented value to %d\n", op.params[0].value.a); TEEC_CloseSession(&sess);--关闭TEE会话。 TEEC_FinalizeContext(&ctx);--释放资源。 return 0; }
参考文档:《TEE subsystem》