ARM可信固件ATF深度拆解:源码架构、安全审计与移植实战
聊ATFArm Trusted Firmware现在更多叫 TF-A之前我先说个事。最近在帮客户做新平台 bring-up一位做内核的老手问我我们的 U-Boot 都从存储介质里跑起来了为什么还非要先跑一段什么 BL31这个问题我太熟悉了。很多人能把 U-Boot 和 Linux 玩得飞起但 U-Boot 之前那几百 KB 安全固件在干什么却很少有人真正关心。直到业务要做安全启动、要上 TEE、要过运营商或行业认证才回过头来啃 Arm Trusted Firmware。这篇东西我打算直接从源码层把 ATF 的架构拆一遍把安全固件工程审计要看的点梳理一遍再把平台移植落地这条主线走一遍。相当于一份“源码测评 工程审计 移植实战”的三合一记录。对刚接触 ATF 的人可以当成一份少走弯路的路线图对已经在做固件和 BSP 的人也可以对照检查自己平台实现里有没有埋坑。1. 为什么需要 ATFARM 安全固件的底层逻辑1.1 没有 ATFTrustZone 只是一句口号ARM 的 TrustZone 技术大家应该都听过把一个物理核分成安全世界Secure World和非安全世界Normal World通过 SCR_EL3 等系统寄存器的 NS 位来切换上下文。问题是上电之后谁来初始化这个安全环境非安全世界的代码可不可以直接访问安全内存Secure Monitor CallSMC指令触发之后又是谁来分发处理这些活儿总得有人干。不搞 ATF当然也可以很多老平台就是自研一段 Monitor 代码放在 EL3专门做 SMC 分发和世界切换。但这带来的问题很实际每个芯片厂各写一套接口五花八门OP-TEE 不知道该怎么对接U-Boot 想调 PSCI 接口也得适配不同固件。这就是 ATF 存在的意义——它是 ARM 官方提供的、跑在 EL3 的开源安全固件参考实现定义了从 BL1 到 BL31 的标准启动流程同时规定了 PSCI、SMC、TBBR 这些标准接口。有了它TEE 厂商、OS 厂商、芯片厂商才不用互相迁就对方私有协议。1.2 ATF 在整个 ARM 生态中的位置在 arm64 平台正常启动链里顺序一般是BootROM - ATF BL1 - ATF BL2 - ATF BL31 OP-TEEBL32 U-Boot/UEFIBL33- Kernel。BL31 常驻 EL3充当整个世界切换的“守门员”。你可以把 BL31 理解成一座大楼的门禁系统非安全世界的 App 想进安全世界办点事得先把 SMC 请求递给门禁门禁查验身份、转发给对应服务再把结果带出来。现在主流 ARMv8 / ARMv9 的高速 SoC从手机芯片到服务器芯片几乎都在用 ATF 作为 EL3 固件底座。即使芯片厂商有自研的 secmonitor多半也会在 SIPSilicon Provider接口上兼容 ATF 的调用风格因为生态已经绑定了。这就是为什么做底层开发和整机安全的工程师没法绕开它。2. ATF 源码架构全景从 BL1 到 BL312.1 启动分区BL1/BL2/BL31/BL32/BL33 各凭本事ATF 把固件拆成多个启动阶段每一级都有明确职责。BL1Boot ROM通常固化在芯片 ROM 或 OTP 区上电最先执行。它做最基本的 CPU 初始化、设定 EL3 异常向量、加载 BL2 并做安全校验。BL2Trusted Boot Firmware运行在 EL1 Secure 状态负责初始化 DDR、加载 BL31、BL32、BL33并校验镜像签名。U-Boot 和 OP-TEE 的镜像都由它搬到内存里。BL31EL3 Runtime Firmware运行在 EL3是常驻的监控器。负责 SMC 分发、PSCI 电源管理、Secure-EL1 插桩、安全中断处理等。kernel 和 U-Boot 调 PSCI 指令实际执行方就是 BL31。BL32Optional TEE OS例如 OP-TEE、Trusty、QSEE跑在 Secure EL1。BL31 负责完成安全世界上下文切换让 TEE 可以执行安全服务。BL33Normal World Bootloader就是 U-Boot、UEFI 等最终引导 Linux/Windows/RTOS。每次世界切换不只是把 PC 指过去还要搞定寄存器上下文、中断、MMU 页表、内存权限。ATF 在lib/el3_runtime/aarch64/context_mgmt.c里维护了一套cpu_context结构把通用寄存器、系统寄存器、安全状态、中断路由等全部打包管理。这也是我最推荐的阅读起点看懂 context 管理ATF 的一半就通了。2.2 源码目录与关键文件把工程地图画出来直接用克隆下来的源码说事。ATF 仓库根目录下有这些一级目录最值得关注的是bl1/、bl2/、bl31/各启动阶段主体代码。plat/平台相关代码芯片厂商新平台基本都从这里的参考板 copy。plat/arm/board/下有 FVP、Juno 等 ARM 官方板。plat/qemu/是 QEMU virt 平台支持非常方便学习。plat/rockchip/、plat/mediatek/、plat/nxp/等是社区维护的实际 SoC。lib/el3_runtime/EL3 运行时核心SMC 分发、上下文管理都在这里。lib/psci/PSCI 标准实现。lib/xlat_tables/、lib/xlat_tables_v2/MMU 页表与内存翻译表安全隔离的关键。drivers/arm/ARM 官方外设驱动比如 GIC、TZC400、DMC500、CCI/CCN。services/SMC 服务登记与实现std_svc、arm_arch_svc、spm 等。tools/cert_create/生成证书链的工具。tools/fiptool打包 FIP 镜像的工具。我一般拿到一个新平台源码先看plat/vendor/board/platform_def.h。这个文件基本把这块板子的 SRAM 大小、BL31 加载地址、外设基址、GIC 布局全定义了相当于一个“平台身份证”。然后看plat_setup.c、bl31_plat_setup.c这类入口初始化文件再去看 GIC 和串口驱动接入方式比闷头从 BL1 入口读快得多。git clone https://github.com/ARM-software/arm-trusted-firmware.git cd arm-trusted-firmware # 以QEMU虚拟平台为例先看几个关键文件 ls plat/qemu/ cat plat/qemu/platform_def.h实战经验是ATF 阅读不建议按启动顺序从头读到尾。BL1 是 ROM 代码与 SoC 内部 BootROM 强相关通用部分不多BL2 只是个“搬运工校验器”逻辑相对直白真正的重头戏在 BL31。把 BL31 的初始化流程、SMC 分发、PSCI 实现看明白工作中 90% 的问题都能定位到方向。2.3 关键服务PSCI、SMC 分发、TBBR 信任链PSCIPower State Coordination Interface是 ATF 对外最核心的服务。U-Boot 和 Linux 在非安全世界不能像裸机那样随便关核、开核、操作电源管理而是通过 SMC 指令把请求交给 BL31 处理。PSCI 在lib/psci/下实现提供 CPU_ON、CPU_OFF、CPU_SUSPEND、SYSTEM_OFF、SYSTEM_RESET 等标准调用。以 CPU0 启动 CPU1 为例流程大致是Normal World 执行SMC #0携带PSCI_CPU_ON_AARCH64参数。CPU 陷入 EL3ATF 的 SMC 分发器根据 Function ID 找到 PSCI 服务。PSCI 内部调用平台提供的pwr_domain_on操作。在目标 CPU 上被唤醒后进入 BL31 的入口完成上下文初始化再将 PC 设置成 Normal World 指定的 entry point。在实际调试中我见过太多“CPU1 起不来”“suspend 后唤醒崩溃”的问题最后都落在 PSCI 平台操作上。ATF 本身提供了 for 循环、spinlock、cache 维护等通用逻辑但具体到每个核的 power domain 怎么开、GIC 的 SGIs 怎么路由、锁 cache 的地址在哪每个芯片差异非常大。SMC 分发机制是 ATF 作为 Monitor 的“总前台”。SMC 指令以 32 位 Function ID 区分服务。ATF 用DECLARE_RT_SVC()宏注册 runtime service这些服务描述符会放在一个指定的链接段里运行时根据 Function ID 范围RT_SVC_DESC逐一比对。写一个自定义的安全 monitor 调用本质上就是注册一个服务在smc_handler里判断参数然后返回结果。代码位置主要在bl31/bl31_main.c和lib/el3_runtime/aarch64/runtime_exceptions.S。TBBRTrusted Board Boot是 ATF 提供的安全启动参考实现。它定义了一套证书链每个镜像都有一张证书证书里包含镜像 HASH 或公钥上一级证书用私钥签名最底层信任根存在 SoC eFuse 或 OTP 中。tools/cert_create生成证书tools/fiptool打包 FIP 镜像。启用安全启动需要同时打开TRUSTED_BOARD_BOOT1和GENERATE_COT1并在 BL1/BL2 中实现 ROTPK 读取。这部分是所有安全认证如 PSA、行业安全评估的硬指标我对它的定义是没有 TBBR安全固件审计就缺少第一块基石。3. 安全固件工程审计代码层面到底查什么3.1 启动信任链证书与镜像校验如何串成一条线安全固件审计的第一件事就是验证“信任链是否闭环”。ATF 的 TBBR 设计的是层级认证BL1 被认为是 BootROM 固化逻辑自带信任根BL1 验证 BL2 的证书与镜像BL2 验证 BL31、BL32、BL33。审计时重点看三处ROTPK 的存放与读取是否真的绑定到 eFuse/OTP还是编译期间写死在代码里。证书格式和 signature 算法是否支持 SHA256/RSA2048 及以上安全强度。很多老方案还在用 ECC 192 或 RSA 1024放到今天已经不适合过安全评估。失败处理路径。镜像校验失败后是死机、进入恢复模式还是直接跳过去后一种是致命的。我审计过一个平台BL2 校验 BL31 用的公钥是从 FIP 包里读的而不是从 TrustAnchor 里取的。这意味着攻击者如果篡改 FIP 的同时替换掉公钥和签名BL2 依然能通过校验。这种问题在代码 review 时很容易被漏掉因为它不是崩溃、不是死循环而是“校验逻辑写得像模像样但信任根被架空了”。3.2 内存与权限隔离xlat_tables 和 TZC 的配合ATF 里的“安全内存”不是说说而已它有两层防护。第一层是 CPU 内部 MMU 页表的权限管理第二层是总线层面的 TrustZone Address Space ControllerTZASC/TZC对物理内存区域的访问控制。lib/xlat_tables_v2负责构建 BL31 的页表。每个区域的属性都会标上MT_SECURE/MT_NS、MT_RO/MT_RW、MT_EXECUTE_NEVER等。审计时要一块块地图对照BL31 自己的.text段必须是 Secure RO栈和设备寄存器是 Secure RWNormal World 能访问的区域绝对不能带 Secure 属性也不应该有执行权限。很多漏洞来自mmap_add_region时权限写宽了比如把某个非安全外设的地址误标成 Secure或者把 Secure 内存标成了可执行。总线层面的 TZC 是最后一道闸门。它按地址区间设置访问权限非法访问会被硬件拦截。我在新平台 bring-up 时就吃过亏BL31 已经起来了但 OP-TEE 的共享内存一访问就 panic查半天发现是 TZC 配置里非安全世界根本没有放行那块 shared memory。这类问题没有捷径只能把platform_def.h里的内存布局和 TZC 配置逐一对照核验。审计要点表我通常做成下面这种速查表带在项目里审计项检查内容常见问题ROTPK / TrustAnchor公钥来源是否为硬件信任根公钥从 FIP 读取、可被替换BL2 镜像校验算法是否 SHA256/RSA2048 以上算法强度不足或未比较完整哈希FIP 包完整性证书、镜像嵌套结构是否有冗余校验失败后仍继续启动xlat 页表权限Secure/NS、RO/RW、XN 标记权限标宽、可执行内存暴露TZC 地址区间NS 世界能访问的物理内存范围共享内存配置错误导致 panicGIC 安全配置Secure 中断组与路由安全中断被路由到 Normal WorldBL31 入口异常向量未定义指令、SError 处理处理函数栈溢出或死循环PSCI 返回码电源操作失败是否返回错误静默失败导致内核 hang3.3 常见审计点清单代码 review 时必须盯的洞除了信任链和内存权限实际工程里还有几个点很容易翻车。GIC 安全配置。ARM GIC 把中断分成 Group0Secure和 Group1。ATF 里通过plat_arm_gic_driver_init、gicv3_driver_init初始化 GIC审计时要确认安全中断比如 TEE 的 SGI、GT 定时器中断配置到了 Group0而且中断路由没被异常改写。如果 Group0 中断被误配到非安全世界等于开了一个通道让 Normal World 能触发安全中断后果可想而知。异常向量与栈保护。BL31 的异常向量表在bl31/aarch64/bl31_entrypoint.S中每个异常入口都要有足够大小的栈。低端平台 SRAM 紧张时栈大小会被压缩得很极限审计时要算一算嵌套异常的最大栈深度。我见过一个平台安全中断出现时 BL31 栈溢出直接踩坏了相邻的.bss导致系统隔几个小时随机重启一次查了很久才定位到。信息泄露。BL31 的错误日志和 panic 打印不要过度暴露内存地址。生产固件一般会关掉 DEBUG 构建用NOTICE/ERROR控制打印级别。如果审计时看到printf把 buffer 原始内容打出来就要警惕敏感数据泄露风险。4. 平台移植落地从零把 ATF 跑起来4.1 选参考平台与目录组织移植 ATF 第一步不是写代码而是“抄作业”——从现有平台里找最接近的参考实现。如果你在调 QEMU 虚拟环境直接用plat/qemu。如果是自研 SoC找相同内存控制器、类似 GIC 版本、相近启动介质的设计参照。比如新平台 GIC 是 GICv3就重点参考plat/arm/board/fvp的实现。如果底层方案和树莓派类似可以看plat/rpi。目录组织一般是plat/vendor/ board/ platform_def.h # 平台地址、大小、外设基址 platform.mk # 编译选项、额外源文件 plat_setup.c # 平台初始化 aarch64/ plat_helpers.S # CPU 操作、互斥、cache 等汇编 bl31_entrypoint.S # BL31 入口钩子如需自定义最省事的做法是直接cp -r plat/arm/board/fvp plat/xx/yy然后把平台名、地址、GIC 配置全部改掉。ATF 的make系统支持PLATplatform选择平台目录你可以完全复用一个参考板再剪裁。4.2 平台配置与 make 参数ATF 顶层目录有一个Makefile会读取plat/vendor/board/platform.mk。平台移植时最常调整的宏包括宏/变量含义PLAT_MAKEFILE平台 Makefile 路径CRASH_REPORTING崩溃时是否打印寄存器现场BL31_BASE/BL31_SIZEBL31 加载地址和大小TRUSTED_BOARD_BOOT启用 TBBR 安全启动GENERATE_COT编译时生成证书链ARM_ARCH_MAJOR/MINOR架构版本例如 8.2HW_ASSISTED_COHERENCY是否依赖硬件缓存一致性USE_COHERENT_MEM是否使用 coherent memory 区域交叉编译的基本命令是make CROSS_COMPILEaarch64-none-elf- PLATqemu DEBUG1 bl31如果想生成 FIP 镜像需要指定BL33指向你的 U-Boot 镜像make CROSS_COMPILEaarch64-none-elf- PLATqemu BL33../u-boot/u-boot.bin fip注意DEBUG1会加大镜像体积、降低优化等级只用于调试量产固件不要用。4.3 移植时最容易被卡住的四个点第一BL31 起点和链接布局。如果 BL31 被加载到错误地址开机会立刻崩。要仔细核对platform_def.h里的BL31_BASE和 SoC BootROM 实际加载地址以及 BL2 传给 BL31 的入口参数是否一致。第二串口驱动。调试初期最需要串口可它往往是最早依赖平台寄存器配置的模块。ATF 常见的串口有console_16550、console_pl011、console_cadence等。如果 SoC 用的不是标准 IP就得在plat_setup.c里自建 console 接口。没有串口输出时一切问题都像黑匣子所以先把串口点亮是最优先任务。第三GIC 初始化。BL31 跑起来后,GIC 要在 EL3 配好中断路由。新的 GICv3 还涉及GICRredistributor 初始化每个核有自己的 redistributor 寄存器漏配会导致中断根本无法送达。初始化顺序错了会看到中断风暴或者 CPU 被 Security 中断反复打断动不动就 hang。第四PSCI 的电源操作。这一块最芯片相关。plat_setup_psci_ops要提供cpu_standby、pwr_domain_on、pwr_domain_off、system_reset、system_off等回调。每个回调都要处理好 cache、MMU、GIC、以及核间唤醒逻辑。我建议第一次移植只实现pwr_domain_on和system_off先把系统和多核跑起来再去碰 suspend/resume 这些高级状态。4.4 以 QEMU 为例跑通最小系统没有真实开发板时QEMU 是最低成本的验证手段。ATF 源码里已经带了plat/qemu理论上直接编译就能跑。步骤如下下载 U-Boot 并编译出u-boot.bin。编译 ATF生成bl1.bin、bl2.bin、bl31.bin和fip.bin。用 QEMU 启动wget https://releases.linaro.org/components/kernel/uefi-linaro/latest/release/qemu/aarch64/QEMU_EFI.fd # 可选 make CROSS_COMPILEaarch64-linux-gnu- PLATqemu BL33/path/to/u-boot.bin fip qemu-system-aarch64 -machine virt,secureon -cpu cortex-a57 -nographic \ -bios bl1.bin -m 1024这里-machine virt,secureon是关键的它让 QEMU 模拟出 EL3 和 TrustZone 环境。如果看到 ATF 的启动打印再从 BL31 跳到 U-Boot那你的 ATF 最小系统就算通了。真机移植时会比 QEMU 多很多坑比如 BootROM 加载 BL1 的限制、DDR 初始化时序、eFuse 安全位、内部 SRAM 大小不足等。但核心思路一致先把串口点亮再把启动链走通接着做地址和权限二次确认最后再做安全启动。5. 实操作战我的“问题与排查”记录5.1 编译与链接阶段的坑ATF 编译报错里最高频的是CROSS_COMPILE没设置或工具链不匹配。ATF 要求 GCC 工具链支持 aarch64并建议使用 GNU 工具链。经常有人问为什么make之后报一堆汇编错误检查后发现用的还是 x86 的gcc。这个错误在make阶段就会暴露换个aarch64-none-elf-或aarch64-linux-gnu-即可。第二个典型问题是BL31 镜像超出 SRAM 大小。链接时报region overflow比如bl31.elf超出BL31_SIZE。解决办法不是盲目加大BL31_SIZE而是先确认编译选项是不是 DEBUG 全开、是否有冗余驱动被编进来再调整内存布局。第三个问题是链接脚本里的 section 对齐问题。有些平台修改platform_def.h后.text或.bss的起始地址没有对齐到页表粒度导致 MMU 建页表时异常。ATF 启用了PAGE_SIZE一般 4KB对齐检查如果地址不对齐早期会给出Translation fault。移植时改BL31_BASE一定要保证 4KB 对齐。5.2 运行时异常与 PSCI 调试验证运行时遇到Synchronous Exception是最常见的情况。先打开CRASH_REPORTING1编译能看到 crash 时异常类型、ELR、SPSR 和 ESR。常见异常原因ESR_ELx.EC 0x25EL3 中发生了SMC说明调用参数可能没走对服务。EC 0x20Instruction Abort from current EL或 0x21PC alignment fault多半是跳转地址不对。EC 0x21 也可能是 BL31 跳到 U-Boot 时入口地址错误。PSCI 调用验证有一个很实用的工具就是直接在内核里操作 CPU 热插拔或 suspend/resume。但这属于“大动作”一旦失败整个系统就 hang 住不太适合调试早期。我习惯先在 U-Boot 阶段用psci命令手动调一下 CPU_ON看看目标核是否起来、能否打印一段自定义输出。这样能快速排除 PSCI 实现的问题。另外调 PSCI 时最好用SET_RUNTIME_SVC自定义一个调试服务通过 SMC 直接触发某个核的 on/off 操作不用依赖内核的 hotplug 框架。这样查问题更精准。5.3 与 U-Boot / OP-TEE 联调经验ATF 不是孤立的它总是和 BL33U-Boot和 BL32OP-TEE配合。联调时最容易踩的坑是FIP 包顺序和参数传递。BL2 在加载完 BL31/BL32/BL33 之后通过plat_get_next_bl_params把各个镜像的入口地址、上下文信息传给 BL31。如果 BL33 entrypoint 地址不对U-Boot 根本起不来。尤其注意 U-Boot 编译时如果启用了CONFIG_ARMV8_MULTIENTRY或者CONFIG_PSCI_0_2它和 ATF 之间的入口约定必须一致。很多“U-Boot 没反应”的问题其实是 ATF 侧传给 BL33 的 DTB 地址或 firmware handoff 参数格式不匹配。OP-TEE 联调时BL31 需要配置SPDopteed编译开关表示能通过 OP-TEE Dispatcher 把上下文切到 Secure EL1。此时 BL31 不只做 PSCI还要负责 OP-TEE 的启动与SMC转发。OP-TEE 起不来的常见原因安全内存区域的TZC没配、OP-TEE 加载地址与链接地址不一致、SPD 没编译进 BL31。在make时加入make ... SPDopteed BL32../optee_os/out/arm/core/tee.bin BL33../u-boot/u-boot.bin fip这样 FIP 中就会包含 BL32 镜像。OP-TEE 启动后通过xtest跑 TEE 用例再回正常世界基本能验证整个安全世界链路。6. 最后一点个人心得与建议做 ATF 移植和审计这几年我最大的体会是不要只把它当成一段“引导代码”。它其实是整个 ARM 安全架构的承重墙。U-Boot 可以随便换、Kernel 可以朝后兼容但 EL3 固件一旦出了问题轻则多核启动失败重则给整个系统留下一个无法用上层补丁修复的漏洞。如果你刚接触 ATF我个人建议的路线是先用 QEMU 跑通最小启动链再把 context manage、SMC 分发和 PSCI 三个核心模块源码精读一遍然后尝试在 QEMU 里增加一个自定义 SMC 服务。不要一上来就扑到真机上做移植那会被串口初始化、DDR training、GIC redistributor 一堆硬件问题淹没。另外想提醒一句安全固件审计这件事最忌讳的就是只看“代码跑得通”。跑得通只能证明功能正常不代表安全边界正确。ATF 这类固件审计的每一处内存属性、每一个外设安全配置都要拿“如果 Normal World 被攻破这扇门会不会被推开”的标准重新审视一遍。毕竟这条 TrustZone 边界是整台设备最后的安全底线。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →