0/4 已展开

LLM 分析

sched_ext: Validate cid override before updating tables

系列基线信息

字段
标题[PATCH] sched_ext: Validate cid override before updating tables
作者zhidao su (Xiaomi)
版本单 patch,无版本号
规模1 file, +5 lines
Message-ID20260714024704.3318132-1-soolaugust@gmail.com
来源sched-ext 频道

明确目的

scx_bpf_cid_override() 在遍历用户传入的 cpu_to_cid 映射时,边验证边写入内核查找表。

如果验证在中途失败(例如发现同一个 cid 被多个 CPU 占用),前面已经写入的条目不会被回滚——查找表处于半更新状态,留下不一致的映射。

本 patch 将验证和写入拆分为两遍循环:先全部验证,再全部写入,确保验证失败时查找表保持原样不变。

遍历代码

原始代码逻辑(单遍循环):

for_each_possible_cpu(cpu) {
    s32 c = cpu_to_cid[cpu];
    // 验证 cid 是否合法、是否重复
    if (cid_invalid(c) || duplicate) {
        scx_error(...);
        return;   // ← 此时前面的表项已经写入,无法回滚
    }
    scx_cpu_to_cid_tbl[cpu] = c;     // 写入
    scx_cid_to_cpu_tbl[c] = cpu;     // 写入
}

Patch 改为两遍:

// 第一遍:只验证,不写入
for_each_possible_cpu(cpu) {
    s32 c = cpu_to_cid[cpu];
    if (cid_invalid || duplicate) {
        scx_error(...);
        return;   // ← 表尚未被修改,安全退出
    }
}

// 第二遍:验证全部通过后才写入
for_each_possible_cpu(cpu) {
    s32 c = cpu_to_cid[cpu];
    scx_cpu_to_cid_tbl[cpu] = c;
    scx_cid_to_cpu_tbl[c] = cpu;
}

关键改动是在原验证循环的 } 之后插入一个 } 关闭第一遍循环,再加一个新的 for_each_possible_cpu(cpu) 开启第二遍写入循环——仅 5 行新增。

ASCII 流程图

原始流程(单遍)                Patch 流程(两遍)

  ┌─────────────┐               ┌─────────────┐
  │ 验证 cpu 0  │               │ 验证 cpu 0  │
  │ 写入 cpu 0  │               │ 验证 cpu 1  │
  ├─────────────┤               │ 验证 cpu N  │
  │ 验证 cpu 1  │               ├──────┬──────┤
  │ 写入 cpu 1  │            ok?│  YES │  NO  │
  ├─────────────┤               │      │      │
  │ ...         │               │      ▼      │
  ├──────┬──────┤            ┌──┴──┐┌───────┐ │
  │  ok  │ fail │            │write││return │ │
  │      │  ▼   │            │ all ││(表不变)│ │
  │      │表半  │            └─────┘└───────┘ │
  │      │更新  │               └───────────┘
  └──────┴──────┘

概念类比

想象你在餐厅更新菜单黑板:原来是一边检查新菜品有没有冲突,一边逐个擦掉旧字写新字。如果写到第 5 个菜时发现冲突不得不停下,黑板上前 4 个已经是新菜、后面还是旧菜——顾客看到一份混乱的菜单。

patch 的做法是:先把所有新菜品名单在纸上逐条审查一遍,确认没问题后才一口气擦黑板重写。审查中发现冲突,直接扔掉纸条,黑板还是老菜单,顾客不受影响。

Highlight 突出问题

  1. TOCTOU(Time-of-Check-Time-of-Use)风险:Sashiko AI 指出,cpu_to_cid 是用户传入的指针,拆成两遍循环意味着第一遍验证时读到的值可能与第二遍写入时不同。如果 cpu_to_cid 指向 BPF map 等共享内存,另一个线程或 BPF 程序可以在两遍之间修改内容,导致验证通过的 cid 在写入时变成非法值,甚至越界写入内核内存。这是真实的安全隐患。

  2. Patch 已被上游覆盖:Tejun Heo 回复指出 for-7.3 分支已经实现了相同的验证-写入分离,并且额外使用 kmemdup() 在开头复制一份输入数组,从而彻底消除了 TOCTOU 风险。本 patch 基于旧版 cid.c,已无法应用,无需重投。

  3. 部分更新的实际危害程度:原始代码中验证失败时已写入的表项是否真的会引发调度错误,取决于 scx_error 之后的调度器行为——这是一个值得确认的细节,尽管上游已修复。

版本演进

本系列只有一个版本(v1),且 maintainer 已确认上游 for-7.3 中有更完善的实现,无需 respin。

与其他相关 patch 系列的关联

  • for-7.3 cid.c 重构:Tejun 提到的 for-7.3 分支中的 scx_bpf_cid_override() 实现已包含验证-写入分离 + kmemdup() 防竞态,是本 patch 所修复问题的上游完整解决方案。

一句话总结

本 patch 试图修复 scx_bpf_cid_override() 中验证失败导致查找表半更新的问题,但拆成两遍循环引入了 TOCTOU 风险,且上游 for-7.3 已有更完善的实现(含 kmemdup() 防竞态),因此无需继续推进。