Loading [MathJax]/jax/input/TeX/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Android Address Sanitizer (ASan) 原理简介

Android Address Sanitizer (ASan) 原理简介

作者头像
字节流动
发布于 2021-06-09 06:37:58
发布于 2021-06-09 06:37:58
5.6K02
代码可运行
举报
文章被收录于专栏:字节流动字节流动
运行总次数:2
代码可运行

前面介绍了 NDK 开发中快速上手使用 ASan 检测内存越界等内存错误的方法,现分享一篇关于 ASan 原理介绍的文章。


本文介绍Android上定位native代码野指针/数组越界/栈踩踏的终极武器—-Address Sanitizer(asan) 和 Hardware assisted Address Sanitizer (hwasan) 的基本实现原理。

Address Sanitizer

基本原理

程序申请的每 8bytes 内存映射到 1byte 的 shadown 内存上。

因为 malloc 返回的地址都是基于8字节对齐的,所以每8个字节实际可能有以下几个状态:

case 1:8 个字节全部可以访问,例如char* p = new char[8]; 将0写入到这8个字节对应的1个字节的shadow内存。

case 2:前 1<=n<8 个字节可以访问, 例如char* p = new char[n], 将数值n写入到相应的1字节的shadow内存,尽管这个对象实际只占用5bytes,malloc的实现里[p+5, p+7]这尾部的3个字节的内存也不会再用于分配其他对象,所以通过指针p来越界访问最后3个字节的内存也是被允许的。

asan还会在程序申请的内存的前后,各增加一个redzone区域(n * 8bytes),用来解决overflow/underflow类问题。

free对象时,asan不会立即把这个对象的内存释放掉,而是写入1个负数到该对象的shadown内存中,即将该对象成不可读写的状态, 并将它记录放到一个隔离区(book keeping)中, 这样当有野指针或use-after-free的情况时,就能跟进shadow内存的状态,发现程序的异常;一段时间后如果程序没有异常,就会再释放隔离区中的对象。

编译器在对每个变量的load/store操作指令前都插入检查代码,确认是否有overflow、underflow、use-after-free等问题。

检测堆上变量的非法操作的基本实现方式

asan在运行时会替换系统默认的malloc实现,当执行以下代码时,

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void foo() {
  char* ptr = new char[10];
  ptr[1] = 'a';
  ptr[10] = '\n'
}

我们知道 new 关键字实际最终调用还是 malloc 函数,而 asan 提供的 malloc 实现基本就如下代码片段所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// asan提供的malloc函数
void* asan_malloc(size_t requested_size) {
    size_t actual_size = RED_ZONE_SIZE /*前redzone*/ + align8(requested_size) + RED_ZONE_SIZE/*后redzone*/;
    // 调用libc的malloc去真正的分配内存
    char* p = (char*)libc_malloc(acutal_size);
    // 标记前后redzone区不可读写
    poison(p, requested_size);

    return p + RED_ZONE_SIZE; // 返回偏移后的地址
}

void foo() {
  // 运行时实际执行的代码
  char* ptr = asan_malloc(10);

  // 编译器插入的代码
  if (isPoisoned(ptr+1)) {
    abort();
  }
  ptr[1] = 'a';

  // 编译器插入的代码
  if (isPoisoned(ptr+10)) {
    abort(); // crash:访问到了redzone区域
  }
  ptr[10] = '\n'
}

asan_malloc 会额外多申请 2 个 redzone 大小的内存, 实际的内存布局如下所示:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
----------------------------------------------------------------   
|    redzone(前)    |    用户申请的内存      |    redzone()    |   
----------------------------------------------------------------

用户申请的内存对应的shadow内存会被标记成可读写的,而redzone区域内存对应的shadow内存则会被标记成不可读写的,

这样就可以检测对堆上变量的越界访问类问题了。

检测栈上对象的非法操作的基本实现方式

对于以下代码片段

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void foo() {  char a[8];  a[1] = '\0';  a[8] = '\0'; // 越界  return;}

编译器则直接在 a 数组的前后都插入1个 redzone,最终的代码会变成下面的方式:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
void foo() {  char redzone1[32];  // 编译器插入的代码, 32字节对齐  char a[8];  char redzone2[24];  // 编译器插入的代码, 与用于申请的数组a一起做32字节对齐  char redzone3[32];  // 编译器插入的代码, 32字节对齐  // 编译器插入的代码  int  *shadow_base = MemToShadow(redzone1);  shadow_base[0] = 0xffffffff;  // 标记redzone1的32个字节都不可读写  shadow_base[1] = 0xffffff00;  // 标记数组a的8个字节为可读写的,而redzone2的24个字节均不可读写  shadow_base[2] = 0xffffffff;  // 标记redzone3的32个字节都不可读写  // 编译器插入的代码  if (isPoisoned(a+1)) {      abort();  }  a[1] = '0';  // 编译器插入的代码  if (isPoisoned(a+8)) {      abort(); // crash: 因为a[8]访问到了redzone区  }  a[8] = '\0';  // 整个栈帧都要被回收了,所以要将redzone和数组a对应的内存都标记成可读可写的  shadow_base[0] = shadow_base[1] = shadow_base[2] = 0;  return;}

程序申请的对象内存和它的shadow内存映射关系

因为 asan 对每 8bytes 程序内存会保留1byte 的 shadow 内存,所以在进程初始化时,asan得预留(mmap)1/8的虚拟内存。

而对于64bit的Linux,实际最大可用虚拟地址是pow(2, 47), 另外要保证预留的地址不会被程序启动时就占用掉,所以实际预留的地址要再加上一个适当的偏移, 这样就不会与app的申请内存区域重叠,于是有: ShadowByteAddr = (AppMemAddr >> 3) + Offset


Hardware assisted address sanitizer 原理简介

依赖 AArch64的 address tagging,也叫 top byte ignore 特性,允许程序将自定义数据存到虚拟地址的最高8位(bit),cpu在操作这个虚拟地址的时候,会自动忽略高8位。

基本原理

内存对齐:不论是在堆上,栈上分配的对象,还是全局对象,他们的内存起始地址都会做16bytes对齐(malloc或者编译器来保证)

标记内存:在分配这些对象时,hwasan挑选一个随机数值tag(<=255),对这个对象做标记,并将它保存到这个对象的对应shadow内存中

标记指针:hwasan提供的malloc函数返回的对象虚拟地址的最高8bits也被设置成同样的tag值,栈上对象的标记工作由编译器完成

映射关系:每16 bytes程序内存映射到1 byte的shadow内存,用于保存tag值

回收对象:重新分配一个随机值,保存对象地址关联的shadow内存中,如果有人使用一个指向一个已经被释放了的对象指针去访问数据,由于tag已经发生了变化,就会被及时检测到

检验tag:跟asan类似,在对每个指针的store/load指令前,编译器都插入相应的检查指令,用于确认正在被读或写的指针的高8位上的tag值与指针所指向对象对应的shaow内存里的tag值是一致的,如果出现不一致就会终止当前进程。

另外,当分配的对象的内存实际小于16字节时,例如我们通过 char* p = new char[10] 分配一个长度是10byte的char数组,因为要保证每16个字节对应1个字节的shadow byte,所以[p+10, p+15]这6个字节的内存也不会再用于分配其他对象。而这部分预留的闲置内存的最后一个字节就可以用来存放数组的实际大小,这样的话,当检测到指针上的tag与shadow内存里的tag是一致时,还要再校验指针所指向对象的实际大小来检测是否有数组越界问题。

原理图解

hwasan的漏检率

对一个指针上的保存的tag值,它实际指向的对象所对应的shadow内存里的tag值可能有256(2^8)种可能。

那么2个不同的对象就会有1/256,即大约 0.4% 的概率拥有相同tag的情况,这样的野指针/越界方位就不能及时的被检测到,但我们还是可以通过长时间的测试和多次测试来提高检测率。

hwasan相比asan的优势

  • 相比 asan,hwsan 的 shadow memory 占用更少(10% ~ 35%) hwsan也要对分配的栈/堆上的变量做16字节对齐,还有每16个字节会占用1个字节的shadow内存用于保存tag值,但它不再要像asan的实现里那样,在分配的对象前后添加redzone,来检查越界访问,所以内存占用会降低不少。
  • 定位对于野指针类问题的概率更高 asan 只能检测到一个野指针恰好访问的是某个对象之前或之后的 redzone 内存的情况,理论上 redzone 越大,能检测到野指针的概率也就越高,不过随之也会带来更大的内存开销(overload); hwsan上,因为两个不同对象的tag值一般是不同的,所以只要是有野指针就能够被及时检测到。

参考

  • AddressSanitizer: A Fast Address Sanity Checker
  • Detecting Memory Corruption Bugs With HWASan
  • google/sanitizers
  • Hardware-assisted AddressSanitizer Design Documentation

作者:wwm 来源:https://wwm0609.github.io/2020/04/17/hwasan/

-- END --

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-05-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 字节流动 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
ASAN和HWASAN原理解析
由于虚拟机的存在,Android应用开发者们通常不用考虑内存访问相关的错误。而一旦我们深入到Native世界中,原本面容和善的内存便开始凶恶起来。这时,由于程序员写法不规范、逻辑疏漏而导致的内存错误会统统跳到我们面前,对我们嘲讽一番。
Linux阅码场
2020/07/07
4.1K0
ASAN和HWASAN原理解析
面向开发的内存调试神器,如何使用ASAN检测内存泄漏、堆栈溢出等问题
首先,先介绍一下 Sanitizer 项目,该项目是谷歌出品的一个开源项目,该项目包含了 ASAN、LSAN、MSAN、TSAN等内存、线程错误的检测工具,这里简单介绍一下这几个工具的作用:
GreatSQL社区
2022/05/11
6.8K0
LLVM 工具系列 - Address Sanitizer 实现原理(2)
上篇文章 「Address Sanitizer 基本原理介绍及案例分析」里我们简单地介绍了一下 Address Sanitizer 基础的工作原理,这里我们再继续深挖一下深层次的原理。
JoeyBlue
2023/01/08
8030
宋牧春: Linux内核内存corruption检查机制KASAN实现原理
http://www.wowotech.net/memory_management/424.html
Linux阅码场
2019/10/08
2.4K0
宋牧春: Linux内核内存corruption检查机制KASAN实现原理
LLVM 工具系列 - Address Sanitizer 基本原理介绍及案例分析(1)
LLVM 提供了一系列的工具帮助 C/C++/Objc/Objc++ 开发者检查代码中可能的潜在问题,这些工具包括 Address Sanitizer,Memory Sanitizer,Thread Sanitizer,XRay 等等, 功能各异。
JoeyBlue
2023/01/08
2.8K0
LLVM 工具系列 - Address Sanitizer 基本原理介绍及案例分析(1)
KASAN实现原理【转】
KASAN是一个动态检测内存错误的工具。KASAN可以检测全局变量、栈、堆分配的内存发生越界访问等问题。功能比SLUB DEBUG齐全并且支持实时检测。越界访问的严重性和危害性通过我之前的文章(SLUB DEBUG技术)应该有所了解。正是由于SLUB DEBUG缺陷,因此我们需要一种更加强大的检测工具。难道你不想吗?KASAN就是其中一种。KASAN的使用真的很简单。但是我是一个追求刨根问底的人。仅仅止步于使用的层面,我是不愿意的,只有更清楚的了解实现原理才能更加熟练的使用工具。不止是KASAN,其他方面我也是这么认为。但是,说实话,写这篇文章是有点底气不足的。因为从我查阅的资料来说,国内没有一篇文章说KASAN的工作原理,国外也是没有什么文章关注KASAN的原理。大家好像都在说How to use。由于本人水平有限,就根据现有的资料以及自己阅读代码揣摩其中的意思。本文章作为抛准引玉,如果有不合理的地方还请指正。
233333
2019/01/03
2.6K0
asan内存检测工具实例
GCC和CLANG都已经集成了功能,编译时加编译选项即可。主要是-fsanitize=address,其他便于调试。
mingjie
2023/10/13
7710
浅谈「内存调试技术」
内存问题在 C/C++ 程序中十分常见,比如缓冲区溢出,使用已经释放的堆内存,内存泄露等。
天存信息
2021/05/11
1.1K0
浅谈「内存调试技术」
技术解码 | 内存问题的分析与定位
本期的技术解码,为您解析 编程中,内存问题的分析与定位方法 对编程语言设计来说,内存管理分为两大类:手动内存管理(manual memory management) 和垃圾回收(garbage collection). 常见的如C、C++使用手动内存管理,Java使用垃圾回收。本文主要关注手动内存管理。 GC GC使内存管理自动化,缺点是引入了GC时不可预测的暂停(unpredictable stall),对实时性要求高的场景不适用。现代的GC实现一直朝着减小“stop-the-world"影
腾讯云音视频
2021/04/29
4.6K0
Arm Memory Tagging Extension
Arm MTE(内存标记)作为Armv8.5指令集的一部分引入。MTE现在内置于Arm 最近宣布的符合Armv9 的 CPU 中,例如 Cortex-X2、Cortex-A710 和Cortex-A510。未来基于Armv9 的 CPU 也将集成 MTE。
用户9732312
2022/05/13
1.8K0
Arm Memory Tagging Extension
Kasan - Linux 内核的内存检测工具
https://www.ibm.com/developerworks/cn/linux/1608_tengr_kasan/index.html
Linux阅码场
2019/10/08
6K0
Kasan - Linux 内核的内存检测工具
Linux内核内存检测工具KASAN
KASAN 是 Kernel Address Sanitizer 的缩写,它是一个动态检测内存错误的工具,主要功能是检查内存越界访问和使用已释放的内存等问题。KASAN 集成在 Linux 内核中,随 Linux 内核代码一起发布,并由内核社区维护和发展。本文简要介绍 KASAN 的原理及使用方法。
233333
2020/11/26
9.5K0
Linux内核内存检测工具KASAN
【C 语言】内存管理 ( 动态内存分配 | 栈 | 堆 | 静态存储区 | 内存布局 | 野指针 )
程序运行后的内存布局 : 从高地址 到 低地址 介绍, 顺序为 栈 -> 堆 -> bss段 -> data 段 -> text段 ;
韩曙亮
2023/03/27
1.9K0
【C 语言】内存管理 ( 动态内存分配 | 栈 | 堆 | 静态存储区 | 内存布局 | 野指针 )
C语言复习概要(五)
指针(Pointer)是C/C++语言中最具特色、也是最让人困惑的概念之一。指针让程序员能够直接操作内存,打破了传统高阶编程语言中的封装抽象。尽管它的学习曲线陡峭,但掌握指针不仅可以提高代码的性能,还能帮助我们理解计算机底层的工作原理。
Undoom
2024/10/16
1210
分享丨C/C++内存管理详解--堆、栈
因此要想成为C++高手,内存管理一关是必须要过的,除非放弃C++,转到Java或者.NET,他们的内存管理基本是自动的,当然你也放弃了自由和对内存的支配权,还放弃了C++超绝的性能。
一头小山猪
2020/04/10
1.2K0
Cache一致性导致的踩内存问题
本文主要分享一个Cache一致性踩内存问题的定位过程,涉及到的知识点包括:backtrace、内存分析、efence、wrap系统函数、硬件watchpoint、DMA、Cache一致性等。
coderhuo
2020/01/20
3.1K0
Cache一致性导致的踩内存问题
总结嵌入式C语言知识点
怎么才能做好嵌入式开发?学好C语言吧!今天就来推荐一篇大佬写的嵌入式C语言知识点总结。
嵌入式与Linux那些事
2023/12/18
5440
总结嵌入式C语言知识点
用C来实现内存池
介绍:        设计内存池的目标是为了保证服务器长时间高效的运行,通过对申请空间小而申请频繁的对象进行有效管理,减少内存碎片的产生,合理分配管理用户内存,从而减少系统中出现有效空间足够,而无法分配大块连续内存的情况。 目标:     此次设计内存池的基本目标,需要满足线程安全性(多线程),适量的内存泄露越界检查,运行效率不太低于malloc/free方式,实现对4-128字节范围内的内存空间申请的内存池管理(非单一固定大小对象管理的内存池)。 内存池技术设计与实现     本内存池的设计方法主要参考S
猿人谷
2018/01/17
3.2K1
用C来实现内存池
sanitizer工具集
Sanitizers是谷歌发起的开源工具集,包括了Address Sanitizer, undefined behavior Sanitizer, Thread Sanitizer, Leak Sanitizer。GCC从4.8版本开始支持Address sanitizer和Thread Sanitizer,4.9版本开始支持Leak Sanitizer和undefined behavior Sanitizer。
全栈程序员站长
2022/11/17
1.3K0
C语言重点突破(五) 动态内存管理
动态内存管理是指在一个程序运行期间动态地分配、释放和管理内存空间的过程。在应用程序中,当程序需要使用变量或对象时,需要在内存中分配一段空间,并在使用完毕后释放该空间,以提高程序的效率和性能。本文意在介绍常用动态内存函数以及如何使用它们来进行动态内存分配。
对编程一片赤诚的小吴
2024/01/23
2870
C语言重点突破(五) 动态内存管理
相关推荐
ASAN和HWASAN原理解析
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验