歷經一年多時間的系統整理合補充,《手機安全和可信應用開發指南:TrustZone與OP-TEE技術詳解 》一書得以出版,書中詳細介紹了TEE以及系統安全中的所有內容,全書按照從硬件到軟件,從用戶空間到內核空間的順序對TEE技術詳細闡述,讀者可從用戶空間到TEE內核一步一步瞭解系統安全的所有內容,同時書中也提供了相關的示例代碼,讀者可根據自身實際需求開發TA。目前該書已在天貓、京東、噹噹同步上線,鏈接如下(麻煩書友購書時能給予評論,多謝多謝):
非常感謝在此期間大家的支持以及各位友人的支持和幫助!!!。
爲方便和及時的回覆讀者對書中或者TEE相關的問題的疑惑,也爲了大家能有一個統一的交流平臺。我搭建了一個簡單的論壇,網址如下:
https://www.huangtengxq.com/discuz/forum.php
關於您的疑問可在“相關技術討論“”中發帖,我會逐一回復。也歡迎大家發帖,一起討論TEE相關的一些有意思的feature。共同交流。同時該論壇中也會添加關於移動端虛擬化的相關技術的板塊,歡迎各位共同交流學習
ARM支持的trustzone技術後提供了ARM cortex的虛擬化基礎。衆所周知ARM具有其中運行模式,但是支持trustzone技術的cortex就已經不再僅僅有其中模式了,而是具有八種模式和兩個狀態:secure world態和non-secure world態。本文將介紹secure world態和non-secure world態之間是如何實現切換的,以OP-TEE中在沒有使用ATF的情況下sm操作代碼爲例。
1. 基礎知識
本章節將介紹理解secure world與non-secure world之間進行切換所需要明白的一些寄存器和相關模式的知識。
1.1 ARM的運行模式
ARM在未支持trustzone技術之前,ARM具有七種運行模式,分別爲:
1. usr模式(用戶模式):正常程序的運行時的模式
2. fiq模式(快速中斷模式):當配置了快速中斷時,如果產生fiq事件,cortex將會切換到該模式
3. irq模式(用戶模式):中斷模式,一般用於通用中斷處理,被ROS使用
4. svc模式(管理模式):操作系統使用的保護模式,
5. sys模式(系統模式):運行具有特權的操作系統任務
6. abt模式(數據訪問終止模式):當數據或者指令預取值時終止則會進入該模式
7. und模式(未定義指令模式):當未定義指令執行的時候則會進入該模式
而支持trustzone技術之後,ARM增加了另外一種mon模式(monitor 模式),而mon模式就起到進行secure world與non-secure world之間進行切換的橋樑作用。所以總結下來在ARM的cortex中具有八種模式兩種狀態,每種狀態下具有自己獨立的八種模式。
1.2 NS位
在持trustzone技術的時候,ARM在AXI系統總線上增加了一個NS位(詳細情況請查閱ARM給出的trustzone白皮書),而NS位就是用來標記當前的數據,指令時屬於secure world態還是non-secure world態,NS位會被保存到scr寄存器的第0位。當NS=1時,處理器處於non-secure world態,當NS=0時,處理器處於secure world態。
1.3 重要寄存器
VBAR(Vector Base Address Register)寄存器:
異常向量基地址,該寄存器將保存異常向量表的基地址,在secure world態和non-secure world態都具有各自獨有的VBAR寄存器用來存放兩種狀態各自獨有的異常向量表所在的基地址。
MVBAR(Monitor Vector Base Address Register)寄存器:
monitor模式下的異常向量表基地址寄存器,該寄存器用來保存在monitor模式下異常向量表的基地址,該寄存器在secure world和non-secure world之間進行切換的時候起到關鍵作用。
SCR(Secure Configuration Register)寄存器
安全配置寄存器,處理器在運行的時候該寄存器中會保存相關的標誌,其中用於標記處理器處於secure world態還是non-secure world態的NS位就被保存在該寄存器中。
SP(Stack Pointer)棧寄存器:
該寄存器用來存放處理器使用的棧的偏移地址
CPSR(Current Program Status Register)程序狀態寄存器:
該寄存器將保存處理器運行時的各種標誌位信息,包括標誌域,狀態域,擴展域和控制域
SPSR(Saved Program Status Register)程序狀態保存寄存器:
當特定的異常中斷髮生時,spsr寄存器將保存當前長度cpsr寄存器中的內容,等異常中斷退出自後,處理器會使用spsr寄存器中的數據來恢復cpsr寄存器中的數據。
LR(Link Register)子程序鏈接寄存器:
該寄存器一般用來保存子程序的返回地址。
1.4 smc彙編指令
當需要讓處理器進入到monitor模式的時候,ARM要求執行smc指令來實現。如果該彙編指令執行成功,則處理器就切換到了monitor模式下並且更新monitor模式下的重要寄存器,包括CPSR, SPSR, LR。該操作與ARM進入到IRQ, ABT等模式的操作一樣,採取的是產生異常來進行模式的切換。當系處理器進入到monitor之後,處理器就回去找尋該模式下的異常處理向量表的位置,而monitor模式下獨有的異常向量表的基地址被保存在MVBAR寄存器中。
2. monitor模式下的處理過程
在secure world態或者是non-secure world態調用smc指令之後,處理器將會觸發異常操作進入到monitor模式中並更新monitor模式下的CPSR, SPSR, LR,並從MVBAR寄存器中獲取到monitor模式的異常中斷向量表基地址,進而找到smc操作的異常處理函數。在《16. OP-TEE中的中斷處理(二)------系統FIQ事件的處理》一文中介紹了monitor模式的異常中斷向量表基地址是如何保存到MVBAR寄存器中的,在此就不再做冗餘介紹。monitor模式下整個處理邏輯如下圖所示。
2.1 進入到monitor模式的中斷向量後的處理過程
在OP-TEE中monitor模式的異常中斷向量表定義在optee_os/core/arch/arm/sm/sm_a32.S文件總,其內容如下:
LOCAL_FUNC sm_vect_table , :
UNWIND( .fnstart)
UNWIND( .cantunwind)
b . /* Reset */
b . /* Undefined instruction */
b sm_smc_entry /* Secure monitor call */
b . /* Prefetch abort */
b . /* Data abort */
b . /* Reserved */
b . /* IRQ */
b sm_fiq_entry /* FIQ */
UNWIND( .fnend)
END_FUNC sm_vect_table
所以當調用smc之後,處理器切換到monitor模式,查找到異常中斷向量表,並執行b sm_smc_entry來對smc異常進行處理。該函數定義在optee_os/core/arch/arm/sm/sm_a32.S文件中。其完整內容如下:
LOCAL_FUNC sm_smc_entry , :
UNWIND( .fnstart)
UNWIND( .cantunwind)
srsdb sp!, #CPSR_MODE_MON//將當前模式的lr和spsr寄存器中的值分別存儲在monitor模式的sp中
push {r0-r7} //將r0到r7中的值壓入棧(sp)
clrex /* Clear the exclusive monitor *///獨佔清除,可以將關係緊密的獨佔訪問監控器返回爲開放模式
/* Find out if we're doing an secure or non-secure entry */
read_scr r1 //獲取當前scr寄存器中的值,並將值保存在r1寄存器中
tst r1, #SCR_NS //判定scr寄存器中的值的NS位是否爲1,如果是1則將會改變CPSR中的條件標誌位爲0
bne .smc_from_nsec //如果請求來自於non-secur world,則跳轉到smc_from_nsec進行執行
/*
* As we're coming from secure world (NS bit cleared) the stack
* pointer points to sm_ctx.sec.r0 at this stage. After the
* instruction below the stack pointer points to sm_ctx.
*/
//將當前處於secure world態中,secure world的運行棧存放在r0中
//所以將當前sp的值減去offset就可以得到secure world的運行棧地址
//並將sp的值指向得到的secure world的運行棧地址
sub sp, sp, #(SM_CTX_SEC + SM_SEC_CTX_R0)
/* Save secure context */
add r0, sp, #SM_CTX_SEC//將sp的值加上secure world context的長度保存在r0寄存器中
//保存secure world中8中模式的主要寄存器的值,並將值存放到r0寄存器,
//而r0寄存器已經指向了CPU棧的位置中以便實現secure context的保存
bl sm_save_modes_regs
/*
* On FIQ exit we're restoring the non-secure context unchanged, on
* all other exits we're shifting r1-r4 from secure context into
* r0-r3 in non-secure context.
*/
add r8, sp, #(SM_CTX_SEC + SM_SEC_CTX_R0) //將sp的值將上secure world context中r0存放的位置
ldm r8, {r0-r4} //將r8寄存器中的值指向的地址中的值依次賦值給r0到r4
mov_imm r9, TEESMC_OPTEED_RETURN_FIQ_DONE // 將FIQ指向完的值保存到r9寄存器中
cmp r0, r9 //對比r0寄存器和r9寄存器中的值
addne r8, sp, #(SM_CTX_NSEC + SM_NSEC_CTX_R0) //如果r0與r9不相等則將sp加上non-secure context中的r0的值保存到r8寄存器中
stmne r8, {r1-r4} //如果r0與r9不相等,則將r1到r4寄存器中的值依次加載到r8指定的位置
/* Restore non-secure context */
add r0, sp, #SM_CTX_NSEC //將sp的值加上non-secure world context的長度保存到r0寄存去中
bl sm_restore_modes_regs //獲取non-secure context的內容
//執行返回到non-seure world的操作
.sm_ret_to_nsec:
/*
* Return to non-secure world
*/
//將sp的值加上non-secure world context中從起始位置到r8寄存器的偏移值
//然後將結果保存到r0寄存器中
add r0, sp, #(SM_CTX_NSEC + SM_NSEC_CTX_R8)
ldm r0, {r8-r12} //r0寄存器中的值指向的地址中的值一次次賦值給r8和r12寄存器
/* Update SCR */
read_scr r0 //獲取當前scr寄存器的值,並保存到r0寄存器中
//將scr中的NS位和FIQ位置1
orr r0, r0, #(SCR_NS | SCR_FIQ) /* Set NS and FIQ bit in SCR */
write_scr r0 //將修改後的r0的值寫入到scr寄存器中
//將sp的值加上non-secure world context中從起始位置到r0寄存器的偏移值
//然後將結果保存到sp中
add sp, sp, #(SM_CTX_NSEC + SM_NSEC_CTX_R0)
b .sm_exit //跳轉到sm_exit函數繼續執行
//指向切換到secure world的操作
.smc_from_nsec:
/*
* As we're coming from non-secure world (NS bit set) the stack
* pointer points to sm_ctx.nsec.r0 at this stage. After the
* instruction below the stack pointer points to sm_ctx.
*/
//當前處於non-secure world態,棧指針就是sp
//所以將當前sp的值減去offset就可以得到non-secure world的運行棧地址
//並將sp的值指向得到的non-secure world的運行棧地址
sub sp, sp, #(SM_CTX_NSEC + SM_NSEC_CTX_R0)
//清除r1寄存器中的NS位和FIQ位
bic r1, r1, #(SCR_NS | SCR_FIQ) /* Clear NS and FIQ bit in SCR */
write_scr r1 //將r1寄存器中的值寫入到scr寄存器中
//將sp的值將上non-secure world context中r8存放的位置
//然後將結果保存到r0寄存器中
add r0, sp, #(SM_CTX_NSEC + SM_NSEC_CTX_R8)
stm r0, {r8-r12} //將r8到r12寄存器中的值保存到r0指向的地址位置
mov r0, sp //將sp的值賦值給r0寄存器
bl sm_from_nsec //跳轉到secure world中進行處理來之non-secure world的smc請求
cmp r0, #0 //對比返回值是否爲零,即帕丁sm_form_nsec函數是否執行成功
beq .sm_ret_to_nsec //如果執行成功則執行返回到non-secure world的操作
/*
* Continue into secure world
*/
//如果sm_from_nsec函數並未執行成功,
//則將sp的值將上secure world context中r8存放的位置
//然後將結果保存到sp中
add sp, sp, #(SM_CTX_SEC + SM_SEC_CTX_R0)
//執行退出sm操作
.sm_exit:
pop {r0-r7} //將棧中的r0到r7寄存去中的值進行出棧操作
rfefd sp! //使用sp寄存器中的數據執行返回操作
UNWIND( .fnend)
END_FUNC sm_smc_entry
2.2 non-secure world態觸發的smc異常的處理過程
當smc異常是在non-secure world態中觸發時,則SCR寄存器中的NS位必定爲1,則處理器會執行smc_from_nsec的分支正式進入對來之non-secure world的smc請求的具體處理。整個執行過程的流程圖如下:
在整個處理過程中,當SCR寄存器中的NS位被設定之後即表示處理器的狀態已經處於secure world態或者是non-secure world態。當判定該smc異常來自於non-secure world後將會執行到sm_smc_entry函數中的smc_from_nsec代碼塊。該段代碼塊如下:
.smc_from_nsec:
/*
* As we're coming from non-secure world (NS bit set) the stack
* pointer points to sm_ctx.nsec.r0 at this stage. After the
* instruction below the stack pointer points to sm_ctx.
*/
//當前處於non-secure world態,棧指針就是sp
//所以將當前sp的值減去offset就可以得到non-secure world的運行棧地址
//並將sp的值指向得到的non-secure world的運行棧地址
sub sp, sp, #(SM_CTX_NSEC + SM_NSEC_CTX_R0)
//清除r1寄存器中的NS位和FIQ位
bic r1, r1, #(SCR_NS | SCR_FIQ) /* Clear NS and FIQ bit in SCR */
write_scr r1 //將r1寄存器中的值寫入到scr寄存器中
//將sp的值將上non-secure world context中r8存放的位置
//然後將結果保存到r0寄存器中
add r0, sp, #(SM_CTX_NSEC + SM_NSEC_CTX_R8)
stm r0, {r8-r12} //將r8到r12寄存器中的值保存到r0指向的地址位置
mov r0, sp //將sp的值賦值給r0寄存器
bl sm_from_nsec //跳轉到secure world中進行處理來之non-secure world的smc請求
cmp r0, #0 //對比返回值是否爲零,即帕丁sm_form_nsec函數是否執行成功
beq .sm_ret_to_nsec //如果執行成功則執行返回到non-secure world的操作
/*
* Continue into secure world
*/
//如果sm_from_nsec函數並未執行成功,
//則將sp的值將上secure world context中r8存放的位置
//然後將結果保存到sp中
add sp, sp, #(SM_CTX_SEC + SM_SEC_CTX_R0)
在該代碼段中有重要的兩條語句,將從sm_smc_entry開始獲取到的scr值保存到r1寄存器中,然後清空r1寄存器中的NS爲和FIQ位完成設定處理器狀態和使能FIQ,然後再將r1寄存器重新載入到scr寄存器中來完成non-secure world態帶secure world態的切換。
當non-secure world態中的smc請求被TEE處理完成之後,處理器將調用sm_ret_to_nsec函數重新回到non-secure world態。其中從secure world態切換到non-secure world態是通過以下代碼來實現的,讀取當前的scr寄存器的值到r0寄存器,然後將r0寄存器中的值的NS位和FIQ位設置成1來完成將處理器切換回non-secure world態和屏蔽FIQ的功能。再通過write_scr函數將修改後的r0寄存器的值重新載入到scr寄存器中。
2.3 secure world態中觸發的smc異常的處理過程
當smc異常是在secure world態中觸發時,則SCR寄存器中的NS位必定爲0,則處理器會執行smc_ret_to_nsec的分支正式進入對來之secure world的smc請求的具體處理。整個執行過程的流程圖如下:
在上述過程中,從secure world切換到non-secure world的方法也是通過修改SCR寄存器中的NS位來實現的。在執行切換之前需要保存secure world態的上下文信息並將當前處理器的上下文信息恢復到non-secure world態的上下文信息。等non-secure world上下文信息恢復之後再修改SCR寄存器的NS位來實現world的切換。保存secure world的上下文信息和恢復non-secure world的上下文信息的操作分別通過sm_save_modes_regs和sm_restore_modes_regs函數來實現,上述兩個函數都定義在optee_os/core/arch/arm/sm/sm_a32.S文件中,內容如下,主要是保存或者恢復ARM其中模式的sp, lr信息:
FUNC sm_save_modes_regs , :
UNWIND( .fnstart)
UNWIND( .cantunwind)
/* User mode registers has to be saved from system mode */
cps #CPSR_MODE_SYS //設置CPSR寄存器中的相關位並進入到的SYS模式
stm r0!, {sp, lr} //將sp和lr寄存器中的值保存到r0指向的地址位置
cps #CPSR_MODE_IRQ //設置CPSR寄存器中的相關位並進入到IRQ模式中
mrs r2, spsr //複製spsr(備份程序狀態寄存)到r2中
stm r0!, {r2, sp, lr} //將r2, sp, lr中的值保存到r0指向的地址中
cps #CPSR_MODE_FIQ //設置CPSR寄存器中的相關位並進入到FIQ模式中
mrs r2, spsr //複製spsr(備份程序狀態寄存)到r2中
stm r0!, {r2, sp, lr} //將r2, sp, lr中的值保存到r0指向的地址中
cps #CPSR_MODE_SVC //設置CPSR寄存器中的相關位並進入到SVC模式中
mrs r2, spsr //複製spsr(備份程序狀態寄存)到r2中
stm r0!, {r2, sp, lr} //將r2, sp, lr中的值保存到r0指向的地址中
cps #CPSR_MODE_ABT //設置CPSR寄存器中的相關位並進入到ABT模式中
mrs r2, spsr //複製spsr(備份程序狀態寄存)到r2中
stm r0!, {r2, sp, lr} //將r2, sp, lr中的值保存到r0指向的地址中
cps #CPSR_MODE_UND //設置CPSR寄存器中的相關位並進入到UND模式中
mrs r2, spsr //複製spsr(備份程序狀態寄存)到r2中
stm r0!, {r2, sp, lr} //將r2, sp, lr中的值保存到r0指向的地址中
cps #CPSR_MODE_MON //設置CPSR寄存器中的相關位並進入到monitor模式中
bx lr //返回調用函數
UNWIND( .fnend)
END_FUNC sm_save_modes_regs
/* Restores the mode specific registers */
FUNC sm_restore_modes_regs , :
UNWIND( .fnstart)
UNWIND( .cantunwind)
/* User mode registers has to be saved from system mode */
cps #CPSR_MODE_SYS //設置CPSR寄存器中的相關位並進入到的SYS模式
ldm r0!, {sp, lr} //將r0寄存器中指向的位置中的值加載到sp和lr寄存器中
cps #CPSR_MODE_IRQ //設置CPSR寄存器中的相關位並進入到IRQ模式中
ldm r0!, {r2, sp, lr} //將r0寄存器中指向的位置中的值加載到r2,sp和lr寄存器中
msr spsr_fsxc, r2 //將r2寄存器指向的地址的數據賦值到spsr寄存器的標誌域,狀態域,擴展域,控制域
cps #CPSR_MODE_FIQ //設置CPSR寄存器中的相關位並進入到FIQ模式中
ldm r0!, {r2, sp, lr} //將r0寄存器中指向的位置中的值加載到r2,sp和lr寄存器中
msr spsr_fsxc, r2 //將r2寄存器指向的地址的數據賦值到spsr寄存器的標誌域,狀態域,擴展域,控制域
cps #CPSR_MODE_SVC //設置CPSR寄存器中的相關位並進入到SVC模式中
ldm r0!, {r2, sp, lr} //將r0寄存器中指向的位置中的值加載到r2,sp和lr寄存器中
msr spsr_fsxc, r2 //將r2寄存器指向的地址的數據賦值到spsr寄存器的標誌域,狀態域,擴展域,控制域
cps #CPSR_MODE_ABT //設置CPSR寄存器中的相關位並進入到ABT模式中
ldm r0!, {r2, sp, lr} //將r0寄存器中指向的位置中的值加載到r2,sp和lr寄存器中
msr spsr_fsxc, r2 //將r2寄存器指向的地址的數據賦值到spsr寄存器的標誌域,狀態域,擴展域,控制域
cps #CPSR_MODE_UND //設置CPSR寄存器中的相關位並進入到UND模式中
ldm r0!, {r2, sp, lr} //將r0寄存器中指向的位置中的值加載到r2,sp和lr寄存器中
msr spsr_fsxc, r2 //將r2寄存器指向的地址的數據賦值到spsr寄存器的標誌域,狀態域,擴展域,控制域
cps #CPSR_MODE_MON //設置CPSR寄存器中的相關位並進入到MON模式中
bx lr //返回調用函數
UNWIND( .fnend)
END_FUNC sm_restore_modes_regs
3. 總結
non-secure world與secure world之間的切換都是通過修改當前的SCR寄存器中的NS位來實現的,而且在secure world態時需要使能FIQ的支持,而在non-secure world態時則需要禁止FIQ的支持。其在兩種態之間的切換的時候需要做到對應的上下文的保存和恢復操作。