arm pointer authentication
魔高一尺,道高一丈
译自:https://lwn.net/Articles/718888/
缓冲区溢出攻击,ROP漏洞攻击是常见的攻击手段,其本质都是将攻击地址放到函数返回点(链接地址)来实现攻击。实际上许多漏洞原理都是想方设法让程序(甚至内核)信任攻击者的设置好的内存地址并让程序去访问这个地址来实现攻击。
当cpu从一个函数返回、并跳转到攻击者提供的攻击地址,cpu开始执行攻击者代码,攻击者即可以控制cpu的执行流程。
多年来针对这类问题的安全加固方式几乎都聚焦在如何让该类攻击更难发生(译者注:如何防止/检测缓冲区溢出等)。不过近期也有一些内核补丁提出了不同解决方案:利用ARM处理器本身的一些特性可检查并拒绝访问攻击者的伪造地址;其中一个就是ARMv8.3新增的"指针认证"("pointer authentication")功能。
"指针认证"特性的主要功能就是检查地址指针是否是非法构建,其原理就是将特殊加密签名认证码整合到指针中,在指针被访问时再对该指针的签名进行认证。如果攻击者无法拿到标签密钥,其通过漏洞安置的欺骗指针也无法通过认证。
当代64位处理器其虚拟地址的长度为64bit,但这64bit并没有全部被用到。例如,采用三级页表结构的ARM64 Linux系统,其虚拟地址的只用到了低位40bit,而其余的高位24bit则用特定的值来填充,也即将40地址扩展为成了64位(感兴趣的同志可以参考arm64中虚拟地址空间布局)。
这样一来其余的高位地址(或者其中的一部分)就可以挪作他用,/比如存放指针身份认证码。
指针身份认证码由三个部分计算而来:指针本身、存放于进程上下文的密钥以及current堆栈指针(或者之类的值)。其中,密钥可以让攻击者无法生成合法的身份认证,而current堆栈指针(或者类似的其他环境参数)可以防止攻击者对一些泄露的有效地址指针进行重复利用。
指针身份认证码的计算可通过新指令PAC完成,同时计算完认证码后该指令还将身份认证码集成到对应的指针中。集成了身份认证的指针其符号扩展位被占用,因而是一个无效的虚拟地址,无法直接被访问。这时候需要使用AUT指令重新将其恢复成一个正常的可用指针。该指令会对带有身份认证信息的指针进行认证和校验,如果认证信息与指针匹配,该指令会将认证信息清洗掉(译者注:确保其可以被MMU正常访问),否则会修改该指针以触发一次异常(译者注:以通知用户有安全隐患)。
因此,如果一个地址没有合法的身份认证,访问该地址可能会引发一次crash。
ARM 8.3提供了五钟指针身份认证的相关密钥:其中有两种用于代码(指令/可执行部分)指针,还有两种用于数据指针,最后一种是通用密钥。
在Mark Rutland所提交的系列补丁(ARMv8.3 pointer authentication userspace support)中只用了其中一种代码指针密钥,而其他的密钥留作将来使用。
目前该功能只支持用户态程序,暂时不支持内核中使用。当用户态创建一个进程内核会为其产生一个随机密钥并存放于该进程上下文; 接着进程就可以用它来创建和认证指针身份,但是无法直接读取到密钥的值。
用户程序可以利用此功能来进行安全加固,例如,防止块缓冲区溢出漏洞的攻击。另外一个好消息就是编译器GCC 7提供了-msign-return-address编译选项来支持指针身份认证。一旦启用该选项用户程序的函数的返回地址会插入指针身份认证;在函数返回到链接指针时就会进行指针身份认证。另外还有一些选项可用于选择将指针认证用于非叶函数(即还有子函数调用的函数)还是应用于所有编译的函数。
如果该特性能够真正起到(它宣称的那样)作用,返回地址身份认证功能足以阻止缓冲器溢出攻击。攻击者虽然可以改写函数返回的链接地址,但是由于改写的地址无法生成正确的认证信息,因而攻击者使用的欺骗地址将没有机会得到执行。虽然会由潜在的暴力攻击存在,但是除非引起目标进程多次crash否则这些攻击也无法有效的实施----因为一旦出现指针身份认证失败引发的crash,用户就注意到(译者注:系统可能存在潜在的安全漏洞被利用的情况)。
截至该文发布时该套补丁还处于第一个回合,因而在补丁真正被接收合入到主线前可能还会有一些修改和调整。另外,这个特性还有许多工作可以继续探索,包括其他几类密钥的应用场景以及内核空间返回地址的保护等等。除了对返回地址进行保护外这个特性还可以用到其他的场景,例如:对一些拥有函数指针成员的数据结构进行认证保护。
虽然指针认证无法解决所有的安全问题,但是有了它我们系统的抗攻击能力会更强。
(更多该特性相关的信息参考 Qualcomm white paper)
译者注:arm pointer authentication支持内核(arm64架构)认证功能在linux-5.7进入主线。