MTE - 栈内存检测原理

本文基于 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博客

  • 25
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值