sched-ext discussion
[PATCH v2] sched_ext: Don't BUG_ON a destroyed DSQ in process_deferred_reenq_users
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-ID:
20260815022017.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_ON。destroy_dsq() 并不会 flush 已经排队但尚未执行的 dru,因此要在处理路径上容忍这种竞态:识别到 ID 已失效就跳过。
旧流程的问题
destroy_dsq()把dsq->id标记为SCX_DSQ_INVALID。- 已排队的 dru 在
process_deferred_reenq_users()循环里仍然被解引用。 - 走到
BUG_ON(dsq->id & SCX_DSQ_FLAG_BUILTIN)时虽然 INVALID 没有 builtin 位,但随后调用reenq_user()会把已销毁对象上的任务重新塞回 runqueue。 - 双重后果:要么立刻
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。