sched discussion
[BUG] KASAN: slab-use-after-free Read in ntfs_fileattr_get
LLM 分析
ntfs3 KASAN slab-UAF 的 syzkaller 复现调试 patch
系列概况
- 标题: [BUG] KASAN: slab-use-after-free Read in ntfs_fileattr_get
- 作者: Jaeyoung Chung
- 版本: 无(单封邮件,非系列)
- 规模: 1 封邮件;patch 修改 2 个文件,4 处 hunk
- 修改文件:
fs/ntfs3/super.c、kernel/sched/membarrier.c - 代码统计: 全部为新增
static计数变量与基于current->comm前缀匹配的调试分支,无既有逻辑改动 - Message-ID: 20260824151736.2243590-1-jjy600901@snu.ac.kr
- 完整性: 邮件正文在 KASAN slab object 转储片段(
ffff88810b7ad380: fa fb fb fb fc fc fc fc fa fb fb fb fc fc fc fc)之后被截断,仅看到开头一句 "Hello, We found a "KASAN: slab-use-after-free Read in ntfs_fi...";repro 脚本、syzrepro0/syzrepro8协同方式、完整调用栈均缺失
补丁目的
作者在 NTFS3 中复现到一条 KASAN slab-use-after-free Read,栈底指向 ntfs_fileattr_get。这份 patch 并不是直接修复,而是在 ntfs3 的 reconfigure/free 路径与 membarrier initcall 路径里插入 syzkaller 复现用调试桩:用 comm 名前缀区分复现器进程,对匹配进程做计数 + 阈值扩张,并在 membarrier 初始化尾部插入 mdelay(1),把一闪而过的竞争窗口人为放大,方便后续稳定复现与 bisect。
旧流程的问题
原始代码在这三处关键点没有观测手段:UAF 触发极不稳定,syzkaller 随机命中一次之后再次复现需要等待长时间的 race 窗口,CI / 本地 bisect 几乎不可行。
新流程
通过添加基于 current->comm 前缀匹配的 if 分支,仅让 syzrepro0 / syzrepro8 进程在关键路径上进入计数 + 延迟逻辑;正常用户进程不受影响,而复现器进程每次都会在固定点停留并留下计数指纹,便于把竞争窗口稳定下来。
关键实现
/* fs/ntfs3/super.c -- 新增静态计数器(放在 ntfs_fs_parse_param 之后) */
static unsigned long syz_rg_swaps;
static unsigned long syz_rg_swap_next = 1;
static unsigned long syz_rg_frees;
static unsigned long syz_rg_free_next = 1;
/* ntfs_fs_reconfigure 开头插入 */
if (!strncmp(current->comm, "syzrepro8", 10)) {
syz_rg_swaps++;
if (syz_rg_swaps == syz_rg_swap_next)
syz_rg_swap_next = syz_rg_swaps + 400;
}
/* ntfs_fs_free 开头插入 */
if (!strncmp(current->comm, "syzrepro8", 10)) {
syz_rg_frees++;
if (syz_rg_frees == syz_rg_free_next)
syz_rg_free_next = syz_rg_frees + 400;
}
/* kernel/sched/membarrier.c */
#include <linux/delay.h>
static unsigned long syz_rg_stalls;
static unsigned long syz_rg_stall_next = 1;
/* membarrier_init 末尾(initcall 路径) */
if (!strncmp(current->comm, "syzrepro0", 10)) {
syz_rg_stalls++;
if (syz_rg_stalls == syz_rg_stall_next)
syz_rg_stall_next = syz_rg_stalls + 5000;
mdelay(1); /* busy-wait 1ms,仅复现器进程触发 */
}
阈值扩张(swap_next = +400、free_next = +400、stall_next = +5000)保证计数只在罕见里程碑处触发自检,避免每次进入都打印日志;mdelay(1) 是同步忙等 1ms,专为把 membarrier init 与后续 ntfs3 操作之间拉开可观察的时序间隔。
syzkaller reproducers
+-------------------+
| syzrepro0 / |
| syzrepro8 |
+---------+---------+
|
| comm-prefix match
| (strncmp == syzrepro0/8)
v
+---------------+---------------+
| | |
v v v
membarrier_init ntfs_fs_reconfigure ntfs_fs_free
+-------------+ +----------------+ +-------------+
| syz_rg_stall| | syz_rg_swap | | syz_rg_free |
| + mdelay(1) | | threshold +400 | | thresh +400 |
+------+------+ +-------+--------+ +------+------+
| | |
+----------------+-------------------+
|
v
+-------+-------+
| KASAN report |
| UAF in |
| ntfs_fileattr_|
| get |
+---------------+
类比
这就像在怀疑漏水的管道里塞了几只电子流量计和一只慢速阀:在容易引起水锤的几个开关上加装 "每经过一次就记一次、并在关键节点停 1ms 留出观察时间" 的装置,让原本一闪而过的泄漏慢放到肉眼可辨。mdelay(1) 相当于在两个阀门之间人为加一段 1ms 的停顿,让水锤的波形可以被压力传感器捕获;comm 前缀匹配则像给流量计贴标签,只有标记过的实验水龙头(syzrepro 进程)会触发,正常住户用水不受影响。
Highlight:风险与注意点
mdelay(1)是 busy-wait:membarrier_init是 boot 期 initcall,对所有依赖该 initcall 顺序的子系统都会额外耗掉 1ms x N 次;这种代码绝不应进入主线,仅适用于 syzkaller 调试构建。- comm 名匹配耦合到 syzkaller 内部命名约定:
"syzrepro0"、"syzrepro8"是 syzkaller 复现器生成的固定 comm;如果 syzkaller 升级改了命名约定,patch 会瞬间失效。 - 这不是真正的修复 patch:UAF 根因仍未定位,需要后续用完整 KASAN stack(释放点 + 读取点的 backtrace)、
kmemleak与slab_debug=UF一起追到真正的释放路径。 - 邮件正文被截断:KASAN 完整调用栈、syzrepro 脚本、
syzrepro0与syzrepro8的协同方式都未给出,maintainer 难以独立复现。 - 混入生产路径的味道:
fs/ntfs3/super.c与kernel/sched/membarrier.c都是热路径,把调试桩直接 commit 而不挂CONFIG_DEBUG_*开关,代码评审应当拒绝。
一句话总结
这是一份针对 NTFS3 ntfs_fileattr_get slab-UAF 的 syzkaller 复现调试 patch:在 ntfs3 reconfigure/free 与 membarrier init 中按 comm 名前缀匹配插入计数与 mdelay(1),仅用于放大竞争窗口并稳定复现,本身并不是真正的修复。