Linux内存异常检测工具—kfence

一、功能介绍

Linux 5.13内核新增一种内存异常检测工具——kfence(Kernel Electric-Fence),它是基于采样的低开销内存检测工具,这里所谓的采样就是提供少量的内存陷阱,来抓取非法内存访问。

对比kasan:

  • 败在属于概率检测,不是所有的异常访问都能抓取。
  • 胜在对系统的开销很小,可以直接在生产环境中使用。

二、使用说明

1. 开启kfence

  • 内核功能宏
    • HAVE_ARCH_KFENCE
    • KFENCE:kfence总的开关
      • 依赖于HAVE_ARCH_KFENCE
    • KFENCE_STATIC_KEYS
    • KFENCE_SAMPLE_INTERVAL:默认采样间隔,是指多久之后,guard page可以回收吗?
    • KFENCE_NUM_OBJECTS:kfence obj的个数,kfence obj主要用于guard page
    • KFENCE_STRESS_TEST_FAULTS
  • 起机内核参数
    • kfence.sample_interval:当值为0时,禁用kfence;当值大于0时,启动kfence

2. 检测结果

三、实现分析

kfence导入的patch集:

  • 框架:https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?h=v5.13&id=0ce20dd840897b12ae70869c69f1ba34d6d16965
  • x86平台:https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?h=v5.13&id=1dc0da6e9ec0f8d735756374697912cd50f402cf
  • arm64平台:https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?h=v5.13&id=840b239863449f27bf7522deb81e6746fbfbfeaf
  • slub对接:https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?h=v5.13&id=b89fb5ef0ce611b5db8eb9d3a5a7fcaab2cbe9e4
  • 异常堆栈打印优化:https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?h=v5.13&id=d438fabce7860df3cb9337776be6f90b59ced8ed
  • 测试代码:https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?h=v5.13&id=bc8fbc5f305aecf63423da91e5faf4c0ce40bf38
  • 文档说明:https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?h=v5.13&id=10efe55f883f2396a0024891ad1d7d5d040364b3

1. 工作原理

  • kfence实际是个特殊内存池:

    • 每个kfence object由2部分组成,因此,
      • 真正object,当前固定为PAGE_SIZE大小,当释放状态,页设置为不可访问
      • Guard Page,将该页设置为不可访问
    • kfence object池是由开机申请连续页表,大小为CONFIG_KFENCE_NUM_OBJECTS
      • 默认大小为255个
      • 实际占用内存大小为:(#kfence object + 1) * 2 * PAGE_SIZE
    • 当申请小于page大小的尺寸时,会优先从kfence pool申请,直到kfence pool用完为止
    • 最终的效果就是每个真正object两边都有一个不可访问的内存页,来实现对越界的检测
    image.png
  • 周期采样:

    • 因为kfence object个数有限,当前实现采用定时采样的方式
    • 这里所谓的采样就是每间隔kfence.sample_interval,允许进行分配一个kfence object进行检测
  • redzone:

    • 若实际申请大小小于PAGE_SIZE,那意味着内存页实际是有部分是未分配的。通过将内存页未分配部分填充为redzone,来实现单个页表里的改写
    • 在申请内存时,根据meta->addr和meta->size,将未分配的部分填充为KFENCE_CANARY_PATTERN
    • 在释放内存时,检测未分配部分的内容是否为KFENCE_CANARY_PATTERN,不是则报错
    image.png

2. 数据结构

  • struct kfence_metadata kfence_metadata[CONFIG_KFENCE_NUM_OBJECTS]:kfence object的维护数据结构
    • 采用下标的方式实现metadata与kfence内存页表间的映射
    • 每个metadata指向2个连续的内存页
  • static struct list_head kfence_freelist = LIST_HEAD_INIT(kfence_freelist):空闲的metadata节点链表
  • static struct delayed_work kfence_timer:

3. 函数实现

起机初始化:

  • void __init kfence_alloc_pool(void):初始化,申请kfence obj pool
    • __kfence_pool = memblock_alloc(KFENCE_POOL_SIZE, PAGE_SIZE);
  • void __init kfence_init(void):初始化,设置sampling timer等,必须在kfence_alloc_pool()调用
    • arch_kfence_init_pool():

申请内存:

  • kfence_alloc():
    • __kfence_alloc():
      • 申请的大小超过PAGE_SIZE,直接返回NULL,不保护了?
      • kfence_guarded_alloc():

内存页属性:

  • static bool kfence_protect(unsigned long addr):设置为不可访问
    • kfence_protect_page(addr, bool)
  • static bool kfence_unprotect(unsigned long addr):设置为可以访问
    • kfence_protect_page(addr, bool)

设置redzone:

  • static inline bool set_canary_byte(u8 *addr)
  • static inline bool check_canary_byte(u8 *addr)
  • static __always_inline void for_each_canary(const struct kfence_metadata meta, bool (fn)(u8 *))

定时采样

  • static void toggle_allocation_gate(struct work_struct *work)
  • schedule_delayed_work(&kfence_timer, msecs_to_jiffies(kfence_sample_interval));

四、参考资料

你可能感兴趣的:(Linux内存异常检测工具—kfence)