0/3 已展开

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-ID20260723082144.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_sizerss / 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。