浅谈内存检测工具与IOT系统

浅谈Kasan、Hwasan、Valgrind和MTE在IOT系统的应用

Posted by Weizhou on February 16, 2020

前言

在现有的IOT设备和系统中进行开发时踩内存是最容易产生的现象之一,同样有着很大的安全隐患。 与现在复杂操作系统Linux,Windows相比,rtos系统同常运行在资源相对受限的单板上。 本文将很对现有的内存检测技术进行简短的分析和介绍。

正文

Valgrind Memcheck

Valgrind Memcheck应该是最早一款带有运行时内存检测的工具。Valgrind拥有一系列工具memcheck是其中一个,该套工具的特点是全面但比较笨重。 该机制的基本原理是通过模仿CPU的工作模式对每一个操作的bit进行验证。memcheck会维护两张表用来记录数据和地址的有效性。
  首先memcheck Valgrind引入了一张合法值表(Valid-value (V) bits),简单来说合法值表用来记录地址是否被初始化。并且只会对三种情况下使用未初始化变量进行检测:
  1. 当变量用于生成分配一块内存
  2. 当变量用于决定执行的路径,例如if
  3. 当变量用于系统调用
举例来说,如下述代码所示,变量ja[10]b[10]在该段代码中都没有进行初始化,并且在循环中进行了读取和存储的操作,但该情景并没有满足上述的三条情况,所以并不会产生报错信息。

int i, j;
int a[10], b[10];
for ( i = 0; i < 10; i++ ) {
  j = a[i];
  b[i] = j;
}

再看下面这段代码:

int i, j;
int a[10];
for ( i = 0; i < 10; i++ ) {
  j += a[i];
}
if ( j == 77 )
  printf("hello there\n");

该段代码在执行到if ( j == 77 )语句时会触发报错,应为ja[10]都为未初始化变量,所以j += a[i]j还是未初始化的变量,当进行判断是符合上述第二个判断条件,该判断会改变程序的执行流,所以会在该情况下报错。
  其次其引入维护了一张合法值表(Valid-value (V) bits),该表用与记录那些地址是可访问,哪些是不可访问的。记录的方式同样是对内存中的每一个bit使用一个bitmap进行维护。合法值表会在如下的情况进行更新和修改:
  1. 当程序开始运行的时候会将全局变量的内存空间在和法表中设置为可访问
  2. 使用内存申请函数时例如malloc/new,会将分配出的空间在合法值表中设置为可访问
  3. 当栈指针SP上移或下移时会根据可用的栈空间地址更新合法值表
  4. 当使用系统调用时会根据需要更新合法值表,例如:当调用mmap函数将文件映射到内存中时,会将被映射的内存设置为可访问
当CPU进行读写操作时会依据合法值表判断是否操作了不合法的地址。除此之外,还能通过该表实现对UAF(释放后使用)和多重释放问题的检测。
Valgrind Memcheck即同时维护上述两个表,并在每次读写时进行检测从而实现的对内存合法性使用的检测。很明显的一个不足就是该机制不能够检测栈中变量溢出stack buffer overflow和全局变量的溢出


Kasan(Kernel Address Sanitizer)

Kasan(Kernel Address Sanitizer)是Google在2015年在github上开源的一种内存检测技术。该技术需要两方面的支持:编译器+操作系统。

首先是编译器部分,该算法首先集成到了LLVM编译器中,之后在GCC 4.9.2和更高版本中都得到了很好的适配。并经过更深层次的扩展形成了Asan(Address Sanitizer)工具,并且继承了跟多的Sanitizer的功能:数组边界检测、使用Null指针检测以及整数反转检测。Asan的大部分功能主要是在Linux的用户空间中进行检测和实现。Kasan则可视为是Asan在系统内核的支持。编译器主要做了一个功能是编译时插桩。插桩的内容可大致分为两个部分:
  一,编译时在ldr和str读写指令前插入检测函数接口,并将预写入或读取的内存地址作为参数传入到到被调用的函数中。并且会基于读写的数据长度插入不同的函数接口。例如:当想欲读取的数据是byte类型时,则会插入_load1_(uint32 addr)类似的函数,当读取的数据类型是half word类型时,则会插入_load2_(uint32 addr)类似的函数接口。

Hwasan

(Todo)

MTE (Memory Tagging Extension)

(Todo)