sched-ext discussion
[PATCH] sched_ext: Gate cid kfuncs behind the SCX struct_ops check
LLM 分析
sched_ext:把 cid kfunc 收回到 SCX struct_ops 检查门内
系列概况
- 标题: [PATCH] sched_ext: Gate cid kfuncs behind the SCX struct_ops check
- 作者: Qiurong Fang fangqiurong@kylinos.cn
- 版本: v1(单封 patch,无系列序号)
- 规模: 1 patch / 2 文件 / +6 / -4- 修改文件:
- kernel/sched/ext/cid.h
- kernel/sched/ext/ext.c
- Message-ID: 20260812061116.2691443-1-fangqiurong@kylinos.cn
- 完整性: 完整,含 commit message、Fixes、SOB、AI-assisted-by
补丁目的
把 scx_bpf_cid_to_cpu()、scx_bpf_cpu_to_cid()、scx_bpf_cid_topo() 三个 kfunc 的调用限定在 SCX struct_ops 程序里。它们本应归在 scx_kfunc_ids_cid 这个 BTF id set 中,但 scx_kfunc_context_filter() 漏检该集合,导致任何 struct_ops 程序(例如 TCP 拥塞控制)都能调到它们——这违反了"必须从 sched_ext 上下文调用"的设计契约。
旧流程的问题
scx_kfunc_context_filter() 是 kfunc 调用前的门卫,先做一次身份识别:
bool in_unlocked = btf_id_set8_contains(&scx_kfunc_ids_unlocked, kfunc_id);
bool in_idle = btf_id_set8_contains(&scx_kfunc_ids_idle, kfunc_id);
bool in_any = btf_id_set8_contains(&scx_kfunc_ids_any, kfunc_id);
/* Not an SCX kfunc - allow. */
if (!(in_unlocked || in_init_cids || in_select_cpu ||
in_enqueue || in_dispatch || ... || in_any))
return 0;
凡是落在"已知 SCX 集合"之外的 kfunc,filter 视作"不是 SCX kfunc,直接放行"——这是个放行兜底。in_cid 这一支没写进白名单,所以三个 cid kfunc 被错放到兜底分支里放掉了。
新流程
把 scx_kfunc_ids_cid 加入已知集合,并把 || in_cid 补到 SYSCALL 旁路与 struct_ops 旁路两处:
bool in_cid = btf_id_set8_contains(&scx_kfunc_ids_cid, kfunc_id);
...
return (in_unlocked || in_select_cpu || in_idle || in_any || in_cid) ? 0 : -EACCES;
...
return (in_any || in_idle || in_cid) ? 0 : -EACCES;
...
if (in_any || in_idle || in_cid)
return 0;
非 SCX 上下文再调用这三个 kfunc 时,会被 -EACCES 拒绝;真正的 SCX struct_ops 程序不受影响。
Patch 概览
kernel/sched/ext/cid.h:声明外部符号scx_kfunc_ids_cid。kernel/sched/ext/ext.c:在scx_kfunc_context_filter()里新增in_cid判定,并在 4 处分支同步补上|| in_cid。
kfunc call from struct_ops prog
|
v
+--------------------------------------+
| btf_id_set8_contains check |
| in_unlocked / in_init_cids / ... |
| in_idle / in_any / in_cid? |
+--------------------------------------+
no match? ----+---- match?
| |
v v
return 0 SYSCALL prog?
(allow) | |
yes no
| |
v v
allow id/cpu struct_ops?
/select_cid | |
/cid yes no
| |
v v
per-op allow -EACCES
list match?
| |
yes no
| |
v v return 0 -EACCES
关键实现
整个 filter 是两段式:先识别身份、再按 prog 类型分发。
/* 1) 解析身份 */
in_unlocked / in_init_cids / in_select_cpu / in_enqueue / in_dispatch /
in_cpu_release / in_idle / in_any / in_cpu_only
+ (新) in_cid/* 2) 分发 */
if (!(... || in_cid))
return 0; /* 兜底放行 - 但要求至少落入一个已知集合 */
if (prog->type == BPF_PROG_TYPE_SYSCALL)
return (in_unlocked || in_select_cpu || in_idle || in_any || in_cid) ? 0 : -EACCES;
if (prog->type != BPF_PROG_TYPE_STRUCT_OPS)
return -EACCES;
/* SCX struct_ops per-op allow list */
return (in_any || in_idle || in_cid) ? 0 : -EACCES;
旧版的关键漏洞是"兜底放行 + 后续分支放行"两处都没把 in_cid 当 SCX 身份,于是任意 struct_ops 调用都被默许。新版把 in_cid 同时钉在兜底分支之后的对称位置。
类比
把办公楼门禁想成 filter:每张员工卡都印着身份标签(普通、机房、总裁办公区)。门口的闸机先扫标签做白名单匹配。
旧系统漏配了"总裁办公区"这条规则——卡片虽然写着"高管权限",但闸机只查普通/机房/访客三类,没匹配上就默认"你不是本楼员工、随便进"——结果隔壁公司(TCP 拥塞控制)拿着普通卡也能进总裁办公区。
补丁就是补一条规则:闸机现在认识"高管权限"了,本楼的高管照常进出,外来卡会被明确拦下(-EACCES)。
Highlight:风险与注意点
- 行为收紧带来的回归面:原本在非 SCX struct_ops 程序里调用这三个 kfunc 的代码会从"能跑"变成"返回 -EACCES"。建议 grep 现有 BPF sample/selftest,确认没有人在 TCP、BPF iter 等地方误用。
- id set 注册顺序:新增的
extern struct btf_id_set8 scx_kfunc_ids_cid;必须在ext.c的DEFINE_BTF_ID_SET中真实存在,否则btf_id_set8_contains()编译期就会失败。 - Sashiko 自动评审的旁支风险:memcg 的
bpf_mem_cgroup_memory_events/bpf_mem_cgroup_vm_events缺少负数边界检查。这是 pre-existing 问题,与本 patch 无关,但仍是高质量议题。 - 下游已合入:Tejun 已 apply 到
sched_ext/for-7.3分支。
版本变化
单封 patch,没有 v1→v2 演进。
一句话总结
把 scx_kfunc_ids_cid 加入 scx_kfunc_context_filter() 的已知 SCX 身份集合,防止三个 cid kfunc 被任意 struct_ops(如 TCP 拥塞控制)误调用,已被 Tejun apply 到 sched_ext/for-7.3。