Detecting Kernel Memory Leaks in Specialized Modules with Ownership Reasoning
背景:
内存泄漏:A memory leak happens when an allocated memory region is not released even though it will never be used again.分配的内存未被正常释放。
内存泄漏bug的不断触发,会造成denial-of-service
eg:
如果skb为假,则return err 但并未释放skb.
do:present K-MELD, a static detection system for kernel memory leaks.
Two key challenges
(1) Specialized functions.(An effective detection requires identifying such specialized allocation functions and the corresponding deallocation functions)
allocation/deallocation of a network buffer
(2) Complicated and lengthy data flow. (An effective detection must determine which location or function is responsible to release the memory object)
K-MELD (Kernel Memory Leak Detector)
K-MELD identifies specialized allocation functions by using a usage-driven and structureaware analysis, then uses a context-aware and path-sensitive mining technique to detect corresponding release functions.
K-MELD 通过使用驱动和结构感知分析来识别专门的分配函数,然后使用上下文感知和路径敏感的挖掘技术来检测相应的释放函数。
1.识别specialized allocation functions和the corresponding deallocation functions
2.回答应该在哪释放内存对象
Contributions
An approach for identifying specialized allocation functions.识别专用分配函数
A rule-mining approach for corresponding specialized deallocations.识别专用释放函数
An ownership reasoning mechanism for kernel objects.探索所有权
A scalable implementation and numerous new bugs. fuzz bug
对function中的内存泄漏进行建模:
1) A function retains ownership of the allocated memory
1) The function finishes without releasing the allocated memory
整体框架:
allocating function的识别
分配函数是在程序执行期间为数据结构动态分配内存的函数。
an allocation function has the following properties,分配函数的必要属性
• It returns a pointer.
• The pointer is immediately followed by a NULL check.
• The pointer is not derived from another base pointer.
int main() {
int arr[] = { 1, 2, 3, 4, 5 };
int* p = arr; // p points to the first element of arr
int* q = p + 2; // q points to the third element of arr//q为派生指针
printf("%d\n", *q); // prints 3
return 0;
}
• The object is initialized before being used, e.g., being read.
使用的分析方法:
Use-finding
分析涉及跟踪程序中给定变量或指针的使用情况,以确定其使用方式和使用位置。 分析可以帮助检测与以意外方式使用的未初始化变量或指针相关的潜在问题。 分析的输出可能是一组使用变量或指针的指令,以及它们各自的程序位置。
source-finding
分析涉及跟踪程序中给定变量或指针的来源,以确定它在何处以及如何被初始化。 该分析可以帮助检测与未初始化的变量/指针相关的潜在问题,或者不同表达式或程序流之间的不一致初始化。 分析的输出可能是一组创建或修改变量/指针的指令,以及它们各自的程序位置。
释放函数的识别
Identifying and Collecting Error-Handling Paths.
Sequential Pattern Mining.
error-handing
通过执行错误路径,触发回滚,调用释放函数。(比正常执行路径段的多)
use-finding
OWNERSHIP REASONING
enhanced escape analysis and consumer function detection
逃逸分析(escape analysis)就是在程序编译阶段根据程序代码中的数据流,对代码中哪些变量需要在栈上分配,哪些变量需要在堆上分配进行静态分析的方法。原本分配在栈上的内存,由于函数的return和其他函数对该内存的使用,造成栈上变量被分配到堆空间上,而堆上的内存需要自行释放,但没有进行释放,造成内存泄漏。
consumer function detection是分析对象如何被程序的其他部分使用和消费的技术。消费者函数是获取内存对象所有权的当前函数的被调用者。如果函数消费者释放或接收到内存对象,或者允许它在所有或部分执行路径上逃逸,我们就称它为函数消费者。
举例:
部分名词解释:
escaping pointers
指针转义是指将指向对象的指针传递到内部范围然后保存以供将来在该范围之外使用的情况。 这可能会导致内存管理问题,并成为潜在的错误来源。
例如,如果一个函数接受一个指向对象的指针,然后将该指针保存在该函数的范围之外,那么当对象被释放时就会出现问题,并且该指针变成一个悬空指针——一个引用一个对象的指针 不再存在的对象。 这可能会导致内存访问冲突和其他类型的错误。
为避免指针转义,最好将指针的范围限制在尽可能小的范围内。 当需要在函数范围之外传递指针时,复制所指向的对象而不是获取指针本身通常是个好主意。 在无法避免指针转义的情况下,重要的是要确保所指向的对象在指针最后一次使用之前未被取消分配。 此外,使用引用计数或垃圾回收等内存管理工具来管理内存并避免此类问题是一种很好的做法。
consumer function
A consumer function, on the other hand is a callee of current function that takes the ownership of the memory object, therefore the current function should not try to release after returning from the consumer function.
消费者函数是 获取内存对象所有权的当前函数的 被调用者。
如果函数消费者释放或接收到内存对象,或者允许它在所有或部分执行路径上逃逸,我们就称它为函数消费者。
Error-handling code
错误处理代码是软件程序中的一段代码,负责处理程序执行期间可能发生的错误和异常。 错误处理代码的复杂程度可以从非常简单到非常复杂,具体取决于错误的性质和软件系统的要求。
错误处理代码的目的是防止程序崩溃或产生不正确的输出以响应错误。 错误处理代码通常首先识别错误源并确定它是否可以在本地处理或是否需要向上传递到程序的更高级别。 一旦错误被识别和定位,代码就会采取适当的措施从错误中恢复并将系统返回到稳定状态。
根据程序的设计和错误的性质,错误处理代码可能会执行诸如将错误记录到文件、向用户显示错误消息、重试操作或回滚数据库事务等操作。 有效的错误处理代码对于编写健壮、可靠且能够从不可预见的情况中恢复的软件至关重要。
kmlloc vs vmalloc
kmalloc()和vmalloc()都是Linux内核提供的内存分配函数,但是它们的用途不同,各有优缺点。
kmalloc() 用于从内核的内存池中分配内存,这是在启动时保留的固定大小的连续内存区域。 kmalloc() 可用于分配子系统或驱动程序所需的少量和大量内存,但其大小限制为内核内存池的最大大小。
另一方面,vmalloc() 用于从内核的虚拟内存池中分配内存,它可以比系统的物理内存大。 vmalloc() 在子系统或驱动程序需要大量内存时很有用,例如设备驱动程序缓冲区或多媒体应用程序。
vmalloc() 也可用于分配非连续内存,这意味着页面在物理内存中不一定是连续的,但在虚拟内存中仍然是连续的,从而可以更灵活地分配内存。
但是,kmalloc() 和 vmalloc() 之间在性能上存在一些差异。 kmalloc() 可以比 vmalloc() 更快地分配内存,但是 vmalloc() 允许更大的内存分配和更大的内存分配灵活性。
总之,kmalloc() 一般用于中小型内存分配,而 vmalloc() 适用于大型和非连续内存分配。 使用哪一个取决于需要内存的子系统或驱动程序的具体需求。
Slab 缓存
Slab 缓存是 Linux 内核中使用的内存分配子系统。 它旨在为频繁创建和销毁的内核对象分配和管理内存。 Slab 缓存被用作通用内存分配方法(如 kmalloc() 和 vmalloc())的更有效替代方法。
Slab 缓存通过将内核内存分解为称为 slab 的固定大小的小块来工作。 每个 slab 代表一组相同的对象,例如,文件描述符、索引节点或网络套接字。 当需要一个对象时,它从适当的slab中分配。
slab 缓存的主要优点之一是它比通用内存分配更有效。 由于 slab 是预分配和预初始化的,因此可以通过简单地从其对应的 slab 返回一个预分配的对象来快速完成内存分配。 Slab 缓存避免了 kmalloc() 和 vmalloc() 的开销,这需要在每次需要对象时分配和初始化内存。
Slab 缓存还提供了更好的性能,因为它们可以重用驱动程序或子系统不再使用的对象,而不是每次需要新对象时都释放和重新分配内存。 这降低了内存分配和释放的频率和成本。
总的来说,slab 缓存是一种为频繁使用的对象分配内核内存的快速有效的方法,提供比通用内存分配方法更好的性能和更低的开销。
kmem_cache_create()
kmem_cache_create()是Linux内核提供的创建slab缓存的函数,slab缓存是一种比使用内核的通用内存分配器更快的对象分配缓存。
kmem_cache_create() 函数有几个参数,包括缓存的名称、缓存中每个对象的大小,以及应该与缓存一起使用的任何特定标志。 这些参数有助于定义缓存的特征以及它将分配的对象。
使用 slab 缓存可以通过为频繁使用的对象预分配内存来帮助优化内存中小对象的分配和释放。 缓存保持一个备用对象池,准备在需要时分配给进程,与使用通用内存分配器相比,提高了整体系统性能。
创建缓存后,可以使用 kmem_cache_alloc() 函数从中分配对象,该函数将指向缓存的指针作为其参数。 同样,可以使用 kmem_cache_free() 函数将对象释放回缓存。
总的来说,kmem_cache_create() 是一个有助于提高系统性能的函数,它允许创建适合特定系统需求的对象缓存,从而实现更高效的内存分配和释放。
function signature
函数签名,也称为函数原型或函数声明,是指编程语言中函数的语法和结构,规定了函数名称、返回类型、参数类型和名称。 函数签名向编译器提供有关函数输入和输出的信息,允许编译器验证函数是否被正确使用。
Use-finding
分析涉及跟踪程序中给定变量或指针的使用情况,以确定其使用方式和使用位置。 分析可以帮助检测与以意外方式使用的未初始化变量或指针相关的潜在问题。 分析的输出可能是一组使用变量或指针的指令,以及它们各自的程序位置。
source-finding
分析涉及跟踪程序中给定变量或指针的来源,以确定它在何处以及如何被初始化。 该分析可以帮助检测与未初始化的变量/指针相关的潜在问题,或者不同表达式或程序流之间的不一致初始化。 分析的输出可能是一组创建或修改变量/指针的指令,以及它们各自的程序位置。
errno
errno
是 C 和类似编程语言中的全局整数变量,用于报告在执行系统调用和库函数期间遇到的错误情况。 当函数调用遇到错误时,可以将errno
的值设置为相应的错误代码或值,用于识别和诊断错误。
在 C 语言中,errno
通常在 <errno.h>
头文件中定义,并在程序启动时或调用 main()
时初始化为 0。 当库函数或系统调用失败时,它会设置 errno 的值,指示发生的错误类型。 然后开发人员可以检查 errno 的值并相应地处理错误。
例如,用于在 C 中打开文件的 open()
系统调用如果成功则返回一个文件描述符,如果失败则设置 errno
的值以指示特定错误,例如 ENOENT
(没有这样的 文件或目录)、EACCES
(权限被拒绝)或 EINVAL
(无效参数)。
用 main()
时初始化为 0。 当库函数或系统调用失败时,它会设置 errno 的值,指示发生的错误类型。 然后开发人员可以检查 errno 的值并相应地处理错误。
例如,用于在 C 中打开文件的 open()
系统调用如果成功则返回一个文件描述符,如果失败则设置 errno
的值以指示特定错误,例如 ENOENT
(没有这样的 文件或目录)、EACCES
(权限被拒绝)或 EINVAL
(无效参数)。