ARM技术论坛
直播中

张伟

7年用户 1578经验值
私信 关注
[经验]

MTE构架设计实现对内存访问的lock和key

MTE设计用来检测内存安全漏洞,增加对抗攻击的鲁棒性。在动态链接系统中,老的代码不用重新编译就可以在heap分配中用MTE。

在Stack中使用MTE需要重新编译。MTE构架设计假设栈指针是可靠的。当作stack分配中使用MTE时,重要的一点是,MTE会和其他构架功能如跳转目标鉴定Branch Target Identification (BTI)和指针验证码 Pointer Authentication Code (PAC)一起使用,来减少允许攻击者控制栈指针的gadget存在的可能性。

MTE和内存安全

MTE实现对内存访问的lock和key。

lock可以设置在内存上,在内存访问时提供key。如果kehy和lock匹配,内存访问被允许,如果不匹配,报告错误。

内存位置上每16byte物理内存被打上额外4bit metadata的Tag。这就是Tag颗粒,Tag的内存实现了lock。

指针,因此虚拟地址也修改为包含key。

为了实现key bits(4bits)而不需要增加指针的大小(非64+4bits),MTE利用了Top byte Ingore (TBI)功能。 但TBI使能的时候,虚拟地址的最高的byte在做地址转换时被忽略。这让最高的byte可以用来存储4bit tag的metadata。最高byte的4bit MTE 用来提供key。

1.png

MTE依赖lock和key不一样来检测内存安全漏洞。因为只用有限的4bit可用,它不能保证在任何时候两个内存位置有不同的tag。但是,内存分配器保证连续分配的内存tag不一样,

2.png

因此保证大多数类型的内存问题都可以被检测。

通常,MTE支持tag的随机生成和基于一个种子的伪随机tag的生成。如果一个程序执行足够多的次数, 一个内存问题至少别检测到一次的可能性基本为100%。

构架细节

MTE加入了一个新的内存类型,Normal Tagged Memory, 到arm构架中。有些异常可能静态决定访问的安全性,对这种新内存类型的读写访问进行比较 ‘存在地址寄存器中高byte的tag’ 和 ‘存在内存中的tag'。

3.png

但比较发现不匹配时,可以配置为异步报告,报告细节放在一个系统寄存器里。构架提供了一个控制来保证这个寄存器在软件进入一个更高EL时被更新。这让操作系统内核可以隔离特定线程执行的不匹配,然后根据这个信息决定如何去做。

同步异常是精确的,因此可以知道是那条指令导致的tag不匹配。相反地,异步报告是不精确的,因为它仅可能隔离特定线程的不匹配。

MTE在armv8-A构架中增加了以下3组指令

Tag操作指令,用于stack和heap的tag

IRG指令

为了保证MTE的统计学上的有效,需要一个随机tag的源。IRG是在hardware层面支持为一个寄存器插入随机tag,以便这个tag其他指令使用。

GMI

这个指令用来控制IRG指令产生随机tag不会从GMI指令设置的一组tag(excluded set of tags)中分配. 这样的目的是软件可以预留为特殊目的保留一些tag值,同时保持正常的随机tag指令功能。

LDG,STG及STZG

这些指令允许获取和设置内存tag。目的是只改变内存tag,而不修改内存数据值,STZG对数据清零。

ST2G, STZ2G

它们是STG,STZG的演进,它们操作的对象为2个memory颗粒(2 x 16 byte)。ST2G将一个tag 设置给32 bytes内存。

STGP

这个指令存储tag和数据到内存。

用于指令运算和tag stack的指令

ADDG, SUBG

他们是ADD,SUB指令的变种,目的是为地址运算。它们让tag 和地址同时分别用一个立即数进行修改。这个指令目的是在stack上创建一个对象的地址。

SUBP(S)

这个指令提供56bit的减法,如果指令带S还会设置flag,它提供了忽略高byte中的tag的指针运算。

系统用处的指令

LDGM, STGM及STZGM

这些内存块的tag操作指令,这些指令在EL0为UNDEFINE。它们的目的是让系统软件可以为初始化和序列化目的操作tag。例如,它们用来实现在交换tag过的内存到一个不是tag-aware的介质。SGTZGM的形式用来高效零初始化内存。

此外, MTE提供一组为使用tag的cache维护指令,这提供操作整个cache lien的高效方式。

可伸缩地部署MTE

Arm预计MTE会在不同的配置和不同的产品开发应用阶段部署。

精确检查的目的是提供出错地方的最多信息,不精确检查目的是为了高性能。

一个OS内核可以选择终止因为tag不匹配导致异常的进程,也可以记录这个问题,然后让这个进程继续。

在MTE使能的情况下测试产品可以发现很多潜在问题。在这个阶段,尽量检测和记录可能的问题。

我们可以配置系统为:

  • 进行精确检查
  • 累积tag不匹配数据,而不是终止进程
  • 这个配置允许收集支持通过测试和fuzzing发现最多数量的问题信息。
  • 在产品发布之后,鼓励配置MTE为:
  • 进行不精确检查
  • 在tag不匹配时终止进程

这个配置提供了性能和检测内存安全问题的平衡。

在产品发布之后,产品中可能包含加密密钥等对攻击者有高价值的信息,对这些进程可以进行精确检查,因此检查错误的地方的准确信息可以被复现,报告给开发者。

MTE也可支持动态改变MTE配置的系统。比如,一个进程因为tag检查导致不精确异常终结,下次这个进程可以使能精确检查来启动,这样的话可以为开发者收集更多的诊断信息。这个部署模式。

在硬件中部署MTE

为了在将来的arm产品中实施MTE,新的AMBA5 Coherent Hub Interface (CHI)规范正在开发,它支持MTE要求的传输和一致性要求。

4.png

在软件中部署MTE

MTE 支持几个层次的部署

Heap Tagging

在动态链接系统,有可能不修改现有的二进制文件而部署heap的tag. 仅仅OS内核和C库代码需要修改。

Arm在Linux kernel支持MTE的原型验证。以下部分需要修改:

  • 当用作地址空间管理时,有能力从用户空间地址移除tag
  • 让虚拟内存空间的clear_page, copy_page功能是tag-aware的。
  • 增加处理tag不匹配的错误处理, 看起来像处理翻译错误的SIGSEGV处理
  • 改变那些可能暴露用户空间进程的内存映射,以便使用Normal Tagged Memory
  • 增加可以检测是否有MTE的功能,和系统寄存器配置以便使能MTE

Arm正在开发支持upstream MTE的Linux内核支持。

在C库中,arm修改了以下内存相关函数

  • Malloc
  • Free
  • Calloc
  • Realloc

除此之外,也修改了memory copy和字符串相关函数,阻止它们过多读源buffer。

Stack tagging

对运行时态栈进行tag,需要编译器支持和内核支持。二进制必须重新编译。可以支持很多不同的stack tagging策略。

我们的合作伙伴选择使用IRG指令为函数进入时分配的新栈帧生成随机tag的策略。编译器然后使用ADDG和SUBG指令为函数内的每个栈片创建tag过的地址,这个tag是初始随机tag的offset。栈分配可能使用合适的tag存储指令来块初始化,但编译器不需要对任何内存片初始化,函数体代码会在使用前对其初始化。

这个策略保证MTE的统计学特性对每个函数的调用有效,也保证栈上相邻对象有不同的tag,因此可以检测连续的overflow和underflow.

保护栈上相邻对象需要增加这些对象对齐到tag颗粒,也就是16 byte。在有些程序里,MTE导致stack的大小因为对齐要求变大。我们benchmarking显示通常增加的比较少。

为了提高性能,使用SP+立即数地址内存访问不使用MTE检查,这是因为编译器可以静态地证明它们是正确的或是在编译时产生诊断信息。

为MTE优化

MTE设计为了正确的代码不需要源代码修改。然而,MTE会导致overhead,因为tag必须从内存获取和存回。 这个overhead内存分配的大小和生命周期有关,tag和data可以一起或单独操作。Overhead可以通过以下方式减少:

同时写tag和初始化内存

在很多情形下,内存必须初始化为0并且设置tag。比如,在OS内核中将一个内存页交给用户空间前进行清除操作。Arm Linux的原型使用STZGM来实现。

避免过分分配为从不写的内存地址空间

在有些情形下,软件分配远超过它需要的地址空间,但仅会在释放之前碰其中的一部分。使用MTE,这个代价很高因为尽管数据不会写到内存,但tag可能会。

避免过分的释放和重新分配

避免过分的释放和重新分配通常是好的实践,不过MTE是不是使用。但是,因为使用MTE的释放和重新分配固定代价,现有的性能问题会放大。

避免在stack上分配大块固定大小内存

在stack上分配大块,固定大小的内存趋向于使用不足,一个例子,比如PATH_MAX的固定大小的buffer通常包含相对短的路径字符串。避免这样的分配,通过减少未使用内存的tag页写的数量,减少了保护栈的代价。

原作者:修志龙_ZenonXiu

更多回帖

发帖
×
20
完善资料,
赚取积分