一、kfence
Kfence (Kernel Electric Fence) 是 Linux 内核引入的一种低开销的内存错误检测机制,因为是低开销的所以它可以在运行的生产环境中开启,同样由于是低开销所以它的功能相比较 Kasan 会偏弱。
Kfence 的基本原理非常简单,它创建了自己的专有检测内存池 kfence_pool
。在 data page
的两边加上了 fence page
电子栅栏,利用 MMU 的特性把 fence page
设置成不可访问。如果对 data page
的访问越过了 page 边界, 就会立刻触发异常。
Kfence 的主要特点如下:
item | Kfence | Kasan |
---|---|---|
检测密度 | 抽样法,默认每 100ms 提供一个可检测的内存 | 对所有内存访问进行检测 |
检测粒度 | 核心的检测粒度为 page | 检测粒度为字节 |
Kernel Electric-Fence (KFENCE)是5.12版本内核新引入的内存使用错误检测机制。它可以检查的错误有:
- 内存访问越界
- 释放后使用
- 无效释放
显然,它可以检测的内存错误类型不如KASAN多。但与KASAN相比,它最大的优势是运行时小Overhead,可以直接用在生产环境中。因此在X86,ARM64,RISCV等平台上均默认开启。
在Arch对应的defconfig中使用CONFIG_HAVE_ARCH_KFENCE开启。
架构及原理
Kfence的原理比较简单,如下图:
初始化
- 初始化过程中,KFENCE向Memblock申请一段内存,作为KFENCE内存池。
- 这个内存池的大小配置为CONFIG_KFENCE_NUM_OBJECTS
- 即,预留两个页面作为保护页(Guard Page),接着为每一个用于分配的内存页分配一个Guard Page。因此总大小为:
1
#define KFENCE_POOL_SIZE ((CONFIG_KFENCE_NUM_OBJECTS + 1) * 2 * PAGE_SIZE)
- 初始化一个Delayed Worker,定期(CONFIG_KFENCE_SAMPLE_INTEVAL)重置kfence_alloc_gate值为0。
这个值可以通过sysfs修改
分配
- kfence_alloc_gate值为0时,使用kmem_cache_alloc所作的内存分配从KFENCE内存池中分配,并增加kfence_alloc_gate的值。kfence_alloc_gate值大于等于1时,直接从SLUB中分配。由此可以看出,kfence是基于采样的内存检测。
大于一个Page(4K)的分配不会从KFENCE Pool中分配
- 每次通过KFENCE进行内存分配时,都会从KFENCE内存池分配一个内存页和一个Guard Page,并在实际使用内存的两端内存填充Canary数据。
解释一下为什么保护数据叫Canary。这是因为在19世纪,金丝雀在采矿业中常用的毒气检测方法,因为它们比人类对毒气更为敏感反应也更快。
- 如果KFENCE内存池中没有可用内存,则直接从SLAB中分配。
释放
- 释放时,检查Canary数据,将所用内存放回KFENCE内存池。
检测报错
在以下情况,会检测报错:
- 释放时发现Canary数据不对。
- 当KFENCE内存池的内存区域发生Page Fault时,它或者是因为越界访问、或者是释放后使用。
- 无效释放:当一段KFENCE内存没有被标记分配,但对齐释放时,会有相应报错提示。
总结
开源社区总能带来新的idea。KFENCE,克服了KASAN等工具需要占用大量内存且影响运行时性能的缺点,是一个有效地运行时内存访问错误检测工具。
当然,因为它所针对的内存区域仅仅是KFENCE内存池,且其是周期性进行采样,检测效果还不得而知。其又有可以动态开关、参数可调节等优点,这些劣势或许也不是问题。后续若有时间可以研究分析对比其和KASAN的检测效果。