ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Arm Trusted Firmware源码审计与平台移植实践:从EL3到可信启动

Arm Trusted Firmware源码审计与平台移植实践:从EL3到可信启动 如果有人问我 ARM64 SoC 上电之后第一段可读代码到底在干什么我现在的回答会很直接它大概率在跑 Arm-Trusted-Firmware也就是大家嘴里的 ATF。这个东西的官方名字现在已经叫 TF-A但整个行业里提“ATF”还是能瞬间对上频道。它解决的问题一句话能说清在一颗 ARMv8/AArch64 甚至 Armv9 的 SoC 上谁来决定启动链上一级固件可不可信谁在最高权限的 EL3 里帮你管安全世界谁承载 PSCI、安全中断、可信启动这些不能丢的功能。答案是 ATF。我最近花了几周时间做了一次比较完整的源码评测从bl1一路读到plat移植层同时搭了 QEMU 的裸机环境反复验证启动、FIP 打包和安全认证流程。这篇文章不写 PPT 式概念复述直接把我这次工程审计和平台移植过程中真正有用的东西整理出来。你如果正在做安全固件相关的评估或者马上要接手一块新板子的 bring-up这里面的思路和清单应该能帮你少踩几个坑。1. 冷启动的第一行代码ATF被设计来扛什么1.1 异常级别、安全世界和启动阶段的三角关系现代 ARM 处理器不是靠某一个寄存器实现安全的而是靠异常级别加世界隔离组合出来的。EL0 到 EL3数字越大权限越高EL3 是最高特权等级能访问所有内存和外设也负责切换异常返回状态。EL3 之下还划分了 Normal world 和 Secure world两条世界都有各自的系统寄存器视图。ATF 干的事情就是把这些异常级别和安全世界从“硬件能力”变成“软件事实”。上电后的启动顺序很经典ROM 里的 BL1 先跑完成最基础的 CPU 和内存控制器初始化把 BL2 放到一段可信内存里并验证BL2 是可信启动的承上启下者负责验证并加载 BL31、BL32、BL33BL31 是 EL3 Runtime Firmware启动完成后留在 EL3 里长期服役处理 PSCI、SDEI、安全中断、SMC 调用这些事情BL32 是可选的可信 OS比如 OP-TEEBL33 才是你熟悉的那类引导程序U-Boot 或者 grub 都算。这里面的信任关系是单向递进的BL1 不信任 BL2必须验签名BL2 不信任 BL31/BL32/BL33也验签名。每一级都有“下一次要运行的东西不可信”的假设这正是安全固件和普通固件在思维方式上最大的不同。1.2 ATF和U-Boot/Linux的身份差异很多人第一次看 ATF 会下意识把它和 U-Boot 放在一起比较觉得都是“引导程序”其实这俩压根不在一个世界里。U-Boot 跑在 Normal world 的 EL2/EL1面向的是外设驱动、文件系统、启动内核ATF 跑在 EL3 和 Secure world面向的是安全启动、电源状态协调、可信内存隔离。换句话说U-Boot 是“帮你把系统跑起来”的东西ATF 是“决定你信不信任那个把系统跑起来的东西”的那个东西。如果 ATF 被攻破后面所有固件都谈不上可信反过来 U-Boot 被搞掉ATF 至少还守着 EL3 的底裤。理解这个边界后面看源码才不会被一堆寄存器操作绕晕。1.3 工程审计为什么绕不开这颗固件做固件安全审计的人拿到一块板子第一件事往往不是看应用层代码而是先抠出启动链的每一级镜像然后问信任根在哪证书链怎么组织是否有回滚保护调试口关了没有这一整套问题的落点最后基本都指向 ATF 的 TBBTrusted Board Boot和平台移植代码。所以我把这次评测的重点放在源码结构和工程实现上而不是单纯讲 ARM 安全原理。2. 源码地图哪些目录决定了可信度的边界2.1 顶层目录职责速查ATF 源码的顶层目录排布非常直白第一次接触的人花半天就能看明白大概。我整理了一份我在评测时常用的职责对照表目录/文件职责涉及的安全边界bl1/Boot ROM 级引导代码信任根最早期初始化bl2/Trusted Boot Firmware加载并验证 BL31/BL32/BL33bl31/EL3 Runtime FirmwareSMC 处理、PSCI、运行服务bl32/可信 OS 入口支持Secure world 生命周期common/镜像描述和加载框架启动镜像解析lib/页表、PSCI、EL3 运行时库MMU 隔离、上下文管理plat/平台移植代码内存布局、外设安全配置drivers/auth/认证驱动框架证书解析和验签路径tools/fiptool、cert_createFIP 打包、证书生成这个表不是给你背的而是让你知道如果做安全审计重点应该在drivers/auth、bl31、plat这三个地方。如果做平台移植重点则在plat/your_platform目录和make helpers那套构建逻辑。2.2 可信启动到底落在哪条代码路径ATF 的可信启动不是“调一个验签函数”就完了它是一套完整的认证框架。tools/cert_create负责生成平台证书和信任链drivers/auth负责在固件运行时对加载的镜像做校验。关键路径我可以简化成三步BL1 或 BL2 读到镜像描述符根据描述符里定义的认证方法去解析证书证书里的公钥再对镜像摘要做验签。真正的“干货”在于验签动作是在auth_mod_verify_signature这类函数里完成的平台移植代码必须在镜像描述符里正确配置REF参考值、VALUE期望值和加密算法这套机制才会真正生效。我在审计某些第三方 SoC 移植版本时见过一个现象平台代码看着启用了TRUSTED_BOARD_BOOT但认证描述符里的算法字段被写成了空值导致实际跳过了验签。这种问题在源码审计里属于最高优先级风险因为表面上有安全启动实际上形同虚设。2.3 哪些代码直接影响安全边界我评估一块移植版是否安全通常会先看四个东西页表配置、TZC 配置、SMC 分发表、证书链描述。页表配置在plat层决定哪些内存是 secure 的TZCTrustZone Controller决定 DDR 区域谁能访问SMC 分发表决定了 EL3 会响应哪些来自普通世界的请求证书链描述则决定 BL2 到底验不验证 BL31/BL33。这四个点不是独立的它们共同构成了 ATF 的安全边界。后面第 3 章我会具体展开审计方法。3. 工程审计我是怎么查ATF移植版是否还安全3.1 先看认证流程是否真的被接通做安全固件审计我拿到代码第一件事不是跑编译而是静态搜一下认证相关函数的调用链。会重点看plat/board/plat_tbbr这种文件里的证书定义确认 BL31、BL33、BL32 镜像都有对应的证书链并且img_parser_mod和crypto_mod都正确挂载了。这里分享一个审计时的小技巧直接查所有get_auth_param返回值和auth_mod_verify_signature的调用位置然后看有没有被#if DEBUG或#if !ENABLE_ASSERTIONS这种条件编译包住的坑。正常产品代码不该在调试宏里跳过验签。另外还要确认ROTPK的存放位置通常是在 SoC 的 eFuse 或 ROM 里做根如果某块板子量产固件仍然从外部 SPI Flash 读根密钥那这个信任根从物理上就不成立。3.2 内存防火墙与MMU配置ATF 的隔离不止靠异常级别内存访问控制同样重要。BL31 使用xlat_tables_v2建立页表把 EL3 代码和数据锁在可信内存区域DDR 端则有 TZC 控制器做硬件防火墙普通世界的 DMA 无法访问 secure 区域。审计时我会重点看plat_get_mmap_desc返回的映射表里BL31 image 所在内存是否被标记为SECURE且DEVICE属性正确TZC 配置中是否为 secure RAM 区域显式配置了TZC_REGION_S_NONE之类的权限还是简单粗暴地把整块 DDR 都放给非安全世界。后者在量产板上很常见往往是因为调试方便但这种配置意味着一旦普通世界被攻破EL3 的代码和数据就在内存里裸奔。3.3 运行时服务与SMC入口是攻击面最大的地方普通世界的 U-Boot、Linux、甚至用户态程序都可能通过SMC指令陷入 EL3。ATF 用rt_svc_descs注册运行时服务每个服务负责一部分具体的 SMC 调用。审计这里的原则很简单服务端点越少越好入参校验越严格越好。我看到过的问题通常有两种一是产品为了调试方便在 EL3 加了自定义 SMC比如直接读内存、写寄存器但没做调用权限区分任何普通世界代码都能触发二是对参数长度和物理地址没做范围检查传入一个 EL3 地址空间的地址就直接操作。这类代码在调试期很爽量产期就是后门。所以我的审计结论里只要出现自定义 SMC 且没有get_el权限判断基本直接判高风险。3.4 高风险检查清单下面这张表是我每次做固件审计都会过的核心清单你可以直接拿去当地板评估的参考检查项关注点风险等级信任根本地化ROTPK 是否来自 ROM/eFuse高证书链完整性所有启动镜像是否都有证书描述高验签调用路径是否真正执行验签而非“假校验”高调试接口量产 UART/SWD/自定义SMC 是否关闭高内存映射BL31 内存是否 secure MMU 正确配置高TZC 区域是否保护关键 secure RAM/外设中高回滚保护证书/镜像是否有版本号并校验中SMC 参数校验是否检查地址范围、长度和调用层级中高安全状态切换Secure world 上下文是否完整保存恢复中构建产物是否使用 DEBUG0、断言关闭中这张表不能替代深入源码审计但它能帮你快速筛出“有没有明显的问题”。4. 平台移植落地把ATF完整移植到一个新板卡上4.1 移植前必须回答的七个问题我接过几块新板子的 ATF bring-up每次动手之前都会先逼自己回答下面这些问题答不上来说明资料还没备齐CPU 核数和 interconnect 拓扑是什么单 cluster 还是多 cluster小核大核怎么组织冷启动时从哪块内存跑 BL1/BL2SRAM 基地址和大小是多少串口用的哪个 IP寄存器基地址是多少PL011 还是 16550DDR 初始化谁做ATF 只需要做简单访问还是需要完整初始化GIC 是 GICv2 还是 GICv3BL31 需要配置哪些中断目标固件架构下有没有可信 OSBL32 需要留内存窗口吗量产时要不要开 Trusted Board Boot证书方案走 COT 还是自定义这几个问题的答案基本决定了plat目录里每个文件写成什么样。我之前见过不少人上来就抄qemu平台的代码结果拓扑、内存、GIC 全对不上编译过了也跑不起来最后回头补基础功课。4.2 最小集实现从platform_def.h到PSCI回调一个能跑的最小平台文件比你想象中少但每个文件都得接对。核心是platform_def.h里面定义串口基地址、SRAM 基地址、GIC 地址、最大虚地址空间这些宏。然后是plat_helpers.S提供plat_my_core_pos、platform_get_core_pos这类基础函数plat_topology.c描述 cluster/core 拓扑plat_psci.c实现plat_setup_psci_ops注册 PSCI 回调。如果你是第一次做我建议拿 QEMU 平台当参照物逐步把qemu目录下的实现换成自己板子的值。QEMU 的好处是没有真实外设那些坑GIC、串口、内存都规规矩矩能让你集中精力理解 ATF 的框架而不是跟硬件 bug 搏斗。等 QEMU 上跑通、看懂了再换真实板子会从容很多。4.3 构建、FIP打包与QEMU验证ATF 的构建系统是标准的 make 体系常用命令长这样make PLATqemu \ BL33../u-boot/u-boot.bin \ LOG_LEVEL50 \ BUILD_BASEbuild \ all fip这里PLAT指定平台目录BL33是正常世界引导镜像fip目标会用fiptool把所有 BL 镜像打包成一个fip.bin。如果你开启 TBB还要指定证书生成相关的GENERATE_COT1和根密钥路径。QEMU virt 平台验证时可以用类似qemu-system-aarch64 -machine virt -cpu cortex-a57 -smp 4 -m 1G -bios build/qemu/debug/bl1.bin -nographic的方式跑起来能看到 ATF 的 log 输出基本算移植成功了一半。验证重点不只是“能进 U-Boot”还要确认 PSCI 能正常工作比如cpu_online能拉起从核system_reset能重启。很多移植板看着起来了实际上从核启动和系统休眠这些功能完全没接后面跑 Linux 才发现问题。4.4 真实SoC移植时的差异QEMU 和真实 SoC 最大的差异在于真实 SoC 有独立的 SRAM 大小、有 TZC 控制器、有 DMA、有功耗域还要跟芯片原厂的 BootROM 配合。比如某些 SoC 的 BL2 必须由 BootROM 加载到特定 SRAM 并启动BL31 可能又得放到另一块保留区。这时候你不仅是在“移植 ATF”还在应对芯片原厂私有启动协议。这也是为什么很多厂商会在官方 ATF 上再做一层自己的 loader。遇到这种情况我建议先不追求跑通全部功能分阶段目标第一阶段让 BL1/BL2 能打印日志第二阶段把 BL31 和 PSCI 跑起来第三阶段再接 BL33。每个阶段都有明确的验证点比一次性全车上容易 debug。5. 调试实录这些坑不装一次真的踩不出来5.1 串口没有输出怎么判断是死机还是没初始化ATF 调试第一关永远是串口串口没输出后面全废。这时候先别乱改代码按这个链路排查确认串口基地址和驱动模型是否一致比如你平台用的是 PL011但代码里配成 16550那显然没有输出确认时钟是否已经使能确认是不是走到了 MMU 开启之后才初始化串口而页表把外设地址映射错了。我踩过最典型的坑是抄代码时宏定义里的UART_BASE多写了一个0十六进制地址直接错位导致 BL2 一访问串口就触发同步异常。这种问题从日志上看像“死机”实际是访问不存在的外设地址。所以调试 ATF 时优先把LOG_LEVEL调大然后在plat/common里加临时打印确认执行流走到哪一步比对着汇编猜快得多。5.2 BL31同步异常/panic多半是内存表或中断配置BL31 跑起来之后出现 panic最常见的原因是xlat_tables_v2配置的内存映射不够或者属性不对。比如你给 BL31 预留的 SRAM 只有 256KB但开了 DEBUG 加高 LOG_LEVEL栈一涨就越界了然后某个bl31_main里的函数调用直接触发异常。这类问题可以通过看 panic 信息里的 PC 值大概定位PC 落在哪段地址多半就是哪段内存的锅。另一个高频坑是 GIC 配置不对。GICv3 和 GICv2 的初始化路径差异很大如果平台配置的是 GICv2但代码里走了 GICv3 的 redistributor 初始化BL31 在启动阶段就会卡死。遇到这种问题先确认GIC600还是GIC400、用的是 legacy 还是 new scheme别一上来就怀疑 PSCI。5.3 工具链版本问题不要拿arm compiler 5.06来找ATF网上经常看到有人在找 arm compiler 5.06、arm compiler 5.06u7 这些老版本工具链我也能理解毕竟很多上古项目确实依赖它。但这里提醒一下ATF 是 ARMv8/AArch64 的信任域固件官方主流支持的是 GCC 或 Arm Compiler 6/LLVM 这类现代工具链你拿 Cortex-M 时代的 arm compiler 5.06 是想编译 AArch64 的 EL3 代码方向就错了。实际构建时我建议用aarch64-none-elf-gcc这类专门的交叉编译器版本选新的稳定版。如果你用的发行版里没有现成的包就直接去 Arm 官网下官方工具链少折腾那些从各种网盘流传的所谓优化版。工具链版本太老会导致链接脚本解析失败编译告警一堆运行时出诡异问题很难排查。5.4 开启TBB后证书校验失败怎么办TBB 在调试模式下很容易出现“启动镜像证书验证失败”的问题。原因通常不是验签算法错了而是证书与镜像版本不匹配。ATF 的cert_create工具生成证书时会把镜像摘要固化进去如果你修改了 BL31 或 BL33 镜像但没重新生成证书那加载时必然校验不过。遇到这种情况我的处理习惯是先看fip.bin里到底放了哪些镜像、哪些证书用fiptool info build/qemu/debug/fip.bin查看镜像列表确认 BL31 的 SHA256 和证书里保存的是否一致。如果一致还报错再看平台定义里ROTPK配置是否跟证书生成用的根密钥对应。调试阶段为了省事可以先不开 TBB等功能全部跑通再回头补安全启动不然每次调试都卡在证书上会极其痛苦。一点补充心得这次完整过完 ATF 源码评测和 QEMU 平台移植之后我最大的体会是ATF 的安全能力最终要看平台代码怎么接。框架本身把信任根、证书链、内存隔离、SMC 分发这些机制都搭好了但每块板子交到工程师手上还是要自己回答“我的设计是否真的把安全边界划对了”。这个问题没有标准答案只能结合具体 SoC 的 BootROM、TZC、GIC、内存拓扑逐项确认。最后分享一个实用小习惯建议每次改动平台移植代码前先跑一遍make PLATplat ... clean再全量重新构建。ATF 的构建缓存偶尔会保留旧的platform_def.h宏定义导致你改了串口地址或者内存布局构建产物里还是老配置浪费一整晚去查日志。加上BUILD_BASE把不同配置的构建产物分开会省心很多。
返回列表