一、MTE 介绍(Memory_Tagging_Extension)
MTE 是ARM新架构(ARM V8.5 引入)的一个特性,它通过给分配的内存打标记(tag),追踪最常见的非法内存操作。如果密钥的值和锁的值一样,表示访问成功,否则会报告一个错误。
通常内存安全相关的bug,特别是在Android codebase 的native 代码层是常见的异常类型。按照google 的异常统计,超过50%的安全漏洞是内存引起的(如下图所示):
为了解决和发现内存bug,Android 引入了ASan/HWASan, KASAN, GWP-ASan,KFENCE和MTE内存检测工具,其中MTE是Android 12开始加入支持。
MTE 原理和HWASan类似,下面对比分析下:
1、检测部分HWASan通过编译方式,在内存访问前插入检测代码;tag(key)生成通过软件随机方式生成;tag(lock)存储在shadow memory中(shadow 内存通常提前设定);
2、MTE 检测和tag生成存储有了硬件层面的支持,MTE通过指令生成tag(key);检测也是通过指令完成;tag (lock)存储的部分存放在物理内存特定区域(无需软件参与显示分配或者设置);
重点差异对比表
以下是三个工具的开销对比,可以直观感受到MTE的提升。
二、MTE 原理
MTE 在实现时就利用的Armv8-A 的TBI(Top Byte Ignore)特性,使用指针的高 4 bits 存储 tag(即 key),使用专用的内存(tagging memory)存储 tag(即 lock)。下图是MTE 工作的原理图
MTE 引入的新指令操作标记内存:
大部分指令完成带tag的加减等数据操作,核心指令IRG,STG ,LDG完成关键的tag生成,存储和读取。
打开MTE时,正确的内存访问需要指针的 key 值与该内存的 lock 值保持一致。当 lock 和 key 不匹配时,可选择触发一个同步或异步异常。下面来介绍这两种模式差别:
1、同步模式(Sync)
Sync 模式是精确的错误检测工具,对性能有一定损耗,在内存访问(ldr/str)时,同步检测tag是否匹配,如果不匹配异常触发,进程会收到SIGSEGV (code SEGV_MTESERR)信号同时立即结束;
其中:siginfo.si_code = SEGV_MTESERR(SERR中的S表示synchronous),siginfo.si_addr = <fault-address>)
当工作在sync 模式时,android 分配器还会记录分配释放的stack trace,出现问题时还能提供更详细的memory异常分类,如 user-after-free,buffer-overflow 等;
2、异步模式(ASync)
更轻量级的内存检测,Android用来发现内存踩踏bug;在内存访问时,异步检测tag是否匹配,如果不匹配则更新TFSR_EL1寄存器中的TF0 bit。当下一次用户/内核切换时,系统会去检测TFSR_EL1寄存器,然后产生SIGSEGV信号(code = SEGV_MTEAERR);此时的 siginfo.si_code = SEGV_MTEAERR (AERR中的A表示asynchronous),siginfo.si_addr = 0 ,表明系统并不知道是哪一条具体的指令导致的问题。
三、MTE 在Android S平台使用方法
MTE 检测分为上层native 和kernel 部分,先来看上层native 使能部分
1、使能所有的native code,通过环境变量控制
2、增加指定路径的project (BoardConfig.mk中)
PRODUCT_MEMTAG_HEAP_SYNC_INCLUDE_PATHSPRODUCT_MEMTAG_HEAP_ASYNC_INCLUDE_PATHS
3、通过Android bp setting打开
4、在版本中还可以通过属性控制:
arm64.memtag.process. <basename>= off| async | sync
比如使能system_server进程mte: arm64.memtag.process.system_server
5、NDK API 控制使能方法
mallopt (NONE or TBI or ASYNC or SYNC)
6、注意默认版本中MTE是关闭的,需要检查对应的rc文件确保MTE打开
[device/XXX/init.target.rc]
on early-init
# export MEMTAG_OPTIONS off
修改成sync 或者async
kernel 侧MTE 使能
1、确认平台本身MTE 已经支持
2、MTE kernel 相关配置
CONFIG_ARM64_MTE // using MTE in the userspace
CONFIG_KASAN and CONFIG_KASAN_HW_TAGS //using MTE in the kernel
3、通过cmdline 控kasan 开关:
四、MTE在KERNEL 和Android S中的实现
1.MTE打开方法
SCTLR_EL1寄存器用来打开关闭MTE,在TCF0设置MTE模式
Android 中MTE 打开的流程
上层scudo malloc分配时流程
2.MTE工作流程
Tag 检测异常触发流程,data abort时,arm 通过ESR_EL1(Exception SyndromeRegister) 寄存
器ISS部分(低22位)来记录:
DFSC, bits[5:0]
Data Fault Status Code.
0b010001 WhenFEAT_MTE2 is implemented Synchronous TagCheck Fault.
DFSC为17时表示触发了MTE tag 检查不匹配;
SYNC模式工作流程
ASYNC模式工作流程
从sync 和async 实现部分看,sync 在触发异常时立刻发送SEGV_MTESERR给进程,
而async在进程系统调用或其他user/kernel切换时,主动检查mte 标记位然后发送
SEGV_MTEAERR。
3. MTE检测框图
MTE在tag mismatch时触发异常,消息回传用户程序后,抓取当前调用栈和访问内存及
附近的tag,通过指针tag和内存tag来判断是哪种内存异常。
MTE Kernel 中的实现如下,关键宏定义:上面的定义可以明显看到MTE Tag size为 4 ,标记16个字节,tag存放在指针偏移56位;
当前kernel中TAG的值范围0xF0~0xFD, 0xFE用来标记未分配或已经释放的内存。上图是kernel 触发tag mismatch的原理图,需要注意的是MTE 的tag lock储存读取也是硬
件实现的。
五、MTE 检测案例
1.userspace踩踏例子
Example1:underflow
std::unique_ptr<int[]> p =std::make_unique<int[]>(4);
volatile int oob = p[-1];
(void)oob;
执行代码后触发tomstone
Example2: overflow
std::unique_ptr<int[]> p =std::make_unique<int[]>(4);
volatile int oob = p[5];
(void)oob;
执行代码后触发tomstone
Example3: useafter free
int * p = new int[16];
delete[] p;
p[3] = 9;
上面的例子中可以看到MTE 能准确的检测到常见的uaf, unferflow, overflow 异常;
整体异常
2. Kernel hwkasan例子
Example : kmalloc overflow
测试代码sample:
ptr = kmalloc(234, GFP_KERNEL);
ptr[256] = 0;
从上面的log看kernel mte 检测过程:
分配的指针:fdffff8878a8f500
越界的地方:fdffff8878a8f601
Pointer tag : [fd], memory tag: [f4].
ptr指针为0xfdffff8878a8f500,标记的可访问内存是0xffffff8878a8f500 ~ 0xffffff8878a8f5EA, mte是
标记是16字节对齐的,所以实际是标记连续234/16 =14.6= 15个(向上取整)。Ptr
指针只能访问tag为fd标记的区域,实际ptr 访问到了tag为f4标记的内存区域,通过
tag计算我们也能推导出内存访问越界点;
Under flow 流程类似overflow,tag 打印会在ptr能访问内存的前面;
User after free 的话mem的tag会变成fe(用来表示已经释放或无法访问的内存);
六、总结
当前MTE 在android中需要绑定scudo 分配器使用,相信随着更多平台支撑mte后,传统的
分配器也会引入mte。
MTE 通过4 bit Tag Size 来做key lock 匹配,存在连续两个内存分配相同的tag key情况,
也就是1/16 概率出现无法检测的情况(kernel 中实际只有14个tag可用,实际概率更高)
;如果能保证相邻内存tag 不同的话,miss的概率会大大降低。
MTE 无法检测 16 byte内的内存越界访问;分配大小非16字节对齐,向后踩踏<16字节
对齐的踩踏无法被MTE检测到,比如:
ptr = kmalloc(12, GFP_KERNEL);
ptr[12] = 0;
….
ptr[15] = 0;
ptr[16] = 0; //这里才能触发检测
MTE不依赖编译,低性能损耗,可以考虑将MTE在用户/试用用户侧选择性的打开,发
现内部难以发现的内存类问题。
参考:
https://www.kernel.org/doc/html/latest/arm64/memory-tagging-exte
nsion.html
https://community.arm.com/arm-community-blogs/b/architectures-an
d-processors-blog/posts/enhanced-security-through-mte?_ga=2.4176
8226.1458341132.1636618591-1030760285.1629468403
https://juejin.cn/post/7013595058125406238
https://www.reexpound.com/2021/05/21/%E6%B7%B1%E5%85%A5%E7%90%86
%E8%A7%A3arm%E5%86%85%E5%AD%98%EF%BC%9A%E4%B8%80-memory-tagging-
extension-mte/
https://aijishu.com/a/1060000000006711
https://developer.arm.com/-/media/Arm%20Developer%20Community/PD
F/Arm_Memory_Tagging_Extension_Whitepaper.pdf
长按关注
内核工匠微信
Linux 内核黑科技 | 技术文章 | 精选教程