)
提起Arm平臺上的安全固件ATFArm Trusted Firmware現(xiàn)在官方叫TF-A幾乎是繞不開的存在。做嵌入式底層、固件安全、系統(tǒng)移植的工程師一上來要面對的就是BL1、BL2、BL31、BL32、BL33這一串術(shù)語以及一份看起來龐大又復(fù)雜的源碼樹。我最早接觸到ATF是在做TEE和Secure Boot集成的時候被文檔里寫的trusted boot chain繞得夠嗆后來干脆一行行讀源碼才把整個鏈路理順。這篇文章會把我做源碼審計和平臺移植過程中積累的東西攤開來講適合兩類人看一類是想搞懂ATF內(nèi)部邏輯、需要做安全固件工程審計的開發(fā)者另一類是正在做新SoC平臺移植、被各種啟動失敗折磨的嵌入式工程師。我不會講太多PPT層面的概念盡量直接落到代碼和操作上。1. 為什么ARM平臺的安全固件繞不開ATF1.1 EL3異常級別只有ATF這一個“合法玩家”Armv8-A引入的異常級別模型把系統(tǒng)分成了EL0到EL3四層EL0跑普通應(yīng)用EL1跑操作系統(tǒng)內(nèi)核EL2跑虛擬化EL3則是最底層的安全固件。這個層級設(shè)計不是擺設(shè)它是整個Arm安全模型的核心。EL3能訪問絕大多數(shù)硬件資源能決定非安全世界能不能訪問某個外設(shè)也能在安全世界和非安全世界之間切換上下文。換句話說EL3就是整個系統(tǒng)里權(quán)限最高的“裁判”。ATF就是這個EL3層級的標(biāo)準(zhǔn)參考實現(xiàn)。你可以在它基礎(chǔ)上改也可以把EL3固件整個換掉但絕大多數(shù)商業(yè)項目都選擇在ATF之上做定制因為Arm自己維護(hù)著完整的驅(qū)動、安全啟動和PSCI電源管理實現(xiàn)從零寫一個EL3固件的成本太高了而且錯誤一旦上線就是安全事件。這也是我把ATF列為安全固件審計第一站的最直接原因。1.2 ATF到底解決了哪些實際問題從功能上說ATF干的事情有三大塊。第一塊是安全啟動它負(fù)責(zé)建立從芯片上電到OS加載之前的信任鏈保證每個加載進(jìn)來的鏡像都是經(jīng)過簽名驗證的。第二塊是運行時服務(wù)系統(tǒng)啟動完成之后ATF常駐在EL3通過SMC指令對外提供服務(wù)其中最重要的就是PSCI電源管理比如CPU的上電下電、系統(tǒng)的重啟和關(guān)機(jī)。第三塊是安全世界與非安全世界之間的切換和通信移動設(shè)備里的TEE如OP-TEE就是由ATF加載和調(diào)度的。這三個能力基本覆蓋了嵌入式設(shè)備的整個生命周期開機(jī)要驗證運行中要管電源還要調(diào)度安全服務(wù)。理解了這三點再去讀源碼心里就有譜了不會一頭扎進(jìn)細(xì)節(jié)出不來。1.3 源碼評測選什么版本ATF倉庫更新很快不同版本之間的接口變化也不小。我這次評測基于v2.8到v2.9左右的代碼這兩個版本在主流SoC平臺和編譯工具鏈上有大量實際驗證社區(qū)資料也全。如果你正在做平臺移植建議先鎖定一個LTS傾向的穩(wěn)定版本不要直接追master。等整個平臺跑通了再考慮升級到新版本否則很容易被上游改動打亂節(jié)奏。2. ATF架構(gòu)全景從啟動鏈到運行時服務(wù)2.1 一段話講清楚BL1/BL2/BL31/BL32/BL33ATF的啟動過程可以理解為“層層接力驗證”。BL1是整個系統(tǒng)的信任根代碼通常固化在芯片ROM里上電后它先初始化最基礎(chǔ)的硬件環(huán)境然后把BL2鏡像加載到SRAM并驗證。BL2是可信啟動固件它負(fù)責(zé)從FIP包中把BL31、BL32和BL33加載出來并且逐一驗證簽名。BL31是EL3的運行時固件啟動校驗完成后一直駐留在EL3處理各種SMC請求。BL32通常是TEE OS比如OP-TEE。BL33是下一級引導(dǎo)程序最常見的是U-Boot或UEFI。這個接力過程里最關(guān)鍵的詞是“驗證”。每一級都只信任上一級驗證過的鏡像信任根在BL1的ROM代碼里密鑰則通過芯片的OTP fuses注入。一旦鏈路建立起來任何一環(huán)被篡改都會導(dǎo)致啟動失敗這也就是Trusted Board Boot的基本邏輯。2.2 EL3、S-EL1和S-EL2兩條世界之間的調(diào)度除了異常級別ATF還要處理Arm TrustZone的“安全世界”和“非安全世界”兩個概念。Linux跑在非安全世界的EL1/EL2OP-TEE跑在安全世界的S-EL1而ATF自己待在EL3。兩個世界之間不是隨便跳的必須通過SMC異常進(jìn)入EL3由ATF完成上下文保存和恢復(fù)、內(nèi)存訪問權(quán)限切換再把控制權(quán)交給目標(biāo)側(cè)。日常開發(fā)中最常接觸的就是psci_cpu_on這類SMC請求。比如Linux要啟動一個CPU核心會發(fā)SMC到EL3ATF的PSCI服務(wù)收到請求后驗證參數(shù)、初始化目標(biāo)核心然后設(shè)置該核心的入口地址讓它跳進(jìn)Linux的啟動代碼。整個過程里面涉及保存寄存器、配置異常向量、設(shè)置內(nèi)存屬性任何一步錯都可能導(dǎo)致系統(tǒng)掛死或者安全漏洞。2.3 源碼目錄結(jié)構(gòu)與構(gòu)建骨架ATF源碼的頂層結(jié)構(gòu)非常有規(guī)律這也是我特別喜歡拿它做工程范例的原因。bl1、bl2、bl31這些目錄對應(yīng)各啟動階段的實現(xiàn)plat目錄存放平臺相關(guān)代碼你的移植工作基本都在這里drivers目錄下面是驅(qū)動GIC、UART、IO內(nèi)存這些都有現(xiàn)成實現(xiàn)lib目錄放公共庫包括el3_runtime、el3_context_mgmt這些核心模塊tools目錄則有fiptool和cert_create這類構(gòu)建工具。構(gòu)建體系基于make每個平臺在plat/廠商/平臺/下維護(hù)自己的platform.mk指定要編譯哪些源文件、用哪個鏈接腳本、配置哪些宏。構(gòu)建時會用fiptool把BL31、BL32、BL33打包進(jìn)一個FIP文件BL2再從FIP里提取對應(yīng)鏡像加載。理解這個目錄邏輯之后無論是審計還是移植你都能快速定位到自己要看的文件。3. 安全固件工程審計ATF代碼里到底在保護(hù)什么3.1 信任鏈審計從OTP密鑰到各階段鏡像驗證做安全固件審計我第一個看的就是信任鏈實現(xiàn)。ATF的Trusted Board Boot支持兩種認(rèn)證方式一種叫Auth_FW使用RSA或ECDSA簽名一種叫Auth_OPTEE使用OP-TEE的簽名方案。實際項目中用的最多的是RSA SHA256組合。構(gòu)建時cert_create工具生成各級證書fiptool把證書和鏡像打進(jìn)FIP啟動時每一級BL用自己的公鑰驗證下一級BL的證書鏈最后再驗鏡像哈希和簽名。審計時要重點確認(rèn)三件事。第一信任根密鑰是否真的燒進(jìn)了OTP而不是編譯期寫死在鏡像里第二驗證邏輯有沒有繞過路徑比如是否存在某個配置可以直接跳過簽名檢查第三回滾保護(hù)是否生效很多設(shè)備安全漏洞出在系統(tǒng)可以降級到舊版本固件上ATF里的修訂計數(shù)器和anti-rollback機(jī)制就是干這個的。查源碼的時候重點看drivers/auth目錄以及每個平臺對ARM_ROTPK_LOCATION的定義。3.2 內(nèi)存隔離與權(quán)限控制是審計的高危區(qū)EL3固件最怕的問題就是內(nèi)存越權(quán)。ATF自己駐留在Trusted RAM和Trusted ROM里通過TZC secure memory controller保護(hù)。審計時要確認(rèn)BL31用的內(nèi)存區(qū)域有沒有被錯誤映射成非安全屬性還要檢查MMU的translation table配置。我習(xí)慣先看平臺內(nèi)存映射表再逐條核對內(nèi)存屬性凡是非安全世界能訪問到的EL3數(shù)據(jù)都是重大安全隱患。另一條線是SMC接口的權(quán)限校驗。ATF對外暴露了很多標(biāo)準(zhǔn)SMC服務(wù)比如PSCI、SDEI、SiP如果某個接口沒有做調(diào)用方來源檢查惡意普通世界的代碼就能通過SMC拿到額外權(quán)限。檢查方法也不復(fù)雜找到每個服務(wù)的handler入口看它在處理請求前有沒有校驗調(diào)用者的exception level和security state有沒有對傳入?yún)?shù)做范圍檢查這兩點一旦缺失就是可以入庫的漏洞點位。3.3 上下文切換中的寄存器泄露風(fēng)險安全世界和非安全世界切換時寄存器狀態(tài)必須完整保存和恢復(fù)。ATF里el3_context_mgmt模塊負(fù)責(zé)這攤事審計時我會特別關(guān)注保存的寄存器集合是否完整有沒有把安全側(cè)的敏感寄存器遺漏。更重要的是恢復(fù)context的時候是否覆蓋了全部通用寄存器、系統(tǒng)寄存器以及浮點寄存器。如果恢復(fù)不全就會出現(xiàn)信息泄露非安全世界通過側(cè)信道讀回安全世界殘留數(shù)據(jù)。我在審計一個合作方固件時就發(fā)現(xiàn)他們的BL31修改過context保存邏輯為了性能把fpregs的保存去掉了結(jié)果OP-TEE里的密鑰材料在切換后留在了FPU寄存器里。這類問題光看文檔很難發(fā)現(xiàn)必須結(jié)合diff和運行時寄存器轉(zhuǎn)儲才能定位。3.4 審計過程中總結(jié)的高危Checklist為了方便后續(xù)審計和自查我把工程審計中碰過的高頻風(fēng)險點整理成了一張清單每次上手新固件都按這個順序過一遍審計項重點關(guān)注常見風(fēng)險信任鏈起點ROTPK來源、OTP fuse策略密鑰寫死編譯目錄無防回滾證書鏈驗證每級BL的認(rèn)證路徑跳過或弱化次級驗證SMC接口參數(shù)檢查、調(diào)用來源檢查缺權(quán)限校驗可被普通世界調(diào)用內(nèi)存映射MMU表格、TZC配置Trusted區(qū)被映射成非安全Context切換寄存器保存恢復(fù)完整性漏存或未恢復(fù)敏感寄存器中斷路由GIC安全配置安全中斷誤路由到非安全側(cè)這張表不復(fù)雜但每次審計都能篩出點東西。做安全固件工程永遠(yuǎn)假設(shè)敵手有能力篡改普通世界的任意代碼EL3固件就是最后的防線所有邊界都要按最壞情況去驗。4. 平臺移植落地指南從一個新SoC到跑通BL314.1 移植前的準(zhǔn)備工作清單拿到一塊新SoC先把這幾樣資料備齊SoC的TRMTechnical Reference Manual至少要包含內(nèi)存映射、GIC配置和UART基地址參考板子的原理圖確認(rèn)串口、電源控制和復(fù)位邏輯GIC版本信息是GICv2還是GICv3這直接影響代碼路徑最后是芯片廠商如果有現(xiàn)成release強(qiáng)烈建議先拿官方的ATF分支跑通再對比mainline。資料備齊之后確認(rèn)編譯工具鏈。ATF官方推薦armclang或GCC社區(qū)用得最多的是aarch64-linux-gnu-gcc。這里提一個容易踩的坑交叉編譯器版本太老或太新都可能引發(fā)鏈接腳本兼容問題我建議先用系統(tǒng)包管理器里的最新穩(wěn)定版出問題再降級不要一開始就在工具鏈上較勁。構(gòu)建時用make PLAT你的平臺工具鏈通過CROSS_COMPILE指定。4.2 新建平臺目錄和platform_def.h核心定義以qemu平臺為模板我會在plat/目錄下新建plat/myboard/myboard目錄然后創(chuàng)建platform_def.h、platform.mk、plat_topology.c這三個基礎(chǔ)文件。platform_def.h負(fù)責(zé)定義內(nèi)存地址和物理基址MAC常量例如BL31_BASE、BL31_LIMIT、UART_BASE、GICD_BASE、GICC_BASE還有PLAT_PHY_ADDR_SPACE_SIZE等。這些地址必須和SoC手冊完全一致差一個bit啟動就掛。實際寫的時候先抄一個結(jié)構(gòu)相近的現(xiàn)有平臺比如qemu或fvp然后把地址全部替換成自己SoC的地址。我見過很多人上來就寫很多初始化邏輯結(jié)果只是platform_def.h里的UART地址寫錯卡在串口無輸出上查了一天。記住平臺移植第一步是讓串口能輸出UART地址正確性優(yōu)先于一切。4.3 BL2到BL31的加載驗證邏輯怎么接平臺目錄里最常見的是plat_get_bl31_params和plat_get_next_bl_params這類接口的實現(xiàn)。它們負(fù)責(zé)把BL2解析出來的鏡像信息傳遞給BL31。新平臺如果不做特殊定制直接復(fù)用common代碼里基于param結(jié)構(gòu)的默認(rèn)實現(xiàn)即可。真正需要自己寫的是平臺初始化函數(shù)比如bl31_platform_setup里要配置GIC、初始化串口、建立MMU映射。GIC的初始化是移植里最煩人的部分。GICv2比較簡單初始化Distributor和CPU Interface就行GICv3則要處理Re-distributor、LPI、ITS這些復(fù)雜機(jī)制。ATF自帶drivers/arm/gic/v3驅(qū)動平臺代碼里只需提供GICD_BASE、GICR_BASE等基址并調(diào)用gicv3_driver_init然后gicv3_distif_init。注意別漏掉對GICR的校驗否則中斷路由會靜默失敗系統(tǒng)看起來能跑但外部中斷一個都進(jìn)不來。4.4 完整構(gòu)建和FIP鏡像打包移植完成后的構(gòu)建流程一般是兩條線。一條是純函數(shù)級驗證直接make PLATmyboard bl31生成BL31鏡像另一條是完整啟動需要先生成證書打包FIP。證書生成依賴cert_create工具同時需要提供私鑰和公鑰。自測環(huán)境可以用開發(fā)密鑰量產(chǎn)則必須換成硬件信任根保護(hù)的密鑰。命令大致是這樣構(gòu)建ATF本身以及FIP的典型命令qemu/qemu等平臺通用邏輯# 先設(shè)置工具鏈 export CROSS_COMPILEaarch64-linux-gnu- make PLATmyboard DEBUG1 bl31 # 生成platform key測試用 cd tools/cert_create make PLATmyboard ./cert_create -n -o debug_key.pem -t debug_key.pem # 打包FIP加載U-Boot作為BL33 make PLATmyboard TBBR1 DEBUG1 \ BL33u-boot.bin \ TRUSTED_KEY_CERTfip_output/trusted_key.crt \ fip實際項目里crt和key的管理很復(fù)雜我建議先跑通非安全啟動也就是不開啟TBBR驗證等整個平臺能啟動到BL33之后再逐步打開簽名驗證。直接一上來就上全套Trusted Boot排查問題的難度會翻好幾倍。順序很重要先串口、再BL31、再FIP、最后開TBBR每步都驗證通過再往下走。4.5 用FVP和QEMU做無硬件預(yù)驗證如果你手頭還沒有真實硬件別慌ATF官方提供了FVPFixed Virtual Platform模型QEMU也有virt平臺支持。這兩個環(huán)境不僅能跑完整的BL1到BL33啟動鏈還能配合調(diào)試器做斷點和寄存器查看。我強(qiáng)烈建議先把代碼在FVP上跑通再遷移到真實SoC很多明顯的邏輯錯誤和地址錯誤在模型上會立刻暴露出來。FVP上調(diào)試的經(jīng)典技巧是用串口輸出加斷言。ATF本身有非常多的assert打開后任何MMU配置錯誤、內(nèi)存越界都會快速崩潰并留下線索。配合GDB遠(yuǎn)程調(diào)試可以直接停在異常向量入口通過ESR_EL3寄存器判斷異常類型定位到具體是MMU fault還是SMC錯誤這套流程在真實板子上也適用。5. 常見問題與排查技巧實錄5.1 串口無輸出先把輸出環(huán)境做對ATF移植遇到的第一個高頻問題就是串口完全沒輸出。大多數(shù)情況不是代碼邏輯錯誤而是platform_def.h里的UART地址和真實SoC不一致或者UART的時鐘分頻沒配上。我一般會先做一次極簡驗證用匯編或極簡C直接向UART硬件寄存器的FIFO寫字符確認(rèn)硬件通路可用再回頭看ATF初始化。另一個容易忽視的是編譯選項。DEBUG1和LOG_LEVEL的設(shè)置直接影響串口輸出量如果DEBUG關(guān)閉且LOG_LEVEL40LOG_LEVEL_NONE那BL31起來后可能什么都不打印。把make命令加上DEBUG1 LOG_LEVEL50LOG_LEVEL_VERBOSE能看到大量平臺初始化和請求流轉(zhuǎn)信息排查問題速度快得多。5.2 BL31加載后立即崩潰的定位思路BL31崩潰比串口無輸出好定位一些因為可能已經(jīng)打印了部分日志。常見原因有三種BL31鏡像的加載地址和鏈接地址不一致導(dǎo)致PC跑飛GIC初始化時訪問了未映射的寄存器地址MMU配置把代碼區(qū)域設(shè)錯成不可執(zhí)行或不可讀。前兩種都會伴隨ESR_EL3異常值第三種則表現(xiàn)為PSTATE和返回地址異常。遇到這類問題我的排查順序是先看崩潰時的fault address和ESR_EL3對照異常類型表確定是數(shù)據(jù)異常還是指令異常然后反查鏈接腳本確認(rèn)BL31_BASE和LMA是否匹配最后用GDB掛在qemu/FVP上把反匯編和寄存器打出來基本能鎖定問題。一定不要憑感覺改代碼ATF這類底層固件猜問題的成本極高。5.3 開啟TBBR后啟動失敗的常見原因開啟Trusted Board Boot后啟動鏈路會多出證書加載和簽名驗證兩個環(huán)節(jié)。最常見的失敗是“Auth image failed”這類錯誤。先檢查FIP里是否包含了證書再確認(rèn)燒進(jìn)SoC的ROTPK是否和生成FIP時使用的公鑰一致。這兩個根因占了TBBR失敗案例的八成以上。另外回滾保護(hù)的設(shè)計也容易出狀況。如果BL1的修訂計數(shù)器比BL2鏡像高BL2會無法加載導(dǎo)致平臺卡在啟動早期。排查辦法是檢查NV counters值必要時清空或重布防值。要注意不同廠商對counter存儲的實現(xiàn)差距很大有的存在OTP有的存算改區(qū)操作前一定要確認(rèn)機(jī)制否則改壞就是永久變磚。5.4 幾個值得存進(jìn)筆記的調(diào)試技巧最后分享幾個我實際項目里反復(fù)用到的調(diào)試技巧。第一ATF支持通過串口交互進(jìn)入Console打開后可以手動執(zhí)行一些調(diào)試命令查看內(nèi)存和寄存器這在排查BL31狀態(tài)時非常有用。第二fiptool的--dump參數(shù)可以直觀看到FIP內(nèi)的鏡像和證書信息懷疑打包問題時先用它確認(rèn)。第三加自己的打印日志時優(yōu)先用NOTICE級別的宏別用INFO否則后續(xù)會被日志淹沒。第四每次改動platform.mf文件后務(wù)必重新編整個fip不要只編bl31替換平臺宏變化經(jīng)常導(dǎo)致二進(jìn)制布局不一致。這些都是實打?qū)崗恼{(diào)試現(xiàn)場積累出來的每一句背后都是至少幾個小時跟代碼死磕的代價。拿出來分享是希望你能少走這些彎路把時間花在真正需要思考的架構(gòu)和安全性問題上。