Asan检测原理(AddressSanitizerAlgorithm chatgpt翻译记录)

原文链接:https://github.com/google/sanitizers/wiki/AddressSanitizerAlgorithm

Short version

运行时库替换了malloc和free函数。malloc区域周围的内存(红区)被标记为污染。free的内存被放置在隔离区并标记为污染。程序中的每个内存访问都会被编译器转换为以下方式:
转换前:

*address = ...;  // or: ... = *address;

转换后:

if (IsPoisoned(address)) {
  ReportError(address, kAccessSize, kIsWrite);
}
*address = ...;  // or: ... = *address;

关键是如何快速实现IsPoisoned和非常紧凑的ReportError。此外,对某些访问进行仪器化可能被证明是冗余的。

Memory mapping and Instrumentation

虚拟地址空间被分为两个不相交的类别:

应用程序内存(Mem):该内存由常规应用程序代码使用。

影子内存(Shadow):该内存包含影子值(或元数据)。影子内存与主应用程序内存之间存在对应关系。在主内存中毒一个字节意味着向相应的影子内存中写入某些特殊值。

这两个内存类别应该以一种能够快速计算影子内存(MemToShadow)的方式进行组织。

编译器执行的仪器化:

shadow_address = MemToShadow(address);
if (ShadowIsPoisoned(shadow_address)) {
  ReportError(address, kAccessSize, kIsWrite);
}

Mapping

AddressSanitizer将应用程序内存的8个字节映射为影子内存的1个字节。

对于任何对齐的8个字节的应用程序内存,只有9种不同的值:

  1. qword中的所有8个字节未被污染(即可寻址)。影子值为0。
  2. qword中的所有8个字节都被污染(即不可寻址)。影子值为负数。
  3. 前k个字节未被污染,其余8-k个字节被污染。影子值为k。

这是由malloc返回8字节对齐的内存块所保证的。唯一的情况是对齐的qword的不同字节具有不同的状态,是malloc分配的区域的尾部。例如,如果我们调用malloc(13),我们将有一个完整的未污染的qword和一个有5个未污染字节的qword。
仪器化如下所示:

byte *shadow_address = MemToShadow(address);
byte shadow_value = *shadow_address;
if (shadow_value) {
  if (SlowPathCheck(shadow_value, address, kAccessSize)) {
    ReportError(address, kAccessSize, kIsWrite);
  }
}
//检查我们访问qword的前k个字节的情况,并且这些k个字节未被污染。
bool SlowPathCheck(shadow_value, address, kAccessSize) {
  last_accessed_byte = (address & 7) + kAccessSize - 1;
  return (last_accessed_byte >= shadow_value);
}

MemToShadow(ShadowAddr)落入了不可寻址的ShadowGap区域。因此,如果程序尝试直接访问影子区域中的内存位置,程序将会崩溃。

64-bit

Shadow = (Mem >> 3) + 0x7fff8000;
[0x10007fff8000, 0x7fffffffffff] HighMem
[0x02008fff7000, 0x10007fff7fff] HighShadow
[0x00008fff7000, 0x02008fff6fff] ShadowGap
[0x00007fff8000, 0x00008fff6fff] LowShadow
[0x000000000000, 0x00007fff7fff] LowMem

32 bit

Shadow = (Mem >> 3) + 0x20000000;
[0x40000000, 0xffffffff] HighMem
[0x28000000, 0x3fffffff] HighShadow
[0x24000000, 0x27ffffff] ShadowGap
[0x20000000, 0x23ffffff] LowShadow
[0x00000000, 0x1fffffff] LowMem

Ultra compact shadow

可以使用更紧凑的影子内存,例如:

Shadow = (Mem >> 7) | kOffset;

实验正在进行中。

Report Error

ReportError可以作为调用实现(这是默认值),但是还有一些稍微更有效率和/或更紧凑的解决方案。在某个时候默认行为是:

  1. 将失败的地址复制到%rax(%eax)中。
  2. 执行ud2(生成SIGILL)。
  3. 在ud2后面的一字节指令中编码访问类型和大小。这三个指令总共需要5-6个字节的机器代码。

也可以只使用一个指令(例如ud2),但这将需要在运行时库中具有完整的反汇编器(或其他一些hack)。

Stack

为了捕获堆栈缓冲区溢出,AddressSanitizer对代码进行如下仪器化:
原始代码:

void foo() {
  char a[8];
  ...
  return;
}

仪器化代码:

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;
}

Examples of instrumented code (x86_64)

# long load8(long *a) { return *a; }
0000000000000030 <load8>:
  30:	48 89 f8             	mov    %rdi,%rax
  33:	48 c1 e8 03          	shr    $0x3,%rax
  37:	80 b8 00 80 ff 7f 00 	cmpb   $0x0,0x7fff8000(%rax)
  3e:	75 04                	jne    44 <load8+0x14>
  40:	48 8b 07             	mov    (%rdi),%rax   <<<<<< original load
  43:	c3                   	retq   
  44:	52                   	push   %rdx
  45:	e8 00 00 00 00       	callq  __asan_report_load8
# int  load4(int *a)  { return *a; }
0000000000000000 <load4>:
   0:	48 89 f8             	mov    %rdi,%rax
   3:	48 89 fa             	mov    %rdi,%rdx
   6:	48 c1 e8 03          	shr    $0x3,%rax
   a:	83 e2 07             	and    $0x7,%edx
   d:	0f b6 80 00 80 ff 7f 	movzbl 0x7fff8000(%rax),%eax
  14:	83 c2 03             	add    $0x3,%edx
  17:	38 c2                	cmp    %al,%dl
  19:	7d 03                	jge    1e <load4+0x1e>
  1b:	8b 07                	mov    (%rdi),%eax    <<<<<< original load
  1d:	c3                   	retq   
  1e:	84 c0                	test   %al,%al
  20:	74 f9                	je     1b <load4+0x1b>
  22:	50                   	push   %rax
  23:	e8 00 00 00 00       	callq  __asan_report_load4

Unaligned accesses(不对齐访问)

当前的紧凑映射不会捕获不对齐的部分越界访问:

int *x = new int[2]; // 8 bytes: [0,7].
int *u = (int*)((char*)x + 6);
*u = 1;  // Access to range [6-9]

在​https://github.com/google/sanitizers/issues/100中描述了一种可行的解决方案,但它会带来性能成本。

Run-time library

Malloc

运行时库替换了malloc/free并提供了错误报告函数,如__asan_report_load8。

malloc分配所请求的内存量,并在其周围使用红区。相应于红区的阴影值被标记为污染,主内存区域的阴影值被清除。

free将整个区域的阴影值标记为污染,并将内存块放入隔离队列中(这样在某段时间内,malloc不会再次返回该块)。

你可能感兴趣的:(c++)