内存标签扩展(Memory Tagging Extension,MTE)是Armv8.5-A中添加的新功能。
目前对计算机系统的攻击,大部分是对内存的攻击。内存安全问题又可以分为两类:空间安全(spatial safety)和时间安全(temporal safety)。
当试图访问安全区域以外的数据,即违反了空间安全性,比如缓冲区溢出(Buffer Overflow)攻击。缓冲区溢出是指在存在缓存溢出安全漏洞的计算机中,攻击者可以用超出常规长度的字符数来填满一个域,通常是内存区地址。缓存区溢出存在于各种电脑程序中,特别是广泛存在于用C、C++等这些本身不提供内存越界检测功能的语言编写的程序中,例如Debian中就存在5亿行C/C++代码。
当试图访问已超出正常时间范围的内存资源时,即违反了时间安全性,比如释放后再使用(Use After Free)攻击。顾名思义,就是当一个内存块被释放之后再次被访问。攻击程序可以先申请一块内存,然后释放内存,但是不清空该内存指针,等待一段时间后再次通过指针对内存进行访问。如果恰好在访问操作之前这块内存被分配给了其它的程序,那么攻击程序可以通过内存对此程序发起攻击。
内存攻击不只以上提到的两种。我不是安全专家,就不在这里啰嗦了。
可以通过软件机制来检测内存访问违例,但代价是运行效率低。
MTE提供一种硬件机制来检测这两类内存违例的情况,这种机制类似于锁和钥匙的关系。在分配内存的时候加上一把锁,访问的时候需要提供一把钥匙,如果钥匙和锁不匹配,即阻止访问,并报告错误。具体来说,通过向物理内存的每个16字节添加四-bit元数据(Metadata)来做内存标记;指针和虚拟地址被修改为包含钥匙。16字节被定义为一个“标签颗粒(Tag Granule)”。为了在不需要较大指针的情况下实现钥匙,Armv8-A架构中使用“顶部字节忽略(Top Byte Ignore,TBI)”功能。启用TBI后,在做地址转换时,虚拟地址的顶部字节会被忽略。这样就可以使用顶部字节来存储元数据,实现内存标签的钥匙。当前,仅使用顶部字节的4-bit。
来看一个例子,下图上半部分,显示的是缓冲区溢出情况。通过new()函数分配一个16-byte的内存给ptr指针。当程序通过ptr指针来访问随后的地址空间,会产生内存违例,这是因为后面的内存的锁与ptr的钥匙不相符。下图下半部分,显示的是UAF情况。当内存被再次分配时,产生了一个新锁,如果攻击程序用旧的指针去访问,钥匙和锁不相符。
MTE支持标签的随机产生,或基于种子的伪随机产生。如果一个程序的执行次数足够,则至少其中一个程序检测到违规的概率趋于100%。
或许你已经注意到了一个细节,那就是4-bit的元数据最多只能标记16种不同的锁。也就是说还有1/16的可能性,错误的钥匙适配到了锁。为了避免这类错误,需要软件通过其它方式增加标签的不同可能性。
MTE增加了一种新的内存类型,普通标签内存(Normal Tagged Memory)。
地址中的标签和内存中的标签之间的不匹配可以配置为导致同步异常(synchronous exception)或异步报告(asynchronous report)。
同步异常是精确的,因为可以精确地确定哪个加载或存储指令导致了标记不匹配。相反,异步报告是不精确的,因为它只能将不匹配隔离到特定的执行线程。
MTE为Armv8-A体系结构添加了三类指令:
1.适用于堆栈(stack)和堆(heap)标记的标签操作指令
- IRG(insert random tag),此指令在第一个源寄存器的地址中插入一个随机逻辑地址标记,并将结果写入目标寄存器。IRG在硬件层面支持为一个寄存器中的地址插入随机tag,这个tag随后可以为其它指令使用。
- GMI(tag mask insert),将第一源寄存器中的标记插入第二源寄存器中指定的排除集,将新的排除集写入目标寄存器。此指令用于操作与IRG指令一起使用的排除标记集,适用于软件为特殊目的使用特定标记值,同时为正常分配保留随机标记行为的情况。
- LDG(load allocation tag),此指令从内存地址加载分配标记(allocation tag),从分配标记生成逻辑地址标记,并将其合并到目标寄存器中。
- STG(store allocation tag),此指令存储分配标记到内存
- STZG(store allocation tag, zeroing),此指令将分配标记存储到内存,将相关数据位置归零
- ST2G,此指令将分配标记存储到内存的两个标记颗粒
- STZ2G,此指令将分配标记存储到内存的两个标记颗粒,将相关数据位置归零
- STGP(store allocation tag and pair of registers),此指令从两个寄存器向内存存储一个分配标记和两个64位双字
2. 用于指针运算和堆栈标记的指令
- ADDG(add with tag),此指令将由标记颗粒缩放的立即数加到源寄存器中的地址,使用立即值修改地址的逻辑地址标记,并将结果写入目标寄存器。
- SUBG(subtract with tag),此指令从源寄存器中的地址减去由标记颗粒缩放的立即数,使用立即数修改地址的逻辑地址标记,并将结果写入目标寄存器。
- SUBP(subtract pointer),此指令从第一源寄存器中保存的56位地址减去第二源寄存器中保留的56位的地址,符号扩展结果到64位,并将结果写入目标寄存器。
3. 用于系统的指令
- LDGM(load tag multiple),此指令读取N个分配标记的自然对齐块
- STGM(store tag multiple),此指令存储N个分配标记的自然对齐块
- STZGM(store tag and zero multiple),此指令存储N个分配标记的自然对齐块,并将零存储到相关数据位置
为了在后续产品种加入MTE,ARM将开发新版本的CHI协议,以支持MTE的传输和一致性要求。
为了支持MTE,还需要对软件进行部署。ARM正在进行相关的工作。
MTE无需更改程序源代码。然而,MTE必然会导致开销,因为标签必须从内存系统中提取并存储到内存系统中。这种开销与内存分配的大小和生命周期以及标记和数据是一起操作还是单独操作有关。开销可以通过以下方式最小化:
- 同时写入标签和初始化内存
- 避免过度分配从未写入数据的地址空间
- 避免过度的释放和重新分配
- 避免在堆栈上分配大块固定大小内存
今天就到这里吧,是不是没用的知识又增加了一些呢?
作者: 老秦谈芯
文章来源:老秦谈芯
推荐阅读
更多IC设计技术干货请关注IC设计技术专栏。
迎添加极术小姐姐微信(id:aijishu20)加入技术交流群,请备注研究方向