C语言程序内存安全检查

静态存储区检查

1
2
3
set(CMAKE_EXE_LINKER_FLAGS "-Wl,-Map=output.map") # 生成map文件  
set(CMAKE_C_FLAGS "-fdata-sections") # 把static变量地址输出到map文件
set(CMAKE_CXX_FLAGS "-fdata-sections")

帮助检测静态存储区的arr数组是否出现了数组越界操作。


动态存储区检查

工具

  • ASAN
  • dmalloc
  • valgrind

ASAN

ASAN(Address Sanitizer)是针对 C/C++ 的快速内存错误检测工具,在运行时检测 C/C++ 代码中的多种内存错误。 在 GCC 编译选项中设置 -fsanitize=address,启用快速内存错误检测器 ASAN。比如 -fsanitize-coverage=trace-pc,启用覆盖率指导的模糊代码检测,等。 详细参考:Home · google/sanitizers Wiki (github.com)

Valgrind

Valgrind由内核(core)以及基于内核的其他调试工具组成。其基于仿真方式对程序进行调试,它先于应用程序获取实际处理器的控制权,并在实际处理器的基础上仿真一个虚拟处理器,并使应用程序运行于这个虚拟处理器之上,从而对应用程序的运行进行监视。

官网 Valgrind工具包包含多个工具,如Memcheck、Cachegrind、Helgrind、Callgrind、Massif。

  • Memcheck工具是Valgrind中最常用的工具,用来检测程序中出现的内存问题。
  • Callgrind收集程序运行时的一些数据,函数调用关系等信息,还可以有选择地进行cache模拟。在运行结束时,它会把分析数据写入一个文件。
  • Helgrind 主要用来检查多线程程序中出现的竞争问题。
  • Callgrind 模拟 CPU中的一级缓存I1,D1和L2二级缓存,能够精确地指出程序中 cache的丢失和命中。
  • Massif 堆栈分析器,它能测量程序在堆栈中使用了多少内存,如堆块、堆管理块和栈的大小。 简单使用,对下面的 C 程序进行测试:
1
2
3
4
5
6
7
8
9
10
11
#include <stdlib.h>  

void f(void) {
int* x = malloc(10 * sizeof(int));
x[10] = 0;       // problem 1: heap block overrun
}                 // problem 2: memory leak -- x not freed

int main(void) {
f();
return 0;
}
1
2
3
4
5
sudo apt install valgrind

gcc -g valgrind_test.c -o valgrind_test

valgrind --leak-check=yes --tool=memcheck ./valgrind_test

自定义带检测的内存管理函数

在实际内容前后加上指定的标记数据,达到检测哨兵的效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#define BEFORE_RED_AREA_LEN  (4)            // 前红区长度  
#define AFTER_RED_AREA_LEN   (4)            // 后红区长度
#define LEN_AREA_LEN         (4)            // 长度区长度

#define BEFORE_RED_AREA_DATA (0x11223344u)  // 前红区数据
#define AFTER_RED_AREA_DATA  (0x55667788u)  // 后红区数据

void *Malloc(size_t __size)
{
    // 申请内存:4 + 4 + __size + 4
    void *ptr = malloc(BEFORE_RED_AREA_LEN + AFTER_RED_AREA_LEN + __size + LEN_AREA_LEN);
    if (NULL == ptr)
    {
        printf("[%s]malloc error\n", __FUNCTION__);
        return NULL;
    }

    // 往前红区地址写入固定值
    *((unsigned int*)(ptr)) = BEFORE_RED_AREA_DATA;     

    // 往长度区地址写入长度     
    *((unsigned int*)(ptr + BEFORE_RED_AREA_LEN)) = __size;  

    // 往后红区地址写入固定值
    *((unsigned int*)(ptr + BEFORE_RED_AREA_LEN + LEN_AREA_LEN + __size)) = AFTER_RED_AREA_DATA;  

    // 返回数据区地址
    void *data_area_ptr = (ptr + BEFORE_RED_AREA_LEN + LEN_AREA_LEN);
    return data_area_ptr;
}


void CheckMem(void *ptr, size_t __size)
{
    void *data_area_ptr = ptr;

    // 检测是否踩了前红区
    printf("[%s]before_red_area_data = 0x%x\n", __FUNCTION__, *((unsigned int*)(data_area_ptr - LEN_AREA_LEN - BEFORE_RED_AREA_LEN)));
    assert(*((unsigned int*)(data_area_ptr - LEN_AREA_LEN - BEFORE_RED_AREA_LEN)) == BEFORE_RED_AREA_DATA);

    // 检测是否踩了长度区
    printf("[%s]len_area_data = 0x%x\n", __FUNCTION__, *((unsigned int*)(data_area_ptr - LEN_AREA_LEN)));
    assert(*((unsigned int*)(data_area_ptr - LEN_AREA_LEN)) == __size); 

    // 检测是否踩了后红区
    printf("[%s]after_red_area_data = 0x%x\n", __FUNCTION__, *((unsigned int*)(data_area_ptr + __size)));
    assert(*((unsigned int*)(data_area_ptr + __size)) == AFTER_RED_AREA_DATA); 
}


void Free(void *ptr)
{
    void *all_area_ptr = ptr - LEN_AREA_LEN - BEFORE_RED_AREA_LEN;

    // 检测是否踩了前红区
    printf("[%s]before_red_area_data = 0x%x\n", __FUNCTION__, *((unsigned int*)(all_area_ptr)));
    assert(*((unsigned int*)(all_area_ptr)) == BEFORE_RED_AREA_DATA);

    // 读取长度区内容
    size_t __size = *((unsigned int*)(all_area_ptr + BEFORE_RED_AREA_LEN));

    // 检测是否踩了后红区
    printf("[%s]before_red_area_data = 0x%x\n", __FUNCTION__, *((unsigned int*)(all_area_ptr + BEFORE_RED_AREA_LEN + LEN_AREA_LEN + __size)));
    assert(*((unsigned int*)(all_area_ptr + BEFORE_RED_AREA_LEN + LEN_AREA_LEN + __size)) == AFTER_RED_AREA_DATA);

    // 释放所有区域内存
    free(all_area_ptr);
}

本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!