sched-ext discussion
[PATCH] sched_ext: Fix NULL sched deref in select_cpu_and sub-sched error path
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.c、kernel/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_lock 或 rq_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_SYSCALL、ops.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_lock;scx_task_sched_rcu()只需 RCU 读端。BPF 上下文必须用后者。- v1 只修了
select_cpu_and,遗漏dsq_insert_vtime:sashiko-bot 与 Andrea Righi 都点出ops.enqueue/ops.dispatch可携带任意KF_RCUtask。 - 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 窗口。