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