Memory Tagging
1、摘要
1988年的Internet蠕虫占据了初始的网络的十分之一,并严重降低了其余部分的速度[1]。30多年后,用类C语言编写的代码中最重要的两类安全漏洞是内存安全。根据2019年BlueHat演示,Microsof产品解决的所有安全问题中有70%是由内存安全漏洞引起的[2]。谷歌针对Android报道了类似的说法,其中超过75%的脆弱性是由内存安全性违规问题引起的[3]。尽管在更新的语言中可能无法实现许多这样的功能,但是用C和C ++编写的使用中代码的基础却是非常多的。仅Debian Linux就包含了超过十亿行[4]。
本文介绍了Armv8.5-A内存标记扩展(MTE)。MTE旨在提高以不安全语言编写的代码的内存安全性,而无需更改源,在某些情况下,也不需要重新编译。容易部署的针对内存安全性违规的检测器和减震器可能会阻止一大类安全漏洞被利用。
2、前言
违反内存安全性可分为两大类:空间安全和时间安全。攻击的第一阶段中可利用的违规构成旨在投递有效恶意载荷或与其他类型的漏洞链接以控制系统或泄漏特权信息。
当在其真实范围之外访问对象时,会违反空间安全性。例如,当堆栈上的缓冲区溢出时。可以利用它来覆盖函数的返回地址,这可以构成多种攻击的基础。
当超出范围使用对对象的引用时,通常是在释放或重新分配该对象的内存之后,就会违反时间安全性。例如,当包含某种函数指针的类型被恶意数据覆盖时,这也可能构成多种攻击类型的基础。
MTE提供了一种检测两种主要类别的内存安全违规的机制。MTE通过提高测试和模糊测试的效率来帮助在部署之前检测潜在的漏洞。MTE还有助于在部署后大规模检测漏洞。
通过精心设计的软件,可以始终检测到顺序安全违例,即在安全范围之前或之后立即访问内存。可以概率性地检测到对地址空间中任意位置的“狂野”冲突。
在部署之前定位和修复漏洞可减少部署代码的攻击面。在部署之后大规模检测漏洞可以在漏洞被广泛利用之前以反应方式修复漏洞。对网络犯罪经济学的研究[5]表明它对规模敏感。及时检测和反应性修补可能在大规模破坏网络犯罪方面非常有效。
3、威胁模型
MTE旨在提供强大的抵御能力,以抵御试图处理攻击者提供的恶意数据的代码。它不解决算法漏洞或恶意软件。
MTE旨在检测内存安全违规并提高抵御违规启用攻击的鲁棒性。在动态链接的系统中,旧版代码可从MTE中受益,无需重新编译即可进行堆分配。
将MTE应用于堆栈需要重新编译。MTE体系结构是在假定堆栈指针可信任的前提下设计的。因此,在为堆栈分配部署MTE时,将MTE的使用与其他功能(例如分支目标标识(BTI)和指针验证码(PAC))相结合很重要,以减少存在允许攻击者控制的gadget的可能性堆栈指针。
4、MTE的内存安全性
Arm Memory Tagging Extension实现了对内存的锁定和密钥访问。可以在存储器上设置锁,并在访问存储器时设置密钥。如果密钥与锁匹配,则允许访问。如果不匹配,则报告错误。
通过将四位元数据添加到物理内存的每个16字节来标记内存位置。这是标签颗粒。标记内存可实现锁定。
指针和虚拟地址都被修改为包含密钥。
为了实现密钥位而不需要更大的指针,MTE使用Armv8-A体系结构的最高字节忽略(TBI)功能。启用TBI后,将虚拟地址的高位字节用作地址转换的输入时,将忽略该地址的高位字节。这允许高位字节存储元数据。在MTE中,最高字节的四位用于提供密钥。
MTE依靠锁和钥匙不同来检测内存安全违规。由于可用的标记位数量有限,因此不能保证两个内存分配对于任何特定执行都将具有不同的标记。但是,内存分配器可以确保顺序分配的标签始终不同,从而确保始终检测到最常见的安全违规类型。更一般而言,MTE支持基于种子的随机标记生成和伪随机标记生成。如果有足够数量的程序执行,则其中至少一个执行程序会检测到违规的可能性接近100%。
5、体系结构细节
MTE在Arm体系结构中添加了一种新的内存类型,即“普通标记内存”。除了在某些可以静态确定访问安全性的地方外,将这种地址加载并存储到此新的内存类型中,然后执行访问操作,将地址寄存器高位字节中存在的标记与存储在内存中的标记进行比较。
地址中的标签和内存中的标签之间的不匹配可以配置为导致同步异常或异步报告。
当不匹配配置为异步报告时,详细信息会累积在系统寄存器中。提供控件以确保在进入以更高异常级别运行的软件时更新此寄存器。这使操作系统内核能够将不匹配情况隔离到特定的执行线程,并根据此信息进行决策。
同步异常非常精确,因为可以准确确定导致标签不匹配的加载或存储指令。相反,异步报告是不精确的,因为只能将不匹配情况隔离到特定的执行线程。
MTE向Armv8-A架构添加了以下概述的指令,并分为三个不同的类别[6]:
用于栈和堆标记的标记操作指令:
IRG
为了使MTE的统计依据有效,需要随机标签的来源。IRG被定义为以硬件提供此功能,并将此类标签插入寄存器以供其他指令使用。
GMI
该指令用于处理与IRG指令一起使用的排除标记集。这适用于软件将特殊标签值用于特殊用途,同时保留随机标签行为以进行常规分配的情况。
LDG, STG, and STZG
这些指令允许在内存中获取或设置标签。它们旨在在不修改数据或将数据清零的情况下更改内存中的标签。
ST2G and STZ2G
这些是STG和STZG的更密集的替代方案,当分配大小允许使用它们时,它们可对两个内存颗粒进行操作。
STGP
该指令将标签和数据都存储到内存中
用于指针算术和堆栈标记的指令:
ADDG and SUBG
这些是ADD和SUB指令的变体,旨在用于地址运算。它们允许用立即数分别修改标签和地址。这些指令用于在堆栈上创建对象的地址。
SUBP(S)
该指令提供了一个带有可选标志设置的56位减法,这对于忽略顶部字节中的标签的指针算术而言是必需的。
用于操作系统的指令:
LDGM, STGM, and STZGM
这些是批量标签操作指令,在EL0处未定义。这些旨在供系统软件操纵标签以进行初始化和序列化。例如,它们可用于实现将标记的内存交换到不支持标记的介质。调零形式可用于有效初始化内存。此外,MTE提供了一组旨在与标签一起使用的缓存维护操作。这些提供了在整个缓存行上运行的有效机制。
6、大规模部署MT
Arm预计MTE将在产品开发和部署的各个阶段以不同的配置进行部署。
精确检查旨在提供有关故障位置的最多信息。不精确的检查旨在提高性能。
OS内核可以选择是终止由于标签不匹配而导致异常的进程,还是记录事件的发生并允许进程继续进行。
测试启用了MTE的产品可能会发现许多潜在的问题。在此阶段,应该检测并记录有关尽可能多问题的尽可能多的信息。
该系统不需要受到攻击者的保护。将系统配置为:
执行精确的检查。
累积有关标签不匹配的数据,而不是终止过程。
此配置允许收集最多的信息,以支持通过定向测试和模糊测试找到最大数量的缺陷。
发布产品后,将MTE配置为:
执行不精确的检查。
在标签不匹配时终止进程。
此配置可在性能和检测可能违反该软件的漏洞的内存安全冲突之间取得平衡。
发布后,可能需要配置对黑客具有高价值的流程(例如加密密钥库)以执行精确的检查,以便可以通过错误报告和遥测系统将有关检查失败的位置的准确信息传递回给开发人员。
系统自适应地更改其MTE配置可能也很合适。例如,如果由于不正确的检查而导致运行不正确的进程因标记检查失败而终止,则下次启动该进程时,可以从启用精确检查开始,以为其开发人员收集更好的诊断信息。此部署模型将不精确检查的性能优势与精确检查的优势相融合,以提供更好的质量反馈。
7、在硬件中实现MTE
为了支持将来实现MTE的Arm产品,正在开发新版本的AMBA 5一致性集线器接口(CHI)规范,该规范支持MTE的传输和一致性要求[7]。
8、在软件中实现MTE
MTE旨在支持多个级别的部署。
堆标记
在动态链接的系统中,可以在不更改现有二进制文件的情况下部署标记堆。仅OS内核和C库代码需要更改。通过添加对Linux内核的支持来构建MTE原型。以下领域需要更改:
当它们用于地址空间管理时,能够从用户空间指针中删除标签。
图2显示了一个基于MTE的示例系统。7使虚拟内存系统中的clear_page和copy_page函数能够识别标签。
增加了对处理因标签不匹配而导致的错误的支持,类似于将转换错误作为SIGSEGV处理的方式。
将可能暴露给用户空间进程的内存映射转换为使用“正常标记”内存。
添加扩展检测和系统寄存器配置以启用扩展
Arm正在为上游提供Linux Kernel支持。
在C库中,Arm修改了以下与内存相关的功能:
malloc
free
calloc
realloc
此外,修改了内存复制和与字符串相关的功能,以防止它们过度读取源缓冲区。
栈标记
标记在运行时栈上分配的内存需要编译器支持和内核支持。二进制文件必须重新编译。栈标记的许多不同策略都是可能的。
我们的合作伙伴已制定了一种策略原型,该策略在分配新栈帧的函数输入期间使用IRG指令选择随机标签。然后,编译器使用ADDG和SUBG指令为函数中的每个栈插槽创建标记的地址,其中标记从初始随机标记偏移。可以使用适当的标签存储指令对栈分配进行批量初始化,但编译器无需初始化任何插槽,该插槽在被函数的主体代码使用之前将被证明已初始化。
此策略可确保MTE的统计属性对于每次调用函数均有效,并确保堆栈上的相邻对象具有不同的标记,因此顺序的上溢和下溢将导致检测。
保护堆栈上的相邻对象需要将这些对象与Tag Granule的对齐方式增加到16个字节。在某些程序中,由于这种效应,MTE导致堆栈使用量增加。我们的基准测试表明,增长通常很小。
为了提高性能,在MTE下不检查使用栈指针寻址模式的立即偏移量的内存访问。这是因为编译器可以在编译时静态证明它们正确或发出诊断。
9、MTE优化措施
MTE的设计使其不需要修改源代码就可以纠正代码。但是,由于必须从存储系统中获取标签并将其存储到内存系统,因此MTE必然会导致开销。此开销与内存分配的大小和生存期以及标签和数据是一起操作还是单独操作有关。可以通过以下方式最小化开销:
同时写入标签和初始化内存
在许多情况下,必须将内存初始化为零并设置标签。例如,在将OS内核中的页面移交给用户空间之前将其清除。Arm的基于Linux的原型为此目的使用了STZGM指令。
避免过度分配从未写入数据的地址空间
在某些情况下,软件分配的地址空间远远超出其需要,并且在取消分配之前仅使用其中的一小部分。使用MTE,开销会更大,因为即使数据永远不会写入内存,也可能需要标记。
避免过多的取消分配和重新分配
通常,避免过度分配和重新分配是一种好习惯,无论是否部署MTE。但是,由于使用MTE会增加分配和取消分配的固定成本,因此可能会放大现有的性能问题。
避免在栈上进行较大的固定大小分配
栈上的大型,固定大小的分配往往会被使用不足,例如,固定大小的缓冲区(例如PATH_MAX)通常包含相对较短的字符串。避免这样的分配,通过减少必须写入的未使用存储标签的数量,减少了保护栈的开销。
参考资料
[1] F. B. I. [Online]. Available: https://www.fbi.gov/news/stories/morris worm-30-years-since-first-major-attack-on-internet-110218[2] M. Miller, “Bluehat Abstracts,” [Online]. Available: https://msrnd-cdn-stor.azureedge.net/bluehat/bluehatil/2019/assets/doc/Trends%2C%20Challenges%2C%20and%20Strategic%20Shifts%20in%20the%20Software%20Vulnerability%20Mitigation%20Landscape.pdf[3] “Google Queue Hardening,” [Online]. Available: https://security.googleblog.com/2019/05/queue-hardening-enhancements.html[4] Debian, “Stretch Statistics,” [Online]. Available: https://sources.debian.org/stats/stretch[5] “ACM,” [Online]. Available: https://dl.acm.org/citation.cfm?id=2654847[6] “AArch64 Instructions,” [Online]. Available: https://developer.arm.com/docs/ddi0596/latest/base-instructions-alphabetic-order[7] “Architecture Reference Manual,” [Online]. Available: https://developer.arm.com/docs/ddi0487/latest