sched discussion
[PATCH v2] sched/debug: reject invalid writes to numa_balancing scan_size_mb
LLM 分析
sched/debug:拒绝向 numa_balancing scan_size_mb 写入非法值
系列概况
- 标题:[PATCH v2] sched/debug: reject invalid writes to numa_balancing scan_size_mb
- 作者:Li RongQing lirongqing@baidu.com
- 版本:v2
- 规模:1 个文件,22 行新增 / 1 行删除
- 修改文件:
kernel/sched/debug.c - 代码统计:
+22 / -1 - Message-ID:
20260723082144.2190-1-lirongqing@baidu.com - 完整性:v2 提交正文与 diff 完整;后两封为同作者的 ping 维持邮件,没有任何新评审意见
补丁目的
把 numa_balancing/scan_size_mb 这个 debugfs 文件从通用 debugfs_create_u32 替换成自定义 file_operations,在写入阶段拦下 0 与超过 UINT_MAX 的值。
一旦 sysctl_numa_balancing_scan_size = 0,下游 task_scan_min() 与 task_nr_scan_windows() 中的 MAX_SCAN_WINDOW / scan_size、rss / nr_scan_pages 都会触发除零;超过 4G 的输入会被 u32 写入路径悄悄截断,结果不可预期。补丁在 setter 边界提前返回 -ERANGE,把入口看紧。
旧流程的问题
+-------------------------------+
| user writes scan_size_mb |
+-------------------------------+
|
v
+-------------------------------+
| debugfs_create_u32 generic |
| set handler (no range check) |
+-------------------------------+
|
v
+-------------------------------+
| *(u32 *)data = val |
+-------------------------------+
|
v
+-------------------------------+
| task_scan_min / |
| task_nr_scan_windows divide |
+-------------------------------+
|
+----------+----------+
| |
v v
val == 0 val > UINT_MAX
divide-by-zero silently truncated
新流程
+-------------------------------+
| user writes scan_size_mb |
+-------------------------------+
|
v
+-------------------------------+
| numa_scan_size_set(data, val) |
+-------------------------------+
|
+----------+-----------+-----------+
| (val==0) | (val>UINT_MAX) | else |
v v v
return return *(u32*)data
-ERANGE -ERANGE = (u32)val
|
v
return 0
|
v
downstream divide sees scan_size >= 1
Patch 概览
numa_scan_size_get:读取时直接把*(u32 *)data拷贝到u64 *val,保持%llu的输出格式。numa_scan_size_set:唯一一处业务校验,挡0与> UINT_MAX,返回-ERANGE。DEFINE_DEBUGFS_ATTRIBUTE:把上述两个 fop 与format字符串组装成标准 debugfs attribute。- 注册位置:把
debugfs_create_u32("scan_size_mb", ...)换成debugfs_create_file_unsafe("scan_size_mb", ...),其它scan_delay_ms/scan_period_min_ms/scan_period_max_ms/hot_threshold_ms维持原样。
关键实现
#ifdef CONFIG_NUMA_BALANCING
static int numa_scan_size_get(void *data, u64 *val)
{
*val = *(u32 *)data;
return 0;
}
static int numa_scan_size_set(void *data, u64 val)
{
if (val == 0 || val > UINT_MAX)
return -ERANGE;
*(u32 *)data = (u32)val;
return 0;
}
DEFINE_DEBUGFS_ATTRIBUTE(numa_scan_size_fops,
numa_scan_size_get,
numa_scan_size_set,
"%llu\n");
#endif /* CONFIG_NUMA_BALANCING */
注册处由 debugfs_create_u32(...) 改为:
debugfs_create_file_unsafe("scan_size_mb", 0644, numa,
&sysctl_numa_balancing_scan_size, &numa_scan_size_fops);
由于 DEFINE_DEBUGFS_ATTRIBUTE() 已经通过内嵌的 debugfs_file_get/put 提供防节点被提前删除的引用计数,这里用 debugfs_create_file_unsafe 跳过 full_proxy,省掉一层无谓的间接调用。
类比
- 把
scan_size_mb想象成水龙头:旧版本只要拧就出水,水压为零时下游水泵空转烧毁;新版本在水龙头内部加了一个"最小流量"保护,水压低于阈值就拒绝开阀,水泵始终收到有效水量。 - 又像火车票二维码闸机:旧闸机只读票根字符串,遇 0 长度票根也照样尝试扫描;新闸机会先看票是否有效(包括长度上限),无效票立刻退回并提示错误。
- 把它想成酒店前台登记:旧前台不问人数就给钥匙,0 人房间照样发卡;新前台先核对人数,0 人或超过最大容纳人数一律拒绝办理。
Highlight:风险与注意点
- 下限只防
0,不防过小的合法值。用户在合法区间内写入1也只是让rss / nr_scan_pages取整到 0,把任务永远放进最弱扫描窗口——这是功能降级而非崩溃,但仍值得后续加入scan_size业务下限。 debugfs_create_file_unsafe走裸 file 而非 proxy,保护完全依赖DEFINE_DEBUGFS_ATTRIBUTE中嵌入的debugfs_file_get/put。将来若有人手动改写成裸fops,防移除语义会一并丢失,需要重新审视。- 修的是
8a99b6833c88把 sysctl 移到 debugfs 时引入的回归面。在它之前的/proc/sys/kernel/sched_numa_balancing_scan_size同样接受 0,未来若回退要一并处理。 - 后两封
答复是同作者的 ping 邮件,仅包含引用 patch 文本,没有新审阅意见,关注 maintainer 是否会接手。
版本变化
- v1 -> v2:把
debugfs_create_file改为debugfs_create_file_unsafe以省去full_proxy;commit message 重写以更清楚地交代 UINT_MAX 截断问题与为何可以unsafe。
一句话总结
通过 debugfs 自定义 setter 把 numa_balancing/scan_size_mb 的非法值挡在写入阶段,让 task_scan_min / task_nr_scan_windows 的除法再也不碰到 0。