sched discussion
[PATCH v2] sched/debug: Validate writes to the scan_size_mb debugfs knob
LLM 分析
sched/debug:给 scan_size_mb debugfs 旋钮加上写入校验
系列概况
- 标题:
[PATCH v2] sched/debug: Validate writes to the scan_size_mb debugfs knob - 作者:Zhan Xusheng
<zhanxusheng@xiaomi.com> - 版本:v2(单 patch,v1 → v2 仅改解析进制与 changelog 用语)
- 规模:1 个 patch,1 个文件,50 行新增 / 1 行删除
- 修改文件:
kernel/sched/debug.c - 代码统计:
+50 / -1 - Message-ID:
20260810142050.3828587-1-zhanxusheng@xiaomi.com - 完整性:含 Fixes tag、Signed-off-by、Reviewed-by、v2 changelog、Link to v1,无缺项
补丁目的
scan_size_mb 是 NUMA balancing 在 debugfs 下暴露的调节旋钮,写入值会作为除数被 task_scan_min() 和 task_nr_scan_windows() 使用。两个特定数值会让内核在普通 clone() / fork() 路径上 Oops:
- 写入
0:MAX_SCAN_WINDOW / 0直接除零。 - 写入
2^24(4K 页):MB_TO_PAGES()对unsigned int左移 24 位回绕成0,再次除零。
把旋钮从原先 sysctl 迁到 debugfs 时丢了 .extra1 = SYSCTL_ONE 下界校验,上界也从未存在。该 patch 用自定义 write handler 替换裸的 debugfs_create_u32(),写入时校验 0 < mb <= NUMA_SCAN_SIZE_MB_MAX,越界返回 -EINVAL。
旧流程的问题
旧实现一行 debugfs_create_u32() 直接把 sysctl_numa_balancing_scan_size 暴露给用户态,无任何边界检查:
echo 0 > scan_size_mb
|
v
sysctl_numa_balancing_scan_size = 0
|
v
fork/clone -> task_scan_min()
|
v
MAX_SCAN_WINDOW / 0
|
v
divide error -> kernel Oops
echo 16777216 > scan_size_mb (4K page)
|
v
MB_TO_PAGES(mb) : mb << (20 - PAGE_SHIFT)
|
v
unsigned int shift wraps to 0
|
v
task_nr_scan_windows() / 0
|
v
divide error -> kernel Oops
两条路径都从 init_numa_balancing() 经 __sched_fork() / copy_process() 进入,普通 clone() 即可触发。
新流程
echo <value> > scan_size_mb
|
v
sched_numa_scan_size_write()
|
v
kstrtouint_from_user(buf, cnt, 0, &mb)
| (base=0 -> decimal or hex)
v
+--------------------------------------------+
| mb == 0 -> return -EINVAL (lower bound)
| mb > NUMA_SCAN_SIZE_MB_MAX -> return -EINVAL (upper bound)
| else: store to global and return cnt
+--------------------------------------------+
|
v
sysctl_numa_balancing_scan_size = mb (safe value)
NUMA_SCAN_SIZE_MB_MAX = UINT_MAX >> (20 - PAGE_SHIFT) 取「左移后仍非零」的最大 MB 数,从源头封住 MB_TO_PAGES() 的回绕窗口。
Patch 概览
- 在
#ifdef CONFIG_NUMA_BALANCING内新增sched_numa_scan_size_write/_show/_open与sched_numa_scan_size_fops。 - 写 handler 使用
kstrtouint_from_user(ubuf, cnt, 0, &mb)解析;base=0保留simple_attr_write_xsigned()原先允许的十六进制写入(v2 由 Chen Yu 指出后调整)。 - 校验通过后写回
sysctl_numa_balancing_scan_size,*ppos += cnt; return cnt;与 simple_attr 约定一致。 - 注册节点从
debugfs_create_u32("scan_size_mb", 0644, numa, &sysctl_numa_balancing_scan_size)改为debugfs_create_file("scan_size_mb", 0644, numa, NULL, &sched_numa_scan_size_fops),仅此一行。 - 旋钮已在
Documentation/scheduler/sched-debug.rst中有文档,注释强调「-EINVAL比悄悄存入下次 clone 即 panic 的值更安全」。
关键实现
#define NUMA_SCAN_SIZE_MB_MAX (UINT_MAX >> (20 - PAGE_SHIFT))
static ssize_t sched_numa_scan_size_write(struct file *filp,
const char __user *ubuf,
size_t cnt, loff_t *ppos)
{
unsigned int mb;
int ret;
ret = kstrtouint_from_user(ubuf, cnt, 0, &mb);
if (ret)
return ret;
if (!mb || mb > NUMA_SCAN_SIZE_MB_MAX)
return -EINVAL;
sysctl_numa_balancing_scan_size = mb;
*ppos += cnt;
return cnt;
}
static int sched_numa_scan_size_show(struct seq_file *m, void *v)
{
seq_printf(m, "%u\n", sysctl_numa_balancing_scan_size);
return 0;
}
static int sched_numa_scan_size_open(struct inode *inode, struct file *filp)
{
return single_open(filp, sched_numa_scan_size_show, NULL);
}
static const struct file_operations sched_numa_scan_size_fops = {
.open = sched_numa_scan_size_open,
.write = sched_numa_scan_size_write,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
要点:
!mb拦截下界,对应task_scan_min()的除零。mb > NUMA_SCAN_SIZE_MB_MAX拦截上界,对应MB_TO_PAGES()的 32 位回绕。base=0让kstrtouint_from_user同时识别"0x10"与"16",与旧 simple_attr 行为兼容。*ppos += cnt; return cnt;是 simple_attr 的标准约定,避免llseek后短写 / 重写状态错乱。
类比
把 scan_size_mb 想象成自助餐厅入口的「每人取餐份数」告示牌。旧版把粉笔直接交给顾客,写 0(「谁也不许取」)或「天文数字」(超过厨房容量)时,后厨阿姨(task_scan_min)要算「总量 ÷ 份数」,分母为 0 或绕回 0 都会当场崩溃。新版则在牌子前装了电子闸机:写 0、超大数都直接红灯拒绝放行,只有合理区间才更新到总规则里,下一位顾客来打饭才安全。
Highlight:风险与注意点
- debugfs 节点权限偏宽:
scan_size_mb默认0644,具备挂载点写权限的用户即可让系统 Oops;发行版应评估是否收紧为root-only或要求CAP_SYS_ADMIN。 - 上界依赖编译时常量:
NUMA_SCAN_SIZE_MB_MAX假设MB_TO_PAGES()仍是 32 位位移;若未来改为 64 位或换公式,上界需重新推导。 - 同文件其它旋钮未加固:
scan_delay_ms / scan_period_min_ms / scan_period_max_ms / hot_threshold_ms仍是裸debugfs_create_u32,其中含除法或饱和语义的字段理论上存在同类风险,建议按相同模式补writehandler。 - stable backport 一致性:
Fixes指向8a99b6833c88,回 stable 时需与整个「SCHED_DEBUG sysctl 迁 debugfs」系列同步评估,避免在 stable 分支上重新暴露旧 sysctl 节点。 - 回归覆盖有限:作者仅在 2 节点 qemu 客机上验证 0、
2^24、3*2^24、512;社区宜追加LTPdebugfs模糊测试,覆盖UINT_MAX、2^24-1、2^24+1等边界。
版本变化
- v1 → v2:
kstrtouint_from_user的base由10改为0,让十六进制写入继续生效,匹配simple_attr_write_xsigned()中kstrtoull(attr->set_buf, 0, &val)的行为(Chen Yu 指出);changelog 中精确点出了对应 helper 名称。
一句话总结
把 NUMA balancing 的 debugfs 旋钮 scan_size_mb 从裸 debugfs_create_u32 换成带 write handler 的 file,并在写入时校验 0 < mb <= UINT_MAX >> (20 - PAGE_SHIFT),封住 task_scan_min 与 task_nr_scan_windows 两处除零 panic。