作者 | Kevin
来源 | 内核工匠(ID:Linux-Tech)
1、TEE背景
在文章开始之前提几个问题:
- Android手机中至少运行着几个操作系统OS?
- 如何进入安全操作系统?
- 异常等级和安全操作系统之间的关系?
- SMC调用的实质、约定及流程是什么?
随着智能手机的普及,手机上数据的价值越来越高,如电子支付密码(包括传统密码、指纹、人脸),带版权信息的数据等。为了进一步保护这些数据的安全,ARM提出了trustzone技术,其原理是将cpu的工作状态和其它相关硬件资源(中断、内存、外设和cache等)划分为安全(secure)和非安全(normal)两种类型,来达到数据隔离与保护。当cpu运行在normal状态时,将只能访问non secure空间的资源,而不能访问secure资源。当cpu运行在secure状态时,既能访问non secure资源,也能访问secure资源。
在ARM上,基于trustzone技术实现了TEE(可信执行环境),与之对应的是REE(不可信执行环境),一般称TEE为Secure World,REE 为Normal World。OP-TEE(开源的TEE OS)运行中Secure World里边,Android运行在Normal World里边。
TEE具备以下特点:
- 受硬件保护机制:TEE隔离于REE,只通过特定的入口于TEE通信,并不规定某一种硬件实现方法
- 高性能:TEE运行时使用CPU的全部性能
- 快速通信机制:TEE可以访问REE的内存,REE无法访问受硬件保护的TEE内存
图1 ARM trustzone技术
2 、REE与TEE交互
在上1节中提到TEE和REE两个运行环境,当REE访问TEE的服务时,首先需要进入到非安全区的特权模式(在Android系统中表现为linux 内核态),在该模式下调用SMC,处理器将进入到监视模式,监视模式下首先备份非安全区的上下文,然后进入到安全区的特权模式,此时的运行环境是安全区的执行环境TEE,并执行TEE中相应的安全服务。
以OPTEE为例,REE与TEE交互及模式转换如下图,CA与TA简单的交互流程如下:
1) CA通过lib teec打开一次会话,通过系统调用进入内核EL1(特权模式),然后调用tee driver相关API
2)tee driver调用SMC命令进入EL3(监视模式)secure monitor
3)secure monitor下解析SMC命令,并找到optee对应的服务。并将CPU的控制交给到optee kernel
4)optee 加载TA程序,校准TA程序的合法性
5)TA开始执行,完成后返回到CA
图2 REE与TEE交互框架
3 、深入SMC调用
在上节中了解到REE与TEE切换的关键是SMC,那么SMC实质是什么?其流程又是什么?
3.1 SMC指令
要深入了解SMC,需要从SMC指令开始探索,其指令如下图:
图3 SMC指令
指令格式为:SMC #即SMC后边跟一个16位数。
但需要注意的是,该指令只能在EL1及更高的异常等级上调用,说明用户空间是不能直接调用该指令。
3.2 SMC指令错误码
对于AArch32架构,SMC32调用的错误码存放在R0中
对于AArch64架构,SMC64调用的错误码存放在X0中,SMC32调用的错误码存放在W0中。
3.3 SMC参数传递
对于AArch32架构,参数传递约定:
- 函数标识符 -- R0
- 函数参数-- R1-R7
- 返回结果 -- R0-R7
- 寄存器R4-R7必须保留,除非它们包含函数定义中指定的结果
- 寄存器R8-R14由调用的函数保存,必须保存在SMC或HVC上
对于AArch64架构,参数传递约定:
- 函数标识符 -- W0
- 函数参数-- X1-X17
- 返回结果 -- X1-X17
- 寄存器X4-X17必须保留,除非它们包含函数定义中指定的结果
- 寄存器X18-X30由调用的函数保存,必须保存在SMC或HVC上
3.4 SMC调用过程中寄存器使用约定
对于AArch32架构,寄存器使用约定如下表:
【注意】Rn:32位寄存器,所有位均可用
图4 AArch32架构SMC调用寄存器约定
对于AArch64架构,寄存器使用约定如下表:
【注意】Xn:64位寄存器,所有位均可用。Wn:64位寄存器,低32位可用。
图5 AArch64架构SMC调用寄存器约定
3.5 SMC服务标识范围
从3.1知道SMC调用格式:SMC #
SMC可以根据不同的OEN(Owning Entity Number)区分不同的服务的功能,当注册SMC runtime service时,需要为对应service指定正确的OEN范围。具体如下表:
图6 SMC服务范围说明
3.6 SMC函数标识范围
SMC对于不同的函数调用,也有一些约定,不同函数标识符值范围对于不同的服务,在0x0000 0000–0xFFFF FFFF范围内,未被表覆盖的数据将被保留。如下表:
图7 SMC函数范围说明
3.7 SMC返回值说明
SMC调用的错误码为有符号的整形:int
通用含义如下表,0表示调用成功,-1表示调用不支持。
图8 SMC返回值说明
3.8 SMC调用的代码实现
根据3.1到3.7的介绍,如何用代码实现SMC调用?
源码路径:kernel-5.10/include/linux/arm-smccc.h
图9 [code]SMC宏定义
所以,AArch64架构SMCCC最终调用形式为:
__arm_smccc_1_1(“smc #0”, __VA_ARGS__)
其中__arm_smccc_1_1的实现:
图10 [code]__arm_smccc_1_1声明
__arm_smccc_SMC主要驱动用来将cortext切换到monitor模式的函数,在切换时保存上下文,SMC调用结束后,恢复上下文。
该函数是以汇编的方式编写,实现如下:
第2行的\instr #0 扩展之后主是SMC #0 即执行SMC指令,进行模式切换,陷入到EL3中,后边单独介绍。
【注】SMC #0执行时,根据SMCCC约定,需要对通用寄存器进行赋值用于传递参数,在AArch32架构中,r0 - r3可以直接保存到寄存器中,r4 - r6需要被压入到堆栈中保存。
AArch64架构中,不需要此操作。
SMC执行完成之后,4、5行会把x0 - x3保存到栈中,根据SMCc约定,x0 - x3用于返回执行结果,即结构体arm_smccc_res。后边7-11行会根据条件是否返回结果arm_smccc_quirk。
【注】stp指令 把一对寄存器写入到右边内存中
图11 [code]arm_smccc_smc函数声明
3.9 SMC调用两种方式
1、fast SMC模式:W0[31] == 1,在执行这类命令时,将会关闭cpu的irq和fiq中断。可保证其执行上下文是原子的,一度程度上影响系统的响应速度和实时性,一般用于执行速度很快的场景中
2、yielding 或者std SMC模式:W0[31] == 0,在执行这类命令时,不会关闭中断。在中断处理中可能需要转发normal world的foreign中断,同时在一些服务中可能需要通过rpc操作调用normal world的相关功能。
详见下表:
图12 SMC两种调用方式
4 、SMC处理流程
通过第3节知道SMC调用底层原理,本节将进一步探索SMC调用之后的流程,之后流程会涉及到EL3中的BL31流程,简单介绍如4.1。
4.1 secure monitor启动流程
BL31的启动流程如图9,BL31主要的工作有:
1 初始化cpu
2 初始化c运行时环境
3 初始化基本硬件,如timer、串口、gic等
4 创建页表,使能cache
5 加载后级镜像以及新镜像的跳转
6 若bl31支持el3中断,则初始化中断处理框架
7处理不同secure状态的SMC,初始化异常等级切换的上下文
8注册处理SMC命令的运行时服务
图13 Secure monitor启动流程
注册服务的实现如下:
图14 [code]注册服务宏定义
在注册处理SMC命令服务时,DECLARE_RT_SVC宏定义了一个结构体__svc_desc__name,并将其放到了一个特殊的段rt_svc_descs中,若需要获取这些结构体指针,只需遍历这段地址即可。
图15 [code]注册服务初始化
各服务经过service->init()初始化后,完成了注册。
4.2 SMC详细处理流程
4.1节完成了TEE服务注册后,考虑从REE到TEE发起SMC调用,则会经历图10的流程。
图16 SMC处理流程
SMC调用引发的同步异常被cpu捕获到,开始执行
sync_exception_aarch64。
图17 [code]异常sync_exception_aarch64
handle_sync_exception实际上是一个宏,关键操作执行smc_handler64
图18 [code]handle_sync_exception宏
在smc_handler64中主要完成:
- 保存x0 - x29等寄存器的值。
- x5指向cookie,x6指向context structure (SP_EL3)
- x12指向cpu_context,用于从ERET返回EL3
- 使用SP_EL0堆栈
- 保存SPSR_EL3, ELR_EL3, SCR_EL3等寄存器
- 计算并加载指定的服务到x15
- 跳转到x15所指的地址 -- std_svc_smc_handler
- 执行完成后调用el3_exit返回
【注】:服务index最大为127,所以会检查x15第7位是否为1
图19 [code]smc_handler64实现
执行完tee EL0的服务后,需要先返回到EL3中,然后返回到REE中,el3_exit主要完成:
- 保存TEE中的SP_EL0,然后切换到SP_EL3
- 恢复SPSR_EL3, ELR_EL3 ,SCR_EL3等寄存器
- 恢复CPTR_EL3寄存器
- 恢复通用寄存器(x1 - x29)
- 最后调用exception_return宏,实际上调用eret,返回到REE环境中
图20 [code]exit_el3实现
【注】调用eret指令后,恢复PC指针,然后跳转到REE - EL1(Linux Kernel)中,跳转地址为el1的elr_el3设置的地址。
5、SMC服务处理
通过第4节知道SMC处理流程中会调用std_svc_setup.c中的服务函数:std_svc_smc_handler
这是一个顶层的SMC服务函数handler,是一个c语言函数,比较好分析:首选根据SMC_fid的值判断是否是psci的SMC指令,如果是,直接执行psci相关指令;否则根据SMC_fid进行选择执行。
函数处理完成后,会返回第4节的smc_handler64汇编代码中,继续执行。
【注】psci是arm提供的一套电源管理接口,当前一共包含0.1、0.2和1.0三个版本。它可被用于以下场景:
1 cpu的idle管理
2 cpu hotplug以及secondary cpu启动
3系统shutdown和reset
图21 [code]std_svc_smc_handler实现
6 结语
本文从TEE使用的背景切入,然后着重分析了REE与TEE交互过程的SMC相关原理和实现。中间涉及到arm构架,SMCCC调用约定,EL异常等级,BL启动流程,TEE服务注册及汇编代码分析等知识。
对于SMC涉及到的线程相关分析,不同的TEE OS实现不一样,这块对实际工作有较大影响,特别是TEE多线程,后边会继续深入研究。
【参考文章】:
https://zhuanlan.zhihu.com/p/401632688
https://www.jianshu.com/p/929d806f7caf
https://blog.csdn.net/shift_wwx/article/details/116005889
《DDI0487G_a_armv8_arm.pdf》
《DEN0028D_SMC_Calling_Convention-1.3eac0.pdf》
【源码获取】:
Linux内核源码:
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/?h=linux-5.10.y
基于ARMv8-A应用处理器,ARM开源参考实现BL31:
https://github.com/ARM-software/arm-trusted-firmware
Linaro 推出的开源TEE:
https://github.com/OP-TEE/optee_os
本文作者Kevin,首发于公众号“内核工匠”(ID:Linux-Tech),分享Linux内核相关黑科技、技术文章、技术资讯和精选教程,欢迎关注。
文章来源:OPPO内核工匠
推荐阅读
更多物联网安全,PSA等技术干货请关注平台安全架构(PSA)专栏。欢迎添加极术小姐姐微信(id:aijishu20)加入PSA技术交流群,请备注研究方向。