sched-ext discussion
[PATCHSET v2] sched/psi: Fix psimon fork deadlock and rtpoll_timer UAF
LLM 分析
sched/psi:修复 psimon fork 死锁与 rtpoll_timer UAF
系列概况
- 标题:[PATCHSET v2] sched/psi: Fix psimon fork deadlock and rtpoll_timer UAF
- 作者:Tejun Heo tj@kernel.org
- 版本:v2(v1 只有 patch 1;v2 重打 prefix 并新增 patch 2)
- 规模:2 个 patch,3 个文件,84 insertions(+), 18 deletions(-)
- 修改文件:
include/linux/psi.h、kernel/cgroup/cgroup.c、kernel/sched/psi.c - 代码统计:patch 1:3 files, 78 insertions(+), 18 deletions(-);patch 2:1 file, 6 insertions(+)
- Message-ID:
20260712174619.3553231-1-tj@kernel.org - 完整性:完整收尾,已由 Tejun 合入
cgroup/for-7.2-fixes(基线 97fef6025844)
补丁目的
本系列修两个并发缺陷:
-
psimon fork 死锁:
pressure_write()在持有cgroup_mutex时首次 forkpsimonkthread;sched_ext enable 路径先阻塞 fork、再拿cgroup_mutex,两者相遇即死锁,其余 fork 全部堆积。 -
rtpoll_timer UAF:调度热路径上无锁的
psi_schedule_rtpoll_work()可能在psi_trigger_destroy()删除 timer 之后又mod_timer()把它重新挂上;group 随后释放,poll_timer_fn()跑在已释放内存上。
旧流程的问题
a5b98009f16d 之后,trigger 创建与 kthread fork 全部压在 cgroup_mutex 里,把整条 fork 路径的锁依赖引入了 cgroup_mutex。
timer 的历史处置同样两难:461daba06bdc 在 synchronize_rcu() 后同步删 timer,会取消新 trigger 刚 arm 的 timer 甚至腐化它;8f91efd870ea 把 init 挪进 group_init()、删除挪进加锁段,换来了 destroy 之后被重新 arm 的窗口。
old: pressure_write()
cgroup_lock()
psi_trigger_create()
kthread_create("psimon") <-- full fork path inside the mutex
cgroup_unlock()
conflict: scx_enable()
block all forks
cgroup_lock() <-- waits forever => deadlock
新流程
new: pressure_write()
cgroup_kn_lock_live() (active ref broken)
cgroup_lock()
psi_trigger_create(..., &need_rtpoll_worker) register only, no fork
if (need_rtpoll_worker) {
cgroup_unlock()
psi_trigger_create_rtpoll_worker(psi) fork happens unlocked
cgroup_lock()
if (!of->priv) ret = -ENODEV
}
smp_store_release(&ctx->psi.trigger, new) publish last
cgroup_unlock()
patch 2: psi_cgroup_free()
... triggers already gone ...
timer_shutdown_sync(&cgroup->psi->rtpoll_timer)
Patch 概览
- 1/2 Create the psimon kthread outside of cgroup_mutex:
psi_trigger_create()新增bool *need_rtpoll_worker出参;新增psi_trigger_create_rtpoll_worker();pressure_write()与psi_write()两阶段化。 - 2/2 Shut down rtpoll_timer in psi_cgroup_free():在 group 释放路径加一行
timer_shutdown_sync()。
关键实现
int psi_trigger_create_rtpoll_worker(struct psi_group *group)
{
struct task_struct *task;
task = kthread_create(psi_rtpoll_worker, group, "psimon");
if (IS_ERR(task))
return PTR_ERR(task);
scoped_guard(mutex, &group->rtpoll_trigger_lock) {
if (!rcu_access_pointer(group->rtpoll_task)) {
atomic_set(&group->rtpoll_wakeup, 0);
wake_up_process(task);
rcu_assign_pointer(group->rtpoll_task, task);
/* catch up on attempts dropped while no worker existed */
psi_schedule_rtpoll_work(group, 1, true);
return 0;
}
}
kthread_stop(task); /* 竞态输家停掉自己 fork 的线程 */
return 0;
}
要点:
- 两阶段:第一阶段注册的 trigger 在 unlock 窗口里 pin 住 group 的 rtpoll 机制,只剩创建竞态需要处理。
- publish last:错误仍同步返回;
of->priv可能在解锁期间被 rmdir 置 NULL,因此重查并返回-ENODEV。 - catch-up poll:worker 上线后补一次轮询,补回 worker 缺席期间丢掉的调度尝试。
patch 2 的依据是 poll_timer_fn() 只唤醒 rtpoll_wait、不会自我重 arm,因此 group 存活期间多触发一次无害;把 timer 生命周期绑定到 group,释放前一次性 timer_shutdown_sync()。
461daba06bdc --> 8f91efd870ea --> v2 patch 2
delete after delete inside shutdown in
synchronize_rcu locked section psi_cgroup_free()
races creation races destroy tied to group life
类比
像公司前台登记:旧流程是前台锁死大门后才去打印门禁卡(fork),偏偏物业此时封了大门,所有人堵死。新流程先把访客登记在册(注册 trigger),开门去打卡,再正式公布名单(publish);若同事已在打卡就不重复。
patch 2 则像提醒闹钟:以前闹钟随"最后一位客人离开"而关,但有人刚好又按了一下"延后提醒",办公室拆掉后闹钟还在响(UAF)。现在把闹钟随公司注销统一断电,中途误按也无害。
Highlight:风险与注意点
- Sashiko AI 指出错误路径中的
psi_trigger_destroy()仍在cgroup_mutex下执行,内部synchronize_rcu()会造成 cgroup 子系统延迟尖峰;Tejun 回复这是 pre-existing(rmdir / pressure disable 同样如此),留待未来改进。 - cgroup 路径需要成对的
cgroup_unlock()/cgroup_lock(),且 kernfs active ref 已被cgroup_kn_lock_live()打破,审阅时要盯住这一生命周期假设。 - patch 2 依赖"group 进入 free 后无人再 arm timer"这条不变式,若日后 trigger 销毁逻辑改动需重新验证。
- 两个 patch 都 Cc stable(patch 2 标 v5.10+),部署时应注意回溯范围。
- 路由:Peter 未在线程中回复,Tejun 自行决定走 cgroup 树。
版本变化
- v1 → v2:patch 1 prefix 由
cgroup改为sched/psi;新增 patch 2(rtpoll_timer UAF);patch 1 逻辑未变,Matt 对 v1 的测试结果继续适用。
一句话总结
把 psimon 的 fork 移出 cgroup_mutex、把 rtpoll_timer 关停绑定到 psi_group 生命周期,同时解决了 sched_ext enable 死锁与 timer use-after-free。