内核内存错误的难处在于:用户态可以靠 Valgrind 跑一遍,但内核既不能容忍 100x 的开销,也不能容忍随机崩溃在生产线上。Linux 6.x 给出了一组互补工具——KASAN 在开发期全覆盖 UAF/OOB,KFENCE 用 guard page 采样把开销压到生产可接受范围,kmemleak 抓沉默泄漏,SLUB_DEBUG 抓对象损坏,UBSAN/KCSAN 补 UB 与 data race。本文按照”开销 / 覆盖率 / 部署位置”三个维度把它们摆清楚。
一、先看图
flowchart TD
BUG[内核内存 bug] --> TYPE{类型}
TYPE -->|UAF/OOB| KASAN[KASAN<br/>shadow memory]
TYPE -->|UAF/OOB 生产| KFENCE[KFENCE<br/>guard page 采样]
TYPE -->|leak| KMEMLEAK[kmemleak<br/>GC 扫描]
TYPE -->|corruption| SLUB[SLUB_DEBUG<br/>redzone/poison]
KASAN --> REPORT[KASAN report<br/>stack trace]
KFENCE --> REPORT2[KFENCE report]
KMEMLEAK --> REPORT3[泄漏列表<br/>alloc stack]
SLUB --> REPORT4[BUG: SLUB<br/>对象损坏]
classDef detect fill:#388bfd22,stroke:#388bfd,color:#adbac7;
classDef report fill:#f0883e22,stroke:#f0883e,color:#adbac7;
class KASAN,KFENCE,KMEMLEAK,SLUB detect
class REPORT,REPORT2,REPORT3,REPORT4 report
二、KASAN
2.1 Generic KASAN
基于 shadow memory:每 8 字节实际内存对应 1 字节 shadow(1/8 开销)。编译器在每次内存访问前插入 shadow 检查。
- UAF(Use-After-Free):释放后 shadow 标记为”freed”
- OOB(Out-Of-Bounds):对象边界外 shadow 标记为”redzone”
启用:CONFIG_KASAN=y CONFIG_KASAN_GENERIC=y
性能开销 ~2-3x CPU + ~1/8 内存。
2.2 SW Tag KASAN(arm64)
利用 TBI(Top Byte Ignore)在指针高 8 位存 tag,内存也标 tag。访问时比较——不匹配 = bug。
开销更低(~1.5-2x)但不如 generic 精确。
2.3 HW Tag KASAN(arm64 MTE)
利用 MTE 硬件做 tag 比较——几乎零 CPU 开销(~3-5%)。
CONFIG_KASAN_HW_TAGS=y,Pixel 8+ 已
dogfood。
2.4 KASAN report
BUG: KASAN: slab-use-after-free in func+0x42/0x80
Read of size 4 at addr ffff888012345678 by task myapp/1234
Allocated by task 1234:
kmem_cache_alloc+0x...
my_alloc_func+0x...
Freed by task 1234:
kfree+0x...
my_free_func+0x...
包含分配和释放的调用栈——直接定位 bug。
三、KFENCE(Kernel Electric Fence)
3.1 思路
KASAN 开销太大不能跑生产。KFENCE
用统计采样:每隔一段时间(默认
sample_interval=100 ms),把下一次 slab
分配重定向到一个独立的 guard page
池。被采样到的对象前后各有一个不可访问页,对象释放后整页继续保持不可访问。
- 越过对象边界 → 触发 guard page 页错误 → KFENCE report。
- 释放后再访问 → 对应页仍然不可访问 → 再次报告 UAF。
3.2 特点
- 开销:稳态 <1% CPU,可跑生产。
- 覆盖率:取决于采样率与对象数,不是每个 bug 都能抓到;KFENCE 是统计学工具,不是覆盖性工具。
- 无需特殊编译器:和 KASAN 不同,不依赖编译时插桩。
启用:CONFIG_KFENCE=y,主流发行版的 6.x
内核默认已开。
cat /sys/kernel/debug/kfence/stats
# enabled: 1
# currently allocated: ...
# total allocations: ...
# sample_interval (ms): 100四、kmemleak
4.1 思路
定期(默认 10 分钟)扫描所有内核内存,查找”无引用”的分配——类似保守式 GC。所谓”无引用”指:在所有被扫描的内核内存区域里都找不到指向该对象的指针。
4.2 启用
CONFIG_DEBUG_KMEMLEAK=y
echo scan > /sys/kernel/debug/kmemleak # 手动扫描
cat /sys/kernel/debug/kmemleak # 查看泄漏
echo clear > /sys/kernel/debug/kmemleak # 清除记录4.3 报告
unreferenced object 0xffff8880... (size 128):
comm "myapp", pid 1234, jiffies 4294967296
backtrace:
kmalloc+0x...
my_driver_probe+0x...
4.4 局限
- 保守式:可能误报(整数恰好看起来像指针,或栈上残值指向被释放对象)。
- 扫描期间会暂停部分内核活动,性能开销不稳定。
- 不适合生产;通常只在开发、CI、长跑测试里开启。
五、SLUB_DEBUG
SLUB_DEBUG 的红区、毒值、freelist 校验在 Slab/SLUB 分配器 已经介绍。这里只把它和其他工具放在一张表里比较:
| 工具 | 检测目标 | 适用阶段 | 典型开销 |
|---|---|---|---|
| KASAN generic | UAF / OOB / 全覆盖 | 开发、CI | 2–3× CPU;~1/8 内存 |
| KASAN HW tag (MTE) | UAF / OOB | 生产(arm64 MTE) | ~3–5% CPU |
| KFENCE | UAF / OOB(采样) | 生产 | <1% CPU |
| SLUB_DEBUG | 对象损坏、double free | 开发、定向排查 | 视选项 2–10× |
| kmemleak | 内存泄漏 | 开发、长跑测试 | 10–20% |
六、UBSAN / KCSAN
6.1 UBSAN
Undefined Behavior Sanitizer 的内核版:检测整数溢出、空指针解引用、对齐错误等 C 语言 UB。
CONFIG_UBSAN=y
CONFIG_UBSAN_TRAP=y # 触发时 BUG() 而非只打日志
6.2 KCSAN
Kernel Concurrency Sanitizer:检测 data race(并发读写同一变量、无 READ_ONCE/WRITE_ONCE/锁等同步标记)。
CONFIG_KCSAN=y
报告示例:
BUG: KCSAN: data-race in func_a / func_b
write to 0xffff... of 4 bytes by task A
read to 0xffff... of 4 bytes by task B
修复:加锁、或标记 data_race() /
READ_ONCE() / WRITE_ONCE()。
七、实践工作流
7.1 开发 / CI
CONFIG_KASAN=y
CONFIG_KFENCE=y
CONFIG_DEBUG_KMEMLEAK=y
CONFIG_UBSAN=y
CONFIG_KCSAN=y
CONFIG_SLUB_DEBUG=y
跑 syzkaller / LTP,收集 dmesg 中的各类 report。CI 阶段以 KASAN + UBSAN 为主,KCSAN 单独跑(开销与误报都更敏感)。
7.2 生产
CONFIG_KFENCE=y # 低开销采样
CONFIG_KASAN_HW_TAGS=y # arm64 MTE 才有意义
其余不开。KFENCE report 通过 dmesg / syslog
收集,结合 crash dump 复盘。
八、常见问题
KASAN 太慢,不能开 generic 模式约 2–3×
CPU 开销,确实只适合开发与 CI。arm64 上若硬件支持
MTE,可以切到 KASAN_HW_TAGS,开销降到
~3–5%。x86 上生产侧只能依赖 KFENCE 采样。
kmemleak 误报多 先
echo clear > /sys/kernel/debug/kmemleak,再触发负载、再
echo scan。把已知误报通过
kmemleak_not_leak()
注释到源码里,避免每次重复。
SLUB_DEBUG 想在生产开但不敢全开 使用
slub_debug=FZP,task_struct 这类参数,只对特定
cache 启用 redzone / poison / store_user,控制开销范围。
九、小结
- KASAN(Generic/SW/HW)是开发期 UAF/OOB 检测的金标
- KFENCE 是生产 UAF/OOB 的低开销采样
- kmemleak 抓泄漏,保守式 GC
- SLUB_DEBUG 检测对象损坏
- UBSAN/KCSAN 覆盖 UB 和 data race
- 组合使用覆盖面最广
参考文献
Documentation/dev-tools/kasan.rstDocumentation/dev-tools/kfence.rstDocumentation/dev-tools/kmemleak.rst- Alexander Potapenko, “KFENCE: A low-overhead sampling-based memory safety error detector.” 2021
- Dmitry Vyukov, “KCSAN: Kernel Concurrency Sanitizer.” 2019
工具
- dmesg(KASAN/KFENCE/SLUB_DEBUG/KCSAN 输出)
/sys/kernel/debug/kmemleak/sys/kernel/debug/kfence/stats- syzkaller
- crash / drgn
上一篇:per-CPU 变量 下一篇:用户态分配器:jemalloc vs tcmalloc
同主题继续阅读
把当前热点继续串成多页阅读,而不是停在单篇消费。
【操作系统百科】Slab/SLUB 分配器
buddy 只管页粒度(4K+),内核大多数对象只有几十到几百字节。slab/SLUB 在 buddy 之上做对象级缓存。本文讲 slab 历史、SLUB 接手、SLOB 退场、kmem_cache、per-CPU cache、KASAN 集成。
【操作系统百科】lockdep 与内核锁验证
lockdep 报的 ABBA 死锁该不该怕?本文讲 lockdep 锁依赖追踪、lockdep_assert_held、KCSAN 并发检查、KFENCE 内存越界检测、false positive 处理。
【操作系统百科】VFS 四层抽象
Linux 的一切皆文件靠 VFS 实现——superblock、inode、dentry、file 四层抽象加 ops 表。本文讲 VFS 核心数据结构、dcache、inode cache、RCU lookup,以及文件系统如何插入 VFS。
操作系统百科
Linux 6.x 视角下的操作系统系列索引:110 篇覆盖调度、虚拟内存、文件系统与 I/O、并发、隔离、可观测性,按主题、阅读路径与关键问题三种入口组织。