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 在实现时就利用的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 不匹配时,可选择触发一个同步或异步异常。下面来介绍这两种模式差别:
更多linux内核视频教程文档资料免费领取后台私信【内核】自行获取.
Linux内核源码/内存调优/文件系统/进程管理/设备驱动/网络协议栈-学习视频教程-腾讯课堂
Sync 模式是精确的错误检测工具,对性能有一定损耗,在内存访问(ldr/str)时,同步检测tag是否匹配,如果不匹配异常触发,进程会收到SIGSEGV (code SEGV_MTESERR)信号同时立即结束;
其中:siginfo.si_code = SEGV_MTESERR(SERR中的S表示synchronous),siginfo.si_addr =
当工作在sync 模式时,android 分配器还会记录分配释放的stack trace,出现问题时还能提供更详细的memory异常分类,如 user-after-free,buffer-overflow 等;
更轻量级的内存检测,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 检测分为上层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.
比如使能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 开关:
SCTLR_EL1寄存器用来打开关闭MTE,在TCF0设置MTE模式
Android 中MTE 打开的流程
上层scudo malloc分配时流程
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。
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储存读取也是硬 件实现的。
Example1:underflow
std::unique_ptr p =std::make_unique(4);
volatile int oob = p[-1];
(void)oob;
执行代码后触发tomstone
Example2: overflow
std::unique_ptr p =std::make_unique(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 异常; 整体异常
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在用户/试用用户侧选择性的打开,发 现内部难以发现的内存类问题。
留言与评论(共有 0 条评论) “” |