• 简单讲讲 KASAN、KCSAN 的核心原理,代码实现细节啥的以后再说
  • Linux Kernel Sanitizers 其实还有 UBSAN(针对未定义行为)、KFENCE(精度换速度的 KASAN)、KMSAN(针对未初始化内存),不过暂时还没进一步了解

KASAN

先顺便写写最常用的 KASAN

KASAN 实际上是用影子内存的方式检测越界读写,所谓影子内存就是在内存的某些地方插入一些元信息标记(比如 canary,还有广义上来说汉明码也有异曲同工之妙),KASAN 会消耗八分之一的额外内存,其实还算是可接受的

KASAN 在每八个字节插入一个字节的标记,若此八字节可读写则为 0,不可读写为负数,前 n 字节可读写则为 n

影子内存的实现细节涉及很多 Linux 内核内存分配的东西(不懂),可以看这篇文章:KASAN实现原理 (wowotech.net)

KCSAN

KCSAN 是用于检测内核竞态条件漏洞的 Sanitizer

较新版本的 kernel 已经对 KCSAN 提供了支持,编译时带上CONFIG_KCSAN=y就能使用 KCSAN,不过要求使用 gcc 或 LLVM 的较新版本

它使用依赖编译工具链(gcc 或 LLVM)实现插桩软观察点(Soft Watchpoint)来监测数据竞争,观察点使用类似位图的方法将访问类型、大小、地址存储在一个 long int 中,优点是灵活性和可移植性好

每次访问内存时,KCSAN 都会做以下操作

  • 检查是否存在匹配的观察点,存在且两者中有一次写操作则说明有数据竞争
  • 如果没有匹配的观察点则设置一个观察点,然后等待一段随机时间
  • 检查比较等待前后的数据,不匹配则说明出现某种竞争

对于使用了READ_ONCE()WRITE_ONCE()宏(Linux 内核里用来标记(marked)原子操作的宏函数)的读写操作,KCSAN 不会设置观察点,但依然会检查是否有匹配的观察点;KCSAN 还提供了用于标记开发者有意造成的共享内存读写的宏函数data_race(),KCSAN 会忽略掉这部分数据竞争。此外,KCSAN 会自动识别出序列锁等同步机制,不会报告其中的“数据竞争”

  • 上面提到的几个宏需要开发者三思在此情形下是否应该使用,否则会影响到 KCSAN 的检测效果
  • 第三篇参考文章介绍了很多开发者使用 KCSAN 的编码建议和注意事项,我不太懂并发编程就不献丑了

此外,KCSAN 还提供了一些函数,用于主动让 KCSAN 检测 RCU (Read-Copy-Update)读部分指针访问过程中可能会出现的竞态漏洞(在读指针的时候指针值被其他线程覆写,导致函数访问或遍历了其他地址,这种竞争原本 KCSAN 是无法检测出来的)

综上,KCSAN 可以检测出

  • 获取锁失败
  • 其他 CPU 非法访问 CPU 的私有的 per-CPU 变量
  • 非堆区的 UAF
  • 在 RCU(Read-Copy-Update 机制)的读部分的指针泄露

KCSAN 在运行时可能会给出大量重复的报告,可以将KCSAN_REPORT_ONCE_IN_MS宏的值(在 kconfig 中)设置大一些,忽略若干毫秒内的报告来规避大量的重复报告

如果想在 kernel 运行时查看 KCSAN 报告的信息,可以在/sys/kernel/debug/kcsan读取统计数据、打开或关闭 KCSAN、设置黑白名单等;对于大型系统,同样可以在 kconfig 中设置观察点设置粒度、检查时间窗口、中断延迟等性能相关的选项,具体看文档

KCSAN 也有一些局限性:

  • 只能检测在不同 CPU 中几乎同时发生的数据竞争(不过后续的新补丁已经可以在进程级别上检查中断引起的数据竞争)
  • 有时需要手动分析才能确定出问题的子系统(这是无法避免的)
  • KCSAN 对开发者的并发同步设计的了解并不深,难以给出更有意义的报告
  • 只能用于调试环境,在生产环境使用会因为需要跟踪全局资源访问而造成五到十倍的性能损耗
  • 使用采样机制,不能保证每次都捕捉到竞态(忽视性能和损耗的话精度比 KTSAN 差)

其他方法

  • KTSAN 是 KCSAN 前出现的一种方法,采用了与用户态的 TSAN 相似的设计,但由于性能问题仅实现了原型,目前已不再维护
    • KTSAN 的问题在于其会为每 8 字节都提供长 32 字节的最近四次访问的元数据,且为了检测线程销毁后的竞态 KTSAN 会转储这部分影子内存而非释放掉,故而这会带来四倍于现有内存容量的额外内存消耗(相比之下 KCSAN 的内存开销仅为几 MB)
    • 对于 UAF,KTSAN 的做法是在内存释放后用特殊内容填充这部分内存,通过检测这些字节是否改变来判断是否存在 UAF
    • 这样暴力的方法的好处是检测精确度比 KCSAN 更好
  • lockdep 是用于检测 Linux 内核代码中的死锁的工具,通过跟踪各个锁的状态并分析锁之间依赖关系的正确性来检测锁的安全性

Reference