0/10 已展开

LLM 分析

sched_ext:修复 sub-sched 错误路径上的 NULL sched 解引用

系列概况

  • 标题: [PATCH v3] sched_ext: Fix NULL sched deref in kfunc sub-sched error paths
  • 作者: Wanwu Li liwanwu@kylinos.cn
  • 版本: v1 -> v2 -> v3(v3 已由 Tejun Heo 应用到 sched_ext/for-7.3-fixes
  • 规模: 2 files changed, 18 insertions(+), 4 deletions(-)
  • 修改文件: kernel/sched/ext/ext.ckernel/sched/ext/idle.c
  • 代码统计: ext.c +11/-2, idle.c +11/-2
  • Message-ID: 20260903060626.814951-1-liwanwu@kylinos.cn(v3 锚点)
  • 完整性: 三版完整评审闭环,含 sashiko-bot AI 评审、Andrea Righi (NVIDIA)、Tejun Heo (maintainer) 反馈;stable Cc 加 # v7.1+

补丁目的

修复 sched_ext 两个 COMPAT kfunc 包装器在 sub-sched 错误路径上的 NULL 指针解引用。

当 root scheduler 挂有 sub-sched 时,scx_bpf_select_cpu_and()scx_bpf_dsq_insert_vtime()scx_error(scx_task_sched(p), ...) 把错误归属到 @p 的 scheduler。scx_task_sched(p)p->scx.sched,对 sched_ext_dead() 之后的 task 与 idle task 为 NULL;并且它是 rcu_dereference_protected(),要求 pi_lockrq_lock,两个包装器都不持有。把 NULL 透传到 scx_error() -> scx_vexit() -> sch->exit_info 解引用 -> scx_vexit+0x25/0xa0 处 oops。

触发场景:BPF_PROG_TYPE_SYSCALL 程序对刚退出但未 reap 的 task 调用 scx_bpf_select_cpu_and(),且 root 上挂有 sub-sched。

旧流程的问题

错误路径用 rcu_dereference_protected()p->scx.sched

  • BPF_PROG_TYPE_SYSCALLops.enqueue/ops.dispatch 等上下文不持有 pi_lock/rq_lock,lockdep 报警
  • sched_ext_dead() 之后或 idle 任务返回 NULL
  • NULL 进入 scx_vexit()mov r15,[rdi+0x398] 直接 oops

新流程

guard(rcu)() 范围内用 scx_task_sched_rcu(p) 安全读取:

  • 读得到 -> 走原有 scx_error() 报错
  • 读不到(task 已 exit 或 idle)-> 静默拒绝,不再把锅甩给 root

Patch 概览

只改两个 kfunc 包装器,骨架一致:

struct scx_sched *tsch = scx_task_sched_rcu(p);

if (tsch)
    scx_error(tsch, "... must be used");

ext.c 覆盖 scx_bpf_dsq_insert_vtime()idle.c 覆盖 scx_bpf_select_cpu_and()

关键实现

// kernel/sched/ext/idle.c (节选)
__bpf_kfunc s32 scx_bpf_select_cpu_and(struct task_struct *p,
                                        s32 prev_cpu, u64 wake_flags)
{
    struct scx_sched *sch = scx_read_sched();   /* root */
    ...
    guard(rcu)();                                /* 已有 RCU */
    ...
#ifdef CONFIG_EXT_SUB_SCHED
    if (unlikely(!list_empty(&sch->children))) {
        struct scx_sched *tsch = scx_task_sched_rcu(p);

        if (tsch)
            scx_error(tsch,
                      "__scx_bpf_select_cpu_and() must be used");
        return -EINVAL;
    }
#endif
    ...
}

scx_task_sched_rcu() 是 RCU 友好版本,不要求 task 锁,与 guard(rcu)() 配套。

旧流程:调用路径与崩溃示意

BPF_PROG_TYPE_SYSCALL / ops.enqueue/dispatch
        |
        v
  scx_bpf_select_cpu_and(p)            scx_bpf_dsq_insert_vtime(p)
        |                                       |
        | guard(rcu)                            | guard(rcu)
        v                                       v
  sch = scx_read_sched() (root, non-NULL)  sch = scx_read_sched()
        |                                       |
        v                                       v
  list_empty(&sch->children) == false    list_empty(&sch->children) == false
        |                                       |
        v                                       v
  scx_task_sched(p)  <-- rcu_dereference_protected
        |         (no pi_lock/rq_lock  ->  lockdep warning)
        v
  p->scx.sched == NULL   (task past sched_ext_dead() / idle)
        |
        v
  scx_error(NULL, "...") -> scx_vexit(NULL)
        |
        v
  mov r15,[rdi+0x398]   ; RDI=NULL, 0x398 = offsetof(sch->exit_info)
        |
        v
  *** kernel NULL pointer dereference @ scx_vexit+0x25 ***

新流程:v3 安全分支

  tsch = scx_task_sched_rcu(p)          ; RCU-safe read
        |
        +-- tsch != NULL --> scx_error(tsch, "...") ; normal fault
        |
        +-- tsch == NULL --> return -EINVAL          ; silently refuse
              (root scheduler is NOT falsely faulted)

 (v2 had used "?: sch" which falsely faulted the root;
 v3 drops that fallback per Tejun's review.)

三版演进对照

 v1            v2            v3
  -------------------------------------------------------
  select_cpu_and fix      fix          fix
  dsq_insert_vtime  leave fix (Andrea)  fix
  NULL handling ?: sch  ?: sch       silent return
  "managed by
 another sched"   claim   claim        dropped (Tejun)
  Suggested-by      -       -            Andrea Righi status            flagged review applied to
 asked for-7.3-fixes

类比

p->scx.sched 想象成员工工牌上的「所属部门」。前台登记员接到投诉时按工牌转给对应部门经理。但工牌可能是「已离职」(NULL)或「公司清洁阿姨」(idle,不归部门)。v1/v2 的 ?: sch 等于"读不到部门就甩给总经理",让总经理替离职员工/清洁阿姨背锅;v3 改为"读不到就直接拒收工单",不再骚扰 root。

Highlight:风险与注意点

  • scx_task_sched() 要求 pi_lock/rq_lockscx_task_sched_rcu() 只需 RCU 读端。BPF 上下文必须用后者。
  • v1 只修了 select_cpu_and,遗漏 dsq_insert_vtime:sashiko-bot 与 Andrea Righi 都点出 ops.enqueue/ops.dispatch 可携带任意 KF_RCU task。
  • Tejun 否定"managed by another scheduler"叙述:root enable 与 fork 时 p->scx.sched 即被赋值,跨调度器不会清零。NULL 仅来自 sched_ext_dead() 之后与 idle。
  • v2 的 ?: sch 把错归到 root,Tejun 认为对 root 不公平,v3 改为静默 return
  • COMPAT 包装器最终会被移除,但 grace period 结束前不能再 oops;本修复先把窗口关掉。

版本变化

  • v1 -> v2:把同样的 RCU 读取 + ?: sch 回退扩展到 scx_bpf_dsq_insert_vtime();commit message 重写可触达性论证。
  • v2 -> v3:根据 Tejun 反馈,移除"managed by another scheduler"叙述;当 p->scx.sched 无法确定时不再 fault root,直接静默 return;新增 Suggested-by: Andrea Righi
  • 落地:v3 由 Tejun 应用到 sched_ext/for-7.3-fixes,stable Cc 加 # v7.1+

与其他 patch 系列的关联

  • 本系列是 Fixes: a5fa0708cbfd("sched_ext: Enforce scheduling authority in dispatch and select_cpu operations")引入的 strict-authority 错误路径的 bugfix。
  • 是 sub-sched(CONFIG_EXT_SUB_SCHED)特性下的可触达性收尾;同一条线最终会通过 COMPAT 包装器废弃流程一并收掉(commit message 明示"scheduled for eventual removal")。

一句话总结

把"强制 fault @p 的 scheduler"改成"RCU 下读得到就 fault、读不到就静默拒绝",关闭 sched_ext sub-sched 错误路径上对 NULL 指针解引用的 crash 窗口。