0/2 已展开

LLM 分析

sched/psi:rtpoll_timer 生命周期修复的 5.15 stable 回移

系列概况

  • 标题sched/psi: Shut down rtpoll_timer in psi_cgroup_free()
  • 作者:原始补丁 Tejun Heo;5.15.y 回移 Sasha Levin;FAILED 通知 Greg KH
  • 版本:无 vN 标记;本线程是 stable 回移流程(失败通知 + 适配版补丁)
  • 规模:2 封邮件,回移补丁 6 行新增
  • 修改文件kernel/sched/psi.c
  • 代码统计1 file changed, 6 insertions(+)
  • Message-ID2026081706-verdict-hangout-e22e@gregkh20260818193056.3302866-1-sashal@kernel.org
  • 完整性:完整。上游 commit 5457025fa8ca3c0d2732109513de839e3e797190,失败通知与回移一一对应

补丁目的

修复 PSI(Pressure Stall Information)里的一个 use-after-free:cgroup 被释放后,仍可能有一个已重新装填(re-armed)的 rtpoll_timer 挂在定时器轮上,触发时 poll_timer_fn() 访问已释放内存。

根因是竞态:psi_schedule_rtpoll_work() 在调度器热路径上无锁调用,而 psi_trigger_destroy() 正在销毁最后一个 rtpoll trigger。

本线程的具体事项:上游补丁无法 cherry-pick 到 5.15-stable(因为 5.15 里 psi 是内嵌结构而非指针,且成员名还叫 poll_*),Sasha Levin 提交了手工适配版。

旧流程的问题

历史上补丁在"销毁路径"里反复挪动 timer 删除点,两头都不对:

  • 461daba06bdc:在 synchronize_rcu() 之后同步删除 timer——挡住了本次竞态,但与 创建 竞态:宽限期内新 trigger 装填的 timer 会被误删,而当时创建还会重新 init timer,导致 timer 结构损坏。
  • 8f91efd870ea:把 init 挪到 group_init(),把删除挪进加锁段——换掉了创建竞态,却打开了本次的销毁窗口。

竞态窗口:

psi_schedule_rtpoll_work()        psi_trigger_destroy()
  rcu_read_lock()
  task = rcu_dereference(rtpoll_task)
                                    rcu_assign_pointer(rtpoll_task, NULL)
                                    timer_delete(&rtpoll_timer)
  mod_timer(&rtpoll_timer, ...)   <-- re-armed
  rcu_read_unlock()
                                    synchronize_rcu()
                                    kthread_stop(task)
  ... cgroup freed, timer pending -> UAF

结论:销毁路径里放哪都不对。

新流程

不再试图在 trigger 销毁时精确删除 timer,而是把 timer 的生命周期绑定到 group 本身,在 psi_cgroup_free() 里一次性关闭。

关键前提:group 还活着时 timer 误触发是无害的——poll_timer_fn() 只是唤醒 rtpoll 等待队列,不会自我重新装填。到 psi_cgroup_free() 时已无人能再 arm 它,所以用 timer_shutdown_sync()(永久关闭,之后不再允许 arm)。

Patch 概览

邮件作者作用
1Greg KH自动通知:上游 commit 无法应用到 5.15.y,附冲突复现步骤
2Sasha Levin5.15.y 适配版:把 cgroup->psi->rtpoll_timer 改成 cgroup->psi.poll_timer

关键实现

上游(mainline,指针 + rtpoll_* 命名):

void psi_cgroup_free(struct cgroup *cgroup)
{
	...
	/*
	 * 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);

	cancel_delayed_work_sync(&cgroup->psi->avgs_work);
	free_percpu(cgroup->psi->pcpu);
	WARN_ONCE(cgroup->psi->rtpoll_states, "psi: trigger leak\n");
}

5.15.y 回移(内嵌结构 + poll_* 旧命名):

	timer_shutdown_sync(&cgroup->psi.poll_timer);

	cancel_delayed_work_sync(&cgroup->psi.avgs_work);
	free_percpu(cgroup->psi.pcpu);
	WARN_ONCE(cgroup->psi.poll_states, "psi: trigger leak\n");

差异只有两处机械改动,语义完全一致;Sasha 在 changelog 中明确标注了 [ adapted ... ]

顺序也重要:timer_shutdown_sync() 必须排在 free_percpu() 之前,否则 timer 回调仍可能摸到已释放的 per-cpu 数据。

   [OLD] destroy path              [NEW] group lifetime
   --------------------            --------------------
   timer_delete in lock --X        timer_shutdown_sync
        <-- races both ways              in psi_cgroup_free()
                                           cannot re-arm -> safe

类比

像酒店房间里的自动叫醒电话。旧做法是:最后一位客人退房时前台手忙脚乱地取消叫醒——但客人可能刚好在退房那一刻又按了一次"半小时后叫我",于是房间已经清空重新分配了,电话还照响,吵到了不该吵的人(已释放内存)。

新做法是:退房时不管叫醒,反正房间还没拆,电话响一下也没事;等到整栋楼这间房被拆掉psi_cgroup_free())时,直接把线路物理拔掉,之后谁也没法再设定叫醒。

Highlight:风险与注意点

  1. 顺序敏感timer_shutdown_sync() 必须在 free_percpu(pcpu) 之前,回移时若插错位置就白修了;本回移位置正确。
  2. timer_shutdown_sync() 的可用性:这是较新的 API(v6.2 引入 timer_shutdown_sync)。5.15.y 要能直接用,说明相关基础设施已被回移进 5.15.y——如果某些更老分支(如标注的 v5.10+)没有它,backport 需要退回 del_timer_sync(),语义弱一些(不阻止再次 arm)。
  3. 命名重构掩盖了 backport 风险rtpoll_* <- poll_* 只是改名,但 review 时容易误以为是不同对象;psi 从内嵌变指针也一样。
  4. "误触发无害"是本补丁成立的核心前提:依赖 poll_timer_fn() 不自我 re-arm。若将来该函数改为会重新装填,这个修复的假设立即失效。
  5. WARN_ONCE(rtpoll_states) 仍保留:说明 trigger 泄漏检测未受影响,但也意味着如果真有 trigger 未清理,timer 关闭并不掩盖该问题。
  6. 待跟进:Greg 的通知里 Possible dependencies: 为空,Closes: 也是空的——原始报告链接缺失,追溯 reproducer 时需要回到 lore 上的原线程。

版本变化

无多版本迭代。演进体现在历史 commit 链上:

  • 461daba06bdc(删除放在 synchronize_rcu() 后)-> 与创建竞态
  • 8f91efd870ea(init 移入 group_init(),删除移入加锁段)-> 与销毁竞态(本次 Fixes: 目标)
  • 5457025fa8ca(本补丁:绑定 group 生命周期)-> 两个竞态同时消失

一句话总结

与其在 trigger 销毁路径上和调度器热路径抢时间删 timer(怎么放都有竞态),不如把 rtpoll_timer 的生命周期绑到 cgroup 上、在 psi_cgroup_free()timer_shutdown_sync() 一刀关闭;本线程是该修复在 5.15.y 上因结构体形态与命名差异 cherry-pick 失败后的手工回移。