sched-ext discussion
[PATCHSET sched_ext/for-7.3] sched_ext: Follow-up fixes and missing cap enforcement
LLM 分析
sched_ext: Follow-up fixes and missing cap enforcement
系列基线信息
| 字段 | 值 |
|---|---|
| 标题 | sched_ext: Follow-up fixes and missing cap enforcement |
| 作者 | Tejun Heo |
| 版本 | 单次提交,无版本号标注 |
| 规模 | 5 patches,13 文件,+181 / -64 行 |
| 基线分支 | sched_ext/for-7.3 (c509d8107026) |
| Message-ID | 20260724182125.985061-1-tj@kernel.org |
| 来源 | sched-ext 频道 |
明确目的
本 patch 系列解决两类问题:
-
工具重启循环 bug:sched_ext 用户态工具在收到内核 RESTART 退出码时自动重启,但没有检查
exit_req标志。若用户在重启等待期间请求退出,该请求被忽略,工具陷入无限重启循环。 -
子调度器 capability 缺口:近期子调度器(sub-scheduler)引入了分层 capability 机制,但部分 cid-addressed 操作缺少 cap 检查,形成权限漏洞——无任何 cap 的子调度器可对目标 CPU 发 IPI(reenq)或操控 cpufreq(cidperf_set)。本系列逐一堵住这些漏洞,并为每次拒绝添加可观测的事件计数器。
遍历代码
Patch 1/5 — 工具重启循环修复
所有 sched_ext 工具的主循环在内核退出调度器后检查退出码:
// 修复前:无条件重启
if (UEI_ECODE_RESTART(ecode))
goto restart;
// 修复后:先看用户是否请求了退出
if (!exit_req && UEI_ECODE_RESTART(ecode))
goto restart;
scx_userland 需要额外修复:
- 主循环缺少
UEI_EXITED()检查,导致从不感知内核侧退出; exit_req同时充当统计打印线程的停止信号,重启时被重置为 0,造成退出请求丢失。- 新增
stats_stop专用于打印线程,exit_req只表达退出意图且不再重置。
Patch 2/5 — 本地 DSQ reenq 拦截
scx_bpf_dsq_reenq() 对 SCX_DSQ_LOCAL_ON 目标会在目标 CPU 上调度延迟 reenq 工作,发送 IPI。此前无 cap 检查。
修复:在 schedule_dsq_reenq() 入口处检查 SCX_CAP_BASE:
if (unlikely(scx_missing_caps(sch, cpu_of(rq), SCX_CAP_BASE))) {
__scx_add_event(sch, SCX_EV_SUB_REENQ_DENIED, 1);
return;
}
检查无锁(lockless)——cap 撤销后的瞬态漏过无害,误拒不可能(调用者已看到所有权 → 检查也能看到)。
Patch 3/5 — kick 拒绝计数
kick_one_cpu() 和 kick_one_cpu_if_idle() 已在 SCX_CAP_BASE 门后静默跳过 kick,但拒绝不可观测。
修复:提取 kickable 变量,当 CPU 可 kick 但缺少 cap 时计入 SCX_EV_SUB_KICK_DENIED:
kickable = (cpu_online(cpu) || cpu == cpu_of(this_rq)) &&
!sched_class_above(cur_class, &ext_sched_class);
if (kickable && !scx_missing_caps(pcpu->sch, cpu, SCX_CAP_BASE))
// 正常 kick
else if (kickable)
__scx_add_event(pcpu->sch, SCX_EV_SUB_KICK_DENIED, 1);
与 reenq 拒绝事件形成互补:reenq 被丢弃前计数,kick 被跳过后计数,preempt 降级已有 SCX_EV_SUB_PREEMPT_DENIED。
Patch 4/5 — scx_cpuperf_set() 重构
将 scx_bpf_cpuperf_set() 中的 cpuperf 写入逻辑抽取为 scx_cpuperf_set(sch, cpu, perf) 返回 0/-errno。嵌套验证改为早返回。
无功能变更,为 Patch 5 做准备——后续需要在此函数内加 cap 门并报告结果。
Patch 5/5 — SCX_CAP_PERF 门控 cidperf_set
新增 SCX_CAP_PERF(独立于 SCX_CAP_BASE),仅 root 持有默认。在 scx_cpuperf_set() 内检查:
if (likely(!scx_missing_caps(sch, cpu, SCX_CAP_PERF))) {
rq->scx.cpuperf_target = perf;
cpufreq_update_util(rq, 0);
ret = 0;
} else {
__scx_add_event(sch, SCX_EV_SUB_CIDPERF_DENIED, 1);
ret = -EACCES;
}
关键设计:
- 检查在目标 rq 锁下进行,ecaps 更新也在同一锁下折叠,因此是权威的——撤销生效后写入不可能到达。
- 新增
scx_bpf_cidperf_set___v2()返回s32(0/-EACCES/-errno),旧 void 版本保留为兼容包装。 - tools 层 compat wrapper 优先调用 v2,旧内核总是应用写入并返回 0。
scx_qmap的子调度器 grant/revoke 现在附带SCX_CAP_PERF,确保 cpufreq demo 继续工作。
ASCII 流程图
Sub-scheduler capability enforcement
====================================
BPF kfunc call cap check point outcome
────────────── ───────────────── ──────
scx_bpf_dsq_reenq() ──> SCX_CAP_BASE ────────────> reenq: proceed
(LOCAL_ON target) (lockless) or: drop + EV_SUB_REENQ_DENIED
scx_bpf_kick_cid() ──> SCX_CAP_BASE ────────────> kick: send IPI
(kick_one_cpu) (lockless) or: skip + EV_SUB_KICK_DENIED
or: degrade + EV_SUB_PREEMPT_DENIED
scx_bpf_cidperf_set() ─> SCX_CAP_PERF ───────────> write cpuperf_target
(cidperf_set___v2) (under rq lock) or: deny + EV_SUB_CIDPERF_DENIED
return -EACCES
┌─────────────────────────────────────────────────────────────┐
│ cap hierarchy (independent axes) │
│ │
│ SCX_CAP_BASE ── queue access (enqueue, reenq, kick) │
│ SCX_CAP_PERF ── hardware control (cpufreq target) │
│ SCX_CAP_ENQ / PREEMPT / ENQ_IMMED ── finer-grained ops │
│ │
│ PERF ≠ BASE; parent may grant scheduling without freq │
└─────────────────────────────────────────────────────────────┘
Tools restart-loop fix (before → after)
BEFORE: AFTER:
┌──────────────┐ ┌──────────────┐
│ kernel exits │ │ kernel exits │
│ with RESTART │ │ with RESTART │
└──────┬───────┘ └──────┬───────┘
│ │
▼ ▼
┌──────────────┐ ┌──────────────────┐
│ always goto │ │ check exit_req? │
│ restart │ │ NO → goto rest. │
└──────┬───────┘ │ YES → return 0 │
│ └──────┬───────────┘
▼ │
┌──────────────┐ ▼
│ infinite │ ┌──────────────┐
│ restart loop │ │ clean exit │
│ (bug!) │ └──────────────┘
└──────────────┘
概念类比
把 sub-scheduler capability 机制比作公司部门权限体系:
-
SCX_CAP_BASE = 门禁卡:有卡才能进入办公楼(CPU),发送通知(IPI/kick)或把文件放到某人桌上(reenq 到本地 DSQ)。没有门禁卡,保安会在门口拦住你,并在登记簿上记一笔(事件计数器)。
-
SCX_CAP_PERF = 空调遥控器:进入楼不等于可以随意调空调温度。公司可能把门禁卡给了外包团队(允许他们在楼里工作),但空调遥控器只给物业(root)。两把钥匙互不包含——这正是 PERF ≠ BASE 的设计。
-
lockless 检查 ≈ 门禁卡的即时刷卡验证:偶尔系统刚注销你的卡、门还没锁上你滑过去了——无关紧要(无害瞬态)。但绝不会出现你有卡却刷卡失败的情况(误拒不可能)。
-
rq 锁下的检查 ≈ 空调遥控器操作必须在机房里完成:谁在机房里、谁有遥控器,都在同一锁下确认——不可能出现遥控器已被收回但你还能调温度的情况。
-
v2 kfunc ≈ 遥控器给出操作反馈:旧遥控器按了就算,不管有没有生效;新遥控器会告诉你"操作成功"或"权限不足"。
Highlight 突出问题
-
kernel-doc 断裂:sashiko-bot 发现 Patch 4 重构后,kernel-doc 注释落在了
scx_cpuperf_set()内部函数上而非scx_bpf_cpuperf_set()kfunc 上。Tejun 确认将在合入时修正——若不修正,API 文档将指向错误函数。 -
SCX_CAP_PERF 向后兼容:sashiko-bot 提出 scx_qmap 在 grant/revoke 中无条件传入
SCX_CAP_PERF,旧内核不知此 bit 可能拒绝。Tejun 回应:子调度器 cap 接口仍在初始开发期,无跨版本兼容承诺,in-tree 工具跟随 in-tree 内核。但外部/下游调度器若移植此代码需注意——旧内核会拒绝未知的 cap bit。 -
lockless 检查的理论边界:reenq/kick 的 cap 检查无锁,依赖"漏过无害、误拒不可能"论证。该论证假设 cap 撤销的可见性与调用者对 CPU 所有权的可见性一致——如果未来 cap 传播路径变化,需重新验证此假设。
-
SCX_EV_SUB_KICK_DENIED vs SCX_EV_SUB_PREEMPT_DENIED 的语义区分需使用者理解:前者是 kick 被完全跳过,后者是 kick 送达但 preempt 降级为普通 reschedule。
-
cidperf_set___v2 compat wrapper 的生命周期:声明在 v7.6 后移除旧版包装——需跟踪时间线确保届时完成清理。
版本演进
本次为首次发布,无版本演进。
与其他相关 patch 系列的关联
本系列紧跟近期 sub-scheduler capability 框架的引入(SCX_CAP_BASE、SCX_CAP_ENQ、SCX_CAP_PREEMPT 等),补上遗漏的检查点并增加 cpufreq 控制的独立权限轴。目标分支 sched_ext/for-7.3 表明这些改动计划合入 Linux 7.3 的 sched_ext 子系统。
一句话总结
堵住 sub-scheduler 的三个权限漏洞(reenq IPI、kick 计数、cidperf cpufreq),修复工具重启循环 bug,新增 SCX_CAP_PERF 作为独立于 BASE 的 cpufreq 权限轴,并通过版本化 kfunc 同步报告拒绝结果。