0/4 已展开

LLM 分析

sched_ext:销毁 DSQ 后 dru 触发 BUG_ON 的修复

系列概况

  • 标题:[PATCH v2] sched_ext: Don't BUG_ON a destroyed DSQ in process_deferred_reenq_users
  • 作者:Tao Cui <cuitao@kylinos.cn>
  • 版本:v2(v1 跳过所有 builtin 标记 id,v2 改为只跳过 SCX_DSQ_INVALID,保留 builtin 的 BUG_ON)
  • 规模:单 patch,1 文件,+4 行;Tejun 应用时再加 READ_ONCE 与变量提升
  • 修改文件kernel/sched/ext/ext.c,函数 process_deferred_reenq_users()
  • 代码统计:v2 = +4/-0;Tejun 落地版 = +5/-1
  • Message-ID20260815022017.3305427-1-cui.tao@linux.dev
  • 完整性:包含 diff、commit message、Fixes:、Signed-off-by、Cc: stable@,并附 v1→v2 变更说明

补丁目的

scx_bpf_dsq_reenq() 排队的"延迟重入"(dru) 由 run_deferred()ops.dispatch() 之外异步执行。如果 DSQ 在 dru 跑之前就被 destroy_dsq() 销毁,process_deferred_reenq_users() 看到 dsq->id == SCX_DSQ_INVALID,立刻撞上 BUG_ONdestroy_dsq() 并不会 flush 已经排队但尚未执行的 dru,因此要在处理路径上容忍这种竞态:识别到 ID 已失效就跳过。

旧流程的问题

  1. destroy_dsq()dsq->id 标记为 SCX_DSQ_INVALID
  2. 已排队的 dru 在 process_deferred_reenq_users() 循环里仍然被解引用。
  3. 走到 BUG_ON(dsq->id & SCX_DSQ_FLAG_BUILTIN) 时虽然 INVALID 没有 builtin 位,但随后调用 reenq_user() 会把已销毁对象上的任务重新塞回 runqueue。
  4. 双重后果:要么立刻 BUG_ON 自杀,要么在后续访问中触发 UAF。

新流程

在 BUG_ON 之前插入 ID 有效性检查,遇到 SCX_DSQ_INVALID 直接 continue 跳过。Tejun 应用版进一步把 dsq->id 的两次读合并为一次 READ_ONCE,并在比较后用同一变量做 BUG_ON,关闭 TOCTOU 窗口。

Patch 概览

Tao Cui v2:

/* destroy_dsq() may have raced and invalidated @dsq, nothing to reenq */
if (unlikely(dsq->id == SCX_DSQ_INVALID))
    continue;

Tejun 落地版(applied to sched_ext/for-7.3):

u64 dsq_id, reenq_flags;
...
dsq_id = READ_ONCE(dsq->id);
if (unlikely(dsq_id == SCX_DSQ_INVALID))
    continue;

BUG_ON(dsq_id & SCX_DSQ_FLAG_BUILTIN);

关键实现

  • 旧版 dsq->id 在 if 判断和 BUG_ON 各读一次,构成无锁的 TOCTOU:destroy_dsq() 可能在两次读之间把 id 清掉并触发 BUG_ON。READ_ONCE 强制编译器只读一次。
  • 配合循环前已有的 smp_mb(),与 schedule_dsq_reenq() 端的发布语义对齐。
  • 仅跳过 INVALID 而非所有 builtin,是 v1→v2 的关键收紧,避免掩盖真正的 builtin 误用。
  • Cc: stable@vger.kernel.org # v7.1+ 提示此修复需要回灌到 7.1 起的 stable 分支。
  scx_bpf_dsq_reenq()           destroy_dsq()
        |                            |
        v                            v
  enqueue dru on rq          dsq->id = SCX_DSQ_INVALID
        |                            |
        +----------- race -----------+
                       |
                       v
          process_deferred_reenq_users(rq)
                       |
             dsq_id = READ_ONCE(dsq->id)
                       |
        +--------------+--------------+
        |                             |
   INVALID id                    valid id
        |                             |
   continue;                  BUG_ON(builtin?)
   skip safely                 reenq_user(rq, dsq, ...)

类比

把它想成外卖调度:餐厅(DSQ)已经打烊,但订单系统里还有一份"晚班订单"(dru)在队列里等派送。旧代码要求订单上的"营业状态戳"必须是合法 builtin,否则直接报警崩溃。新代码改成:派送员出门前先看一眼戳,发现打烊就直接丢掉这份订单,不再强送;Tejun 又把"看一眼"做成一次读,防止两次核实之间餐厅临时关门又撞上旧逻辑。

Highlight:风险与注意点

  • UAF 风险仍未根治:Sashiko 标注 dsq 指针本身在 RCU 宽限期内可能被释放,只判 ID 不足以保护解引用;后续需确认 process_deferred_reenq_users() 是否在 RCU 读侧临界区执行,或考虑 rcu_dereference()
  • TOCTOU 已被 READ_ONCE 关闭:但代码风格上应避免函数体内多处复用同一个 id 字段;Tejun 的 dsq_id 局部变量值得作为模板。
  • builtin 误用未变:此 patch 不解决 builtin DSQ 被错误传入 reenq_user 的场景,仍会 BUG_ON;属于另一类问题,需要在调用点或 BPF API 层拦截。
  • 回灌范围:Cc stable v7.1+;发行版若启用 sched_ext 应尽快跟进。
  • 后续观察点:若 Sashiko 的 UAF 报告属实,可能需要 V3 来同时修复 id 失效和 dsq 释放顺序。

版本变化

v1 → v2:v1 在 dru 处理循环里直接对所有 SCX_DSQ_FLAG_BUILTIN 标记的 id continue 跳过;v2 收到 Tejun 反馈后只跳过 SCX_DSQ_INVALID,builtin 误用仍由 BUG_ON 兜底。
Tejun 应用:在 v2 之上加 READ_ONCE 合并读取、局部变量 dsq_id、并把 Cc: stable@ 写进 commit body。

一句话总结

通过在 process_deferred_reenq_users() 中跳过已失效的 DSQ id、并用 READ_ONCE 合并读,修掉 destroy_dsq 与 dru 重入导致的 BUG_ON 崩溃,已合入 sched_ext/for-7.3 并回灌 stable。