6.3 安全扩展:TrustZone与指针认证


6.3 安全扩展:TrustZone 与指针认证

本节摘要:TrustZone 把整个系统分成安全与非安全两个世界,NS 位随总线事务流动、EL3 的安全监视器是唯一摆渡人;指针认证(PAC)则给函数返回地址装上签名锁,序言签名、尾声校验,改写返回地址的攻击在硬件层当场翻车。本节讲清两者的汇编层形态、使用方式与代价。

前两节的世界观是"一台处理器、一套代码"。本章最后一节补上现代 ARM 芯片的两个安全护栏——它们改变了这个世界观:TrustZone 让一台芯片同时扮演两个互不信任的世界,指针认证让"劫持函数返回"这类经典攻击在硬件层失去生存空间。两者都直接改写汇编代码的形态,是汇编工程师绕不开的现代现实。

TrustZone:一台芯片里的两个世界

TrustZone 的机制可以压缩成一个比特:每个总线事务都带着 NS 位(非安全标志),处理器状态同样分安全与非安全两半,安全状态发起的事务 NS 为 0,能访问安全内存与非安全内存;非安全状态发起的事务 NS 恒为 1,碰安全内存直接被总线拒绝——注意这是总线层的拒绝,不是操作系统层面的权限,非安全代码就算privileged满级也摸不到安全世界的一页。世界之间的唯一通道是 EL3 的安全监视器:非安全世界想请求安全服务(比如读硬件密钥),执行 smc 指令,异常落到 EL3,监视器验证请求、代办事宜、用 eret 送回结果。

安全世界不是另一个处理器,而是同一套寄存器堆的银行保险库模式:通用寄存器两世界共享(这正是攻击面——安全世界必须假设寄存器来的每个值都有毒,先消毒再用),栈指针与系统寄存器有银行库专用副本。对汇编层的实际影响有三:读芯片手册时要认出"哪些外设只在安全世界可见";写安全固件时要对非安全世界传来的指针做归属校验(smc 参数里的地址必须真的属于非安全内存,否则非安全代码借你的手读到了安全数据——这就是经典的"混乱代理"攻击);调试时安全世界的代码常常拒绝调试器旁观,这是特性不是故障。

PAC:给返回地址上锁的硬件

指针认证解决一个具体问题:返回地址劫持。第 5 章讲过,函数的返回地址躺在栈上,谁改了它,ret 就跳到哪——几十年来缓冲区溢出攻击的主通道都是它。PAC 的思路是把返回地址在压栈前签名、弹栈后校验:签名密钥藏在系统寄存器里,用户态代码摸不到;签名值挂在指针的高位比特(ARMv8 的虚拟地址只用低 48 位或 52 位,高位空着正好当签名仓库)。函数序言与尾声多出两条指令:

// -mbranch-protection=pac-ret 生成的函数序言尾声(对比无 PAC 版本) func: paciasp // 用 A 密钥给 x30 签名,签名值镶进 x30 高位 stp x29, x30, [sp, #-16]! mov x29, sp ... ldp x29, x30, [sp], #16 autiasp // 校验签名:栈上地址若被改过,高位变毒位 ret // 毒位地址触发翻译错误,攻击当场翻车

解读这套机制的关键在"失败模式":autiasp 校验失败并不立刻抛异常,而是把 x30 的高位改成非法形态,等 ret 真去跳它时以翻译错误收场——把"地址被改"延迟到"跳转失败",硬件实现简单且攻击者拿不到修复机会。代价呢?每函数两条指令加一次密码运算,微基准下高调用密度场景有百分之几的开销,主流系统默认打开。汇编层的直接后果:反汇编里看到 paciaspautiasp 别当成噪声,它们是安全策略的指纹;手写汇编与 C 混编时要保持保护开关一致,一半签名一半不签的调用链会出校验错配。

图 6-4:两道安全护栏的形态对比

图 6-4:两道安全护栏的形态对比

使用方式与代价的账本

两道护栏的使用门槛都比想象低。TrustZone:应用开发者通常不直接碰它——手机上的指纹与支付功能跑在安全世界,你只是它的用户;嵌入式与固件岗位则绕不开,安全的分区表、密钥存储、可信启动都是 TrustZone 的工程化。PAC:一条编译开关的事(-mbranch-protection=pac-ret 或系统级默认),应用代码零改动。代价账本要诚实:TrustZone 的代价是复杂度——安全与非安全固件要分别开发、分别升级、互相验证,系统工程量近于两套系统;PAC 的代价是零星性能与偶发的兼容坑(手写汇编忘记配对签名)。我们的建议与全书立场一致:先在汇编层认识它们的形态(本文的任务),用不用、怎么用属于架构决策,但看不懂它们的代码形态,读现代二进制的路就走不全。

深挖:第三道护栏 MTE 与安全调用的家族图谱

PAC 之外,ARMv9 还铺了第三道护栏:内存标签扩展(MTE)。思路是把每个 16 字节内存块贴一个 4 位标签,指针高位携带同款标签,访存时硬件自动对账——标签不符立即报错。它抓的是 PAC 管不到的另一类事故:越界写与释放后使用改写了"数据区",指针本身没被劫持。三道护栏合起来,防线从"锁回路"(PAC)扩展到"看守数据"(MTE)与"划世界"(TrustZone),各自独立开关。

安全调用也有家族图谱可背:svc 进内核是常规系统调用;hvc 进 EL2 是虚拟化调用;smc 进 EL3 是世界切换。三者的入口在向量表里各占独立槽位(6.1 节的十六槽地图里同步异常按来源分级),参数传递都走 AAPCS 的寄存器约定,但每个世界对参数的信任等级不同——内核可以大致相信应用,EL3 必须全盘怀疑。读安全固件源码时,函数开头成排的指针校验不是防御性编程的仪式感,而是"混乱代理"攻击(6.3 正文提过)的现实防御。

常见问答

问:开了 PAC 之后手写汇编还能自由跳转吗? 答:跳转自由,返回受限。blbr 不受 PAC 约束,受约束的是"用栈上保存的 x30 做 ret"这条路——签名与校验围绕它展开。所以手写汇编只要不自己乱改栈上的返回地址,PAC 完全透明;若你的代码故意玩"改返回地址实现跳板"这类老技巧,在 PAC 世界里该换个现代做法了。

本节要点回顾

  • NS 位划世界:安全与否随总线事务流动,非安全态碰安全内存是总线层拒绝。
  • EL3 是唯一摆渡smc 进、eret 出,非安全世界的一切安全请求走这一个门。
  • 寄存器共享是攻击面:安全固件必须消毒外来值,校验指针归属。
  • PAC 双指令:序言签名、尾声校验,失败延迟到跳转时以翻译错误收场。
  • 开关即用:PAC 一条编译开关生效,混编时保护配置要一致。

异常、中断、启动、安全四件事都在手了。下一章回到日常工具:把工具链与调试方法配齐,前面所有实验就能真正跑起来。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U