sched-ext discussion
[PATCH] sched_ext: Fix deadlock with PSI trigger creation
LLM 分析
sched/psi:解除 psimon fork 死锁并封堵 rtpoll_timer UAF
系列概况
- 标题:
[PATCHSET v2] sched/psi: Fix psimon fork deadlock and rtpoll_timer UAF - 作者:Tejun Heo
- 版本:v2(取代 URL 中 Matt Fleming 的 sched_ext 单补丁及 Tejun 的 v1)
- 规模:2 个实际 patch
- 修改文件:3 个(
include/linux/psi.h、kernel/cgroup/cgroup.c、kernel/sched/psi.c) - 代码统计:
+84/-18 - Message-ID:
20260712174619.3553231-1-tj@kernel.org - 完整性:cover 声明 0/2,实际 2/2;完整线程 13 封,无缺失警告、无 tip-bot2 污染
一句话总结:不再在
cgroup_mutex内创建 psimon,并把定时器最终停机推迟到 PSI group 释放,从根上修复死锁和 UAF。
补丁目的
a5b98009f16d 为修复 pressure 文件释放竞态,让 pressure_write() 持有
cgroup_mutex 调用 psi_trigger_create();首次创建特权 rtpoll trigger 时,后者会
kthread_create("psimon")。kthread 创建走完整 fork 路径,而 sched_ext enable 已先阻止
fork、再申请 cgroup_mutex,于是形成 ABBA 死锁,其他 fork 也全部堆积。
URL 指向 Matt 的原始方案:在 sched_ext 侧交换锁序。Tejun 指出
CLONE_INTO_CGROUP 会在持 scx_fork_rwsem 读锁时申请 cgroup_mutex,单纯换序会制造
另一组 ABBA;最终 v2 因而把 fork 移出 cgroup 临界区,并顺带修复评审发现的 timer UAF。
旧流程的问题
死锁链如下;scx_fork_rwsem 写锁还会挡住所有普通进程创建,影响不是局部卡顿:
scx_root_enable_workfn pressure_write / systemd
scx_fork_rwsem(W) cgroup_mutex
等 cgroup_mutex ─────────┐ psi_trigger_create()
│ kthread_create(psimon)
└── ABBA ─────── scx_pre_fork()
等 scx_fork_rwsem(R)
普通 fork ──────────────────────────────────────┘(一起阻塞)
另一个既有窗口位于最后一个 trigger 销毁时:调度器热路径已通过 RCU 读到
rtpoll_task,销毁方清空它并 timer_delete() 后,热路径仍可 mod_timer();group 随后
释放,poll_timer_fn() 就可能访问已释放内存。
新流程
pressure_write() [持 cgroup_mutex]
└─ psi_trigger_create(): 校验并登记 trigger,只报告 need_worker
└─ need_worker?
├─ 否:release-store 发布 trigger
└─ 是:解 cgroup_mutex
└─ kthread_create(psimon) + 并发安装裁决 + catch-up poll
└─ 重拿 cgroup_mutex,复查 of->priv
├─ 已失效:销毁未发布 trigger,返回错误
└─ 有效:release-store 发布 trigger
最后 trigger 销毁:允许无害的 timer 尾声
cgroup 生命周期终点:timer_shutdown_sync() → free_percpu() → kfree(group)
旧:对象登记、worker fork、文件状态发布都挤在 cgroup_mutex 内,timer 又在 trigger
生命周期中被非同步删除。新:对象登记与 fork 分阶段,文件指针最后发布;timer 的同步
停机绑定到更长、边界更明确的 group 生命周期。
Patch 概览
| Patch | 核心改动 |
|---|---|
| 1/2 | 拆分 trigger 登记与 psimon 创建;解锁后 fork,处理并发创建、失效文件和漏采样 |
| 2/2 | 在 psi_cgroup_free() 用 timer_shutdown_sync() 终止仍可能被竞态重挂的 timer |
关键实现
1. 两阶段创建避免锁依赖扩散
psi_trigger_create() 新增 need_rtpoll_worker 出参。它仍在
rtpoll_trigger_lock 下把 trigger 加入链表、更新最小周期和状态计数,但不再 fork。
只有没有现存 rtpoll_task 时才要求调用者进入第二阶段。system PSI 的 psi_write()
也改用同一接口,但它本来不持 cgroup_mutex。
2. 并发 worker 创建是“胜者安装、败者回收”
多个写者都可能观察到需要 worker 并各自创建 kthread。它们随后在
rtpoll_trigger_lock 下竞争:首个写入 group->rtpoll_task 并唤醒线程;其余调用
kthread_stop() 回收从未唤醒的线程。安装后强制调度一次 rtpoll,补偿无 worker
窗口内被丢弃的调度请求。
3. 解锁窗口与发布顺序
cgroup_kn_lock_live() 已打破 kernfs active reference;释放 cgroup_mutex 期间,rmdir
或并发 pressure 写可清掉 of->priv。重加锁后必须复查,失败就销毁新 trigger。
只有全部步骤成功,才用 smp_store_release(&ctx->psi.trigger, new) 发布,既维持“一 fd
一个 trigger”,也让初始化内容先于读者可见。
4. timer 随 group 终止
poll_timer_fn() 只唤醒 waitqueue,不会自我重挂,因此 group 存活时偶发尾声无害。
psi_cgroup_free() 已处于再无人能合法 arm timer 的阶段;此处
timer_shutdown_sync() 同时等待回调结束并禁止再次启动,然后才释放 per-CPU 数据和
group,消除 UAF。该 patch 标注 stable 范围为 v5.10+。
类比
旧实现像物业拿着整栋楼总钥匙办理新员工入职,而入职又必须经过正在等这把钥匙的门禁,
双方互等。新实现先在物业登记工牌,放下总钥匙后再带员工过门禁,回来确认房间仍存在才
交付工牌;定时器则像延迟快递,不在“最后一个员工离开”时强退,而在“整间办公室注销”
时统一确认没有在途件后再拆门牌。
Highlight:风险与注意点
- worker 创建失败会同步销毁已登记 trigger;
of->priv失效路径同样回滚,不会发布半成品。 - 回滚中的
psi_trigger_destroy()会在持cgroup_mutex时等待 RCU grace period,可能拉长
全局 cgroup 操作延迟。Sashiko 标为 High;Tejun 说明 rmdir/pressure disable 已存在同类
行为,认定不是本系列新回归,但值得后续把销毁移出锁或异步化。 - worker 创建竞争可能短暂多建 kthread,败者会被停止;这是低频首 trigger 路径的开销,
换取了清晰的无锁 fork 边界。 - 两个 patch 均有 Matt Fleming 的
Tested-by,以及 Johannes Weiner、Suren Baghdasaryan
的Acked-by;线程中没有 applied/queued 通知,当前本地树也仍是补丁前代码,不能描述为已合入。 - 两个 patch 都带 stable 标记;回移植时需分别核对 PSI 接口、timer API 和目标分支生命周期。
版本变化
- Matt 原始投稿只调整
scx_root_enable_workfn()锁序,被指出会与
CLONE_INTO_CGROUP形成反向死锁,且未覆盖其他 enable/disable 路径。 - Tejun v1 改在 cgroup/PSI 根因处拆出 fork;URL 线程后续明确宣告它被 v2 取代。
- v2 的 patch 1 仅把主题从 cgroup 重标为 sched/psi,代码不变;新增 patch 2 修复
Sashiko 在 v1 评审中发现的既有rtpoll_timerUAF。最终两补丁均通过原报告者测试。
一句话总结
最终 v2 没有继续修补 sched_ext 锁序,而是切断 cgroup_mutex → fork 依赖,并把 timer
停机放到 PSI group 的真正生命终点;方案已测试并获两位 PSI 维护者 Ack,但尚无合入通知。