父子局-每日草稿箱

20180108约学习,每天一篇————–是不可能的,能写多少算多少,每天的放在这里,以后再整理

…………….好了赢了,不学习了。。。。

Sanitizer

一款安全检查工具,它含有多个子工具。

LeakSanitizer

检测内存泄漏的

MemorySanitizer

检测未初始化使用的

ThreadSanitizer

检测竞争与死锁的

AddressSanitizer

检测内存访问问题的,目前支持如下错误检测:

1
2
3
4
5
6
7
8
Use after free (dangling pointer dereference)
Heap buffer overflow
Stack buffer overflow
Global buffer overflow
Use after return
Use after scope
Initialization order bugs
Memory leaks

这里可以把错误大致分为两类:①越界访问②释放后访问。针对这两个问题,此工具的原理如下:
首先针对问题②:

  1. 编译时用自己运行时库函数替换掉mallocfree,然后在被释放的内存里….下毒,并且在代码里的解指针操作前加上校验(此处可以优化,因为有的时候能保证访问是安全的,这时就不必要再检查了,如紧连着两次同属性访问同一地址在第一次检查后第二次就没有必要再检查了),若发现指向的内存有..毒就抛出异常。
    1
    2
    3
    4
    if (IsPoisoned(address)) {                          //此处为检查代码
    ReportError(address, kAccessSize, kIsWrite);
    }
    *address = ...; // or: ... = *address;
  2. 怎么为有毒与无毒呢?可以对程序真正使用的内存进行映射(shandow内存)以标志内存是否可用,由于是按字节寻址,所以一字节对应标志位的一比特,但事实上AddressSanitizer并不是直接这样1byte对应1bit的,而是8bytes对应1byte,于是不同处在于:
    1.8字节都没有中毒,对应的shandow字节为0
    2.8字节都有中毒,对应的shandow字节为负数
    3.前k字节没中毒,余下8-k字节中毒,则对应的shandow字节为8-k。因为malloc返回的内存都是8字节对齐的,所以shadow里的字节应该要么是0要么是负数,但是因为对齐填充的内存并不是用户申请的,所以它理应不能用,那么最小权限原则它不应该可用,例如malloc(1)这里请求1字节,事实上返回了8字节可用而且不会出什么错误,但是根据程序的逻辑之应该用1字节,于是shadow对应的字节因该为8 - 1 = 7
  3. 有位置记录内存状态了,再将真实使用的内存与shadow内存相映射就可以完成上述的检查了:
    1
    2
    3
    4
    shadow_address = MemToShadow(address);
    if (ShadowIsPoisoned(shadow_address)) {
    ReportError(address, kAccessSize, kIsWrite);
    }
    如Linux64的映射方式为:
    1
    Shadow = (Mem >> 3) + 0x7fff8000;
  4. 于是malloc时相应的shadowMem是0,而free后shadowMem为负数,这样就解决了释放后的问题。
  5. 另外shadowMem本身是系统级的内存访问保护。

其实shadowMem的作用不止于此,它的实质是在应用层标志所有不可访问的内存,于是经过转化就能用来解决问题①,对于越界访问只要在变量周围加入加入不可访问的内存,那么出现越界访问就被转化为非法访问了。

1
2
3
4
5
6
7
8
9
10
11
12
13
void foo() {
char redzone1[32]; // 32-byte aligned
char a[8]; // 32-byte aligned
char redzone2[24];
char redzone3[32]; // 32-byte aligned
int *shadow_base = MemToShadow(redzone1);
shadow_base[0] = 0xffffffff; // poison redzone1
shadow_base[1] = 0xffffff00; // poison redzone2, unpoison 'a'
shadow_base[2] = 0xffffffff; // poison redzone3
...
shadow_base[0] = shadow_base[1] = shadow_base[2] = 0; // unpoison all
return;
}

事实上从上面的介绍它并不完美,对于释放后使用,当释放后再分配前使用明显是会检测出来的,但是当释放后又被再次分配,通过上面的检查它其实是不会被检测的,AddressSanitizer的实现方法就是将释放后的内存放入一个隔离队列,在未来一段时间内不再分配它,以此来增加被检测的可能性。另外它还有很多其他特性详参见官方文档。

使用

GCC 4.8版开始支持它,更多用法与问题可以访问wiki页

1
2
3
4
5
6
7
8
9
10
11
12
#对于单文件
gcc -fsanitize=address -ggdb -o test test.c #前面是为了开启功能,后面是为了调试方便,若不开调试需要打开输出错误
#对于工程
./configure --disable-shared CFLAGS="-fsanitize=address -ggdb" CXXFLAGS="-fsanitize=address -ggdb" LDFLAGS="-fsanitize=address"
make
#对于afl,
AFL_USE_ASAN=1 ./configure CC=afl-gcc CXX=afl-g++ LD=afl-gcc--disable-shared
AFL_USE_ASAN=1 make
#-fno-omit-frame-pointer #输出调用栈信息,获取更好的堆栈追踪,实际测试好像没什么变化

#默认检测到第一个问题程序就终止了,运行时加上下面这个环境变量就能让程序继续执行
ASAN_OPTIONS=halt_on_error=0

另外我也遇到这个问题了,解答笑了,哈哈哈哈哈哈哈哈哈~

1
2
3
Q: Why didn't ASan report an obviously invalid memory access in my code?

A1: If your errors is too obvious, compiler might have already optimized it out by the time Asan runs.

参考

https://github.com/google/sanitizers/wiki/AddressSanitizerAlgorithm