0/14 已展开

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.hkernel/cgroup/cgroup.ckernel/sched/psi.c
  • 代码统计:patch 1:3 files, 78 insertions(+), 18 deletions(-);patch 2:1 file, 6 insertions(+)
  • Message-ID20260712174619.3553231-1-tj@kernel.org
  • 完整性:完整收尾,已由 Tejun 合入 cgroup/for-7.2-fixes(基线 97fef6025844)

补丁目的

本系列修两个并发缺陷:

  1. psimon fork 死锁pressure_write() 在持有 cgroup_mutex 时首次 fork psimon kthread;sched_ext enable 路径先阻塞 fork、再拿 cgroup_mutex,两者相遇即死锁,其余 fork 全部堆积。

  2. 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 的历史处置同样两难:461daba06bdcsynchronize_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_mutexpsi_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。