sched discussion
FAILED: patch "[PATCH] sched/psi: Shut down rtpoll_timer in psi_cgroup_free()" failed to apply to 5.10-stable tree
LLM 分析
sched/psi:把 rtpoll_timer 的关闭下沉到 cgroup 释放流程
系列概况
- 标题:FAILED: patch "[PATCH] sched/psi: Shut down rtpoll_timer in psi_cgroup_free()" failed to apply to 5.10-stable tree(自动转发),以及后续 [PATCH 5.10.y] sched/psi: Shut down rtpoll_timer in psi_cgroup_free()
- 作者:Greg KH(stable 转发)、Tejun Heo(upstream)、Sasha Levin(5.10.y backport)
- 版本:mainline 5457025fa8ca -> 5.10.y backport
- 规模:单文件、6 行新增
- 修改文件:kernel/sched/psi.c
- 代码统计:1 file changed, 6 insertions(+)
- Message-ID:2026081707-cozy-flier-448d@gregkh、20260819111459.3552196-1-sashal@kernel.org
- 完整性:upstream commit 完整转发,5.10.y 适配版块由 Sasha Levin 重发,主体一致
补丁目的
PSI(Pressure Stall Information)在 cgroup 销毁路径上存在 use-after-free:
psi_schedule_rtpoll_work()由调度热路径 lockless 调用,可能在psi_trigger_destroy()已经timer_delete(&rtpoll_timer)之后,再次mod_timer()重新装弹。synchronize_rcu()+kthread_stop()完成后 cgroup 被释放,pending 的 timer 仍然存在,poll_timer_fn()会在已释放内存上运行。
补丁把 timer 的关闭绑定到 cgroup 的生命周期:在 psi_cgroup_free() 中调用 timer_shutdown_sync()(5.10.y 适配为 timer_delete_sync())显式关闭 rtpoll_timer(5.10 中名为 poll_timer)。
旧流程的问题
历史上两个修复都没能封死 race:
461daba06bdc("psi: eliminate kthread_worker ..."):在synchronize_rcu()之后同步删除 timer -- 解决 destroy 侧的 race,但反过来和 create 路径打架:同一 grace period 内新 trigger 的mod_timer仍可重新装弹一个刚被 cancel 的 timer,触发 timer corruption。8f91efd870ea("psi: Fix race between psi_trigger_create/destroy"):把 init 挪到group_init(),delete 挪到 locked 段。把 create 侧的 race 换成 destroy 侧的窗口 -- destroy 路径上timer_delete之后仍有 hot pathmod_timer的窗口。
两条路径都缺乏 "timer 与 group 共生死" 的语义。
新流程
- 不再依赖 destroy 路径上的精细时序。
psi_cgroup_free()中调用timer_shutdown_sync(&cgroup->psi->rtpoll_timer),此时已没有任何东西可以再 arm 它。- 因 timer 之后再也不会被 arm,
timer_shutdown_sync()的 "关掉且禁止再 arm" 语义刚好对上。
cgroup unbind / rmdir
|
v
psi_trigger_destroy() (last trigger)
+-- rcu_assign_pointer(rtpoll_task, NULL)
+-- timer_delete(&rtpoll_timer)
+-- synchronize_rcu() + kthread_stop()
|
v
psi_cgroup_free() (NEW: timer bound to group)
+-- timer_shutdown_sync(&cgroup->psi->rtpoll_timer) <-- key fix
+-- cancel_delayed_work_sync(&cgroup->psi->avgs_work)
+-- free_percpu(cgroup->psi->pcpu)
+-- WARN_ONCE(rtpoll_states, "psi: trigger leak\n")
psi_schedule_rtpoll_work() psi_trigger_destroy()
------------------------------ ------------------------------
rcu_read_lock() timer_delete(&rtpoll_timer)
task = rcu_dereference(...) synchronize_rcu()
mod_timer(&rtpoll_timer, ...) kthread_stop(task_to_destroy)
rcu_read_unlock() |
v
race window -> group freed
|
v
poll_timer_fn() on freed mem |
v
timer_shutdown_sync() (new fix)
Patch 概览
两封邮件是同一 commit 5457025fa8ca 的不同分发:
| 邮件 | 版本 | 主要差异 |
|---|---|---|
| #1 | mainline -> stable 自动转发 | 提示 5.10.y 上 apply 失败,需人工 backport |
| #2 | 5.10.y backport | cgroup->psi-> -> cgroup->psi.、rtpoll_timer -> poll_timer、timer_shutdown_sync() -> timer_delete_sync(),commit log 同步改写 |
关键实现
upstream hunk(mainline):
+ /*
+ * A psi_schedule_rtpoll_work() call racing the last trigger's
+ * destruction may have re-armed the timer after psi_trigger_destroy()
+ * deleted it. Spurious firing while the group is alive is harmless.
+ */
+ timer_shutdown_sync(&cgroup->psi->rtpoll_timer);
5.10.y hunk:
+ /*
+ * A psi_schedule_poll_work() call racing the last trigger's
+ * destruction may have re-armed the timer after psi_trigger_destroy()
+ * deleted it. Spurious firing while the group is alive is harmless.
+ */
+ timer_delete_sync(&cgroup->psi.poll_timer);
差异:
cgroup->psi在新内核是指针,在 5.10 是嵌入字段,所以->vs.。- 5.10 还没有
timer_shutdown_sync(),旧 API 是timer_delete_sync()。 - 5.10 仍叫
poll_timer,命名演化后才改rtpoll_timer("rt-poll")。
类比
把 cgroup 想成一家公司:
psi_trigger_destroy()是最后一位股东退场。psi_schedule_rtpoll_work()是不断路过的快递员,他会在股东走时把新包裹塞进传达室的"定时取件"按钮(mod_timer)。- 老规则下,公司关门(cgroup 释放)之后下一次"取件铃"响时,快递员按的其实是废弃的传达室门铃 -- UAF。
- 新做法是物业在合同里写死:取件铃一响直接断电、永远不再装回去(
timer_shutdown_sync/timer_delete_sync)。之后不管快递员怎么按,铃都不会再被装上,公司大门关上之后也不会再有任何"取件"事件。
Highlight:风险与注意点
psi_schedule_rtpoll_work仍在 hot path 上 lockless 调用,新代码并不能阻止"多余触发",它接受"group 还活着时多余的 timer 触发是无害的"这一前提(poll_timer_fn()只 wake waitqueue,不会 re-arm)。这个不变式一旦被打破(例如有人让poll_timer_fn自装载),旧 race 立即回归。- 5.10.y 用
timer_delete_sync而非timer_shutdown_sync:timer_delete_sync之后 timer 理论上仍可被重新 arm;目前靠"之后没人会 arm"这一不变量兜底。若后续有人在 5.10 上加新的 arm 路径,需要重新审视是否会引入新的 UAF。 WARN_ONCE(cgroup->psi->rtpoll_states, ...)仍保留,意味着 trigger 计数泄漏依然告警;本 patch 只解决 timer 那一段,不处理 trigger 计数本身的泄漏。- Greg KH 的失败邮件是脚本自动转发,5.10.y 的 backport 必须人工核对字段命名差异。这次 Sasha 同步把 commit log 中的
psi_schedule_rtpoll_work()改为psi_schedule_poll_work(),与 5.10 的命名保持一致,避免文档与代码对不上。 - Reported-by: Sashiko AI -- 这是 AI 扫描发现的 UAF,值得复盘:是否还有别的
cgroup_free/timer_shutdown类遗漏,例如其他 cgroup 子系统或 work_struct 的同类 race。
版本变化
本 thread 内只有 mainline -> 5.10.y 一次 backport 演进:
- mainline
5457025fa8ca:使用cgroup->psi->rtpoll_timer+timer_shutdown_sync()。 - 5.10.y backport:字段嵌入化(
->->.)、旧 timer API(timer_delete_sync())、变量名(rtpoll_timer->poll_timer)、commit log 同步改名为psi_schedule_poll_work()。
一句话总结
把 PSI rtpoll timer 的关闭从 destroy 路径下沉到 psi_cgroup_free(),让 timer 与 cgroup 同生死以封堵 UAF race;5.10.y 通过字段嵌入、旧 timer API 与命名差异做适配 backport。