CAMP: Compiler and Allocator-based Heap Memory Protection 论文阅读笔记

黎 浩然/ 24 10 月, 2023/ 内存错误检测工具/SANITIZER, 研究生/POSTGRADUATE/ 0 comments

https://zplin.me/papers/CAMP.pdf

CAMP是一个检测和捕获堆内存错误(Heap Memory Corruption)工具。CAMP利用编译器和定制内存分配器。编译器为目标程序添加了边界检查和逃逸跟踪指令,而内存分配器跟踪内存范围,并与插桩协作减少悬空指针。

逃逸跟踪指令是指代在编程语言或编译器中添加的指令,用于跟踪在程序中创建的对象的引用何时超出其创建范围的情况。逃逸跟踪指令的目的是帮助检测和解决内存管理问题,例如悬空指针内存泄漏堆溢出

CAMP能够启用各种编译优化策略,从而消除了冗余和不必要的检查仪器。这个设计最大程度地减少了运行时开销,同时保证了安全性。作者对CAMP与现有工具评估和比较,使用了真实应用程序和SPECCPU基准测试。

Design Overview

Figure1: The design overview of CAMP.

CAMP编译器是建立在 LLVM 之上的,它将源代码作为输入并输出与CAMP内存分配器链接的二进制文件。编译时,编译器首先将源代码翻译成LLVM IR,然后用范围检查和逃逸跟踪指令插桩以防止堆内存错误。在此之后,CAMP编译器应用了几种新颖的编译优化以降低内存保护的开销,同时不牺牲安全性保证。

在运行时,CAMP的内存分配器处理堆内存分配释放请求以及还提供对插桩指令的支持。具体来说,内存分配器为每个分配跟踪对象内存范围以在每个访问前中验证指针的边界信息。分配器还处理逃逸跟踪指令以建立指向关系。

Listing1: A toy vulnerable example.

Listing2: The toy program with CAMP’s protection.

Compiler Instrumentation

Instrumenting Range Checking:

程序模型不允许将整数强制转换为指针,初始指针要么来自显式的内存分配(例如malloc),要么来自全局或堆栈变量的地址。然后,通过指针算术运算创建新指针,以便访问内存。

CAMP中的范围检查确保在进行算术运算后,所有指针仍然保持在界内从而防止堆溢出。范围检查接受三个参数,分别是基指针 src、算术运算的结果指针dstdst类型大小size

基指针(base pointer)用于跟踪程序中的数据结构或内存分配的基本位置或起点。

Garbage Collection Safepoints in LLVM — LLVM 18.0.0git documentation

CAMP运行时将断言,从dstdst+size的内存范围在src的内存范围内。需要注意的是,CAMP只保护堆内存,因此验证非堆内存指针是多余的。为了节省不必要的验证,CAMP 会对LLVM IR执行数据流和别名分析,以确定指针之间的指向关系。如果编译时可以确定某个指针不指向堆内存,CAMP将不会为其插桩范围检查。

Instrumenting Escape Tracking:

在可能存在指针逃逸(即复制指针)的操作之后插入跟踪(Tracking)。跟踪接受复制的指针及其存储的地址作为参数。CAMP会为所有潜在的指针逃逸插桩,CAMP会跳过那些在编译时确定不引用堆的指针逃逸。CAMP的目标是保护堆内存,跳过这些非堆内存有助于提高性能。

CAMP的逃逸跟踪指令(第3行)以变量地址和指针作为输入,注释哪个地址包含对内存分配的引用。然后,程序释放了这些内存(第7行)。在释放过程中,CAMP将识别已经存在的悬空指针,然后将其中和为非友好状态。因此变量buf不再引用已释放的内存,当试图对其进行引用时,程序会崩溃(第9行)。

Compilation Optimization

Listing3: An example of optimizing structuref ield access checks.

Optimizing range checks with type information:

函数bar分配了一个新的对象obj(第7行)。malloc的返回类型是void*,而不是struct obj*,因此编译器插入了一个类型转换指令,在此之后,CAMP 插入了一个范围检查以确保内存空间足够容纳结构体obj(第8行)。 通过这种类型转换验证,编译器可以安全地推断有类型指针的内存空间至少是其类型大小。 因此,编译器可以得出结构字段引用的指针是在界内的结论。因此CAMP可以移除第12, 14行的范围检查。此外,编译器还可以保证指针o(第6行)的内存大小至少是其类型大小,因此可以优化并消除第8行的范围检查。

Removing Redundant Instructions

Listing4: Example codes of applying eliminating redundant optimization.

首先收集所有结果指针(第3行),并根据它们的基指针对它们进行分类(第5-8行),其中键是基指针,值是结果指针的集合。在每次迭代中,遍历映射中的元素(第12行)。如果我们找到满足冗余条件的两个指针(第13行), 则移除后面的那个(第14行)并调整剩下的那个的偏移量为它们的最大值(第15行)。之后,更新迭代的输出(第16行)并退出循环以开始下一次迭代。需要注意的是,如果没有找到冗余对,输出将仅为迭代的输入(第19行)。

CAMP的实验结果评估示例

总结

这篇论文工作虽然没有太亮眼的创新和设计,实验结果也没有比同类型的工具有很大的提升。但是却提供了基于LLVM后端的IR插桩工具简单却相对完善的设计思路。

Share this Post

Leave a Comment

您的邮箱地址不会被公开。 必填项已用 * 标注

*
*