MTE – 栈内存检测原理

MTE – 栈内存检测原理MTE 属于 ARMv8 5 指令集的拓展功能 即 memorytagext 要使能 MTE 功能 需要有支持 MTE 的硬件 kernel 以及编译器和 libc 定制 malloc 等堆内存分配方法

大家好,欢迎来到IT知识分享网。

目录

1.概述

2.栈内存检测原理

3.MTE 指令

4.举例说明

4.1 未初始化的局部变量作为函数参数

4.2 初始化为0的局部变量作为函数参数

4.3 初始化为非0的局部变量作为函数参数

4.4 多个局部变量作为函数参数

4.5 基于SP寄存器(或偏移)的存取指令


本文基于 google 文档 https://github.com/google/sanitizers/wiki/Stack-instrumentation-with-ARM-Memory-Tagging-Extension-(MTE) 分析~

1.概述

MTE 属于 ARMv8.5 指令集的拓展功能~ 即 memory tag extension~

要使能MTE功能,需要有支持 MTE 的硬件,kernel,以及编译器和libc(定制malloc等堆内存分配方法)~

其中,支持MTE的硬件和kernel是必要条件~

支持MTE的编译器是实现 stack 检测的必要条件~

支持MTE的libc是实现 heap 检测的必要条件~

2.栈内存检测原理

首先是tag(标签)的概念~

MTE 有两类 tag:

A. 分配标签(allocate tag也可以说是memory tag),每16个字节内存对应一个4位的mem tag

B. 地址标签(即address tag),地址的高4位存放 address tag

这里说的地址可以理解成存放内存地址的寄存器,比如X寄存器或SP寄存器~~~

内存访问指令(基于SP的内存访问指令除外)会比较address和memory tag,不匹配时生成异常~~

编译器(LLVM)可以应用MTE 检测 stack 内存安全,这是通过将 MTE 指令插入到生成的代码中来实现的!

由于对齐要求,所有 stack 变量的大小都会增长到最接近的 16 字节的倍数,并且也按16对齐,因为所有MTE指令都要求其地址操作数按 16 字节对齐~

LLVM 启用 MTE 的方法是,在编译时添加编译选项 -march=armv8+memtag -fsanitize=memtag~

(目前 LLVM 对 MTE 的支持工作还在进行中)

3.MTE 指令

主要的 MTE 指令有

指令名

使用方法

说明

IRG

IRG Xd, Xn

将Xn复制到Xd, 并且生成一个随机tag设置给Xd(Xd,Xn表示arm的X寄存器即地址寄存器)~

此时,Xd 的 tag 即为 address tag!

STG

STG Xd, [Xn]

将 Xd 的 tag 设置给 [Xn, Xn + 16) 这块内存~

此时,[Xn, Xn + 16)这块内存的 tag(mem tag)就等于 Xd 的 tag(address tag)了!

STZG

STZG Xd, [Xn]

STG 的变体。

在 STG 的基础上,将 [Xn, Xn + 16)这块内存清0(内容设置为0)

STGP

STGP Xt, Xt2, [Xn]

STG 的变体。

将 Xt,Xt2 这两个寄存器的值写到 [Xn, Xn + 16) 这块内存中(arm64 的 X寄存器size是8字节,但是 MTE 要求 16字节对齐,所以这里会用两个X寄存器来初始化变量所在的内存)~

并且将 Xn 的 tag 设置给 [Xn, Xn + 16) 这块内存 ~

ADDG

ADDG Xd, Xn, #imm1, #imm2

ADD 的变体。

将 Xn 复制到 Xd~

将立即数 imm1 加到 Xd 的地址部分~

将立即数 imm2 加到 Xd 的tag 部分~

ST2G

ST2G Xd, [Xn]

STG 的变体。

将 Xd 的 tag 一次性设置到 2 个内存粒度上(MTE 的内存粒度为16个字节,要求16字节对齐)~

即将 Xd 的 tag 设置给 [Xn, Xn + 32) 这块内存~

4.举例说明

4.1 未初始化的局部变量作为函数参数

{ int x; use(&x); }

irg x0, sp // 将sp即栈指针设置给x0(sp一直指向栈顶,此时栈顶的位置即变量x所在的位置),同时生成随机tag,设置给x0 的高4位(即address tag) stg x0, [x0] // 将x0 的tag 设置给 x0 指向的16字节内存(即变量x所在的位置),此时变量 x 的mem tag 与寄存器 x0 的address tag 相同 bl use // 函数跳转(在use函数中通过 x0 来操作变量 x,由于 tag 相同所以可以操作,如果操作超出16字节则会报错) stg sp, [sp] // 将 sp 的 tag 设置给 sp 指向的 16 字节内存(即此时的栈顶位置,也就是变量 x)

4.2 初始化为0的局部变量作为函数参数

{ int x = 0; use(&x); }

irg x0, sp // 同上 stzg x0, [x0] // 同上,同时将变量x清0 bl use // 同上 stg sp, [sp] // 同上

4.3 初始化为非0的局部变量作为函数参数

{ int x = 42; use(&x); }

irg x0, sp // 同上 mov w8, #42 // 将x8寄存器的低32位值设置为42(w表示对应x寄存器的低32位) stgp x8, xzr, [x0] // 将x8的值写到x0(指向的16字节)的低8字节,将xzr(0寄存器,表示全0)的值即全0写到x0的高8字节 bl use // 同上 stg sp, [sp] // 同上

4.4 多个局部变量作为函数参数

{
int x, y; use(&x, &y); use(&x, &y); }

irg x19, sp // 先将sp(指向栈顶,假设为变量x)设置给x19(临时使用),并生成和设置随机 tag 给 x19 addg x1, x19, #16, #1 // 将x19设置给x1,x1的值加16,x1的tag加1~ x1指向变量y mov x0, x19 // 将x19设置给x0,x0指向变量x~ 这样x0和x1就有不一样的tag了(x0与x19的tag相同) stg x19, [x19] // 设置变量x的 mem tag stg x1, [x1] // 设置变量y的 mem tag bl use // 同上 addg x1, x19, #16, #1 // 第二次调用函数前,重新将x19设置给x1,x1的值加16,x1的tag加1~ x1指向变量y mov x0, x19 bl use st2g sp, [sp] // 将栈顶的32个字节(即变量x和y)的mem tag设置为与 sp的address tag 相同

4.5 基于SP寄存器(或偏移)的存取指令

这里要特别说明,基于SP寄存器和立即偏移量的加载和存储指令都不检查 tag。 这允许编译器在上述代码片段中避免为x的标签地址分配一个被调用保存的寄存器,并在调用后加载x的值,就像它没有被标记一样。

基于SP寄存器和立即偏移量的加载和存储行为一般被认为是安全的,因为它们只出现在访问当前函数堆栈上的局部变量的代码中,它们的正确性可以静态地进行验证~~

比如下面的代码片断,指令 ldr w0, [sp]通过SP寄存器读取栈顶位置的 x 变量值,并写到 w0 寄存器~

{
int x; use(&x); return x; }

irg x0, sp stg x0, [x0] bl use ldr w0, [sp] // 将栈顶的32位数据传入寄存器w0(即x0的低32位) stg sp, [sp]

这段代码想说明的是,按照一般的 MTE tag 检查规则来看, ldr w0, [sp]这条指令是会报错的,因为变量 x 当前的 mem tag 与 sp 的 address tag 不相等~

但是实际上并不会报错,因为基于 SP 的内存访问并不会触发 MTE 的 tag 检查~

MTE – 堆内存检测原理:

MTE – 堆内存检测原理-CSDN博客

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/128961.html

(0)
上一篇 2025-08-27 19:33
下一篇 2025-08-27 19:45

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信