scx_call_op_set_cpumask() builds a per-cpu cmask in the set_cmask scratch, which lives in BPF-writable arena. A scheduler can corrupt the scratch's inline header (base, nr_cids, alloc_words) from another cpu, so sizing and indexing the write from it risks an out-of-bounds write. Drive the build from kernel-known geometry instead. scx_cmask_ref_init_kern() imposes base and nr_cids rather than reading them, and scx_cmask_ref_from_cpumask() fills the scratch from the ref. Neither reads the header back.
Showing the changes to kernel (3 of 3 files). Show all
Unified · Split
| @@ -951,6 +951,38 @@ int scx_cmask_ref_init(struct scx_sched *sch, const struct scx_cmask *src, · 950 unmodified lines | |||
| 951 | return 0; | 951 | return 0; |
| 952 | } | 952 | } |
| 953 | 953 | ||
| 954 | /** | ||
| 955 | * scx_cmask_ref_init_kern - Bind a scx_cmask_ref to a kernel-owned cmask | ||
| 956 | * @sch: scheduler the cmask belongs to | ||
| 957 | * @m: kernel address of the target cmask, storage sized for @nr_cids at @base | ||
| 958 | * @base: first cid of the active range | ||
| 959 | * @nr_cids: active range length | ||
| 960 | * @ref: output ref | ||
| 961 | * | ||
| 962 | * Like scx_cmask_ref_init() but the geometry is supplied by the caller, not | ||
| 963 | * read from @m's header, so a concurrent BPF write to the header can't steer | ||
| 964 | * later sizing or offsets. Rewrite the header from the trusted geometry and | ||
| 965 | * bind @ref to it. | ||
| 966 | */ | ||
| 967 | void scx_cmask_ref_init_kern(struct scx_sched *sch, struct scx_cmask *m, | ||
| 968 | u32 base, u32 nr_cids, struct scx_cmask_ref *ref) | ||
| 969 | { | ||
| 970 | WRITE_ONCE(m->base, base); | ||
| 971 | WRITE_ONCE(m->nr_cids, nr_cids); | ||
| 972 | WRITE_ONCE(m->alloc_words, SCX_CMASK_NR_WORDS(nr_cids)); | ||
| 973 | |||
| 974 | ref->sch = sch; | ||
| 975 | ref->src = m; | ||
| 976 | ref->base = base; | ||
| 977 | ref->nr_cids = nr_cids; | ||
| 978 | |||
| 979 | ref->shard_first = scx_cid_to_shard[base]; | ||
| 980 | if (likely(nr_cids)) | ||
| 981 | ref->shard_end = scx_cid_to_shard[base + nr_cids - 1] + 1; | ||
| 982 | else | ||
| 983 | ref->shard_end = ref->shard_first; | ||
| 984 | } | ||
| 985 | |||
| 954 | /** | 986 | /** |
| 955 | * scx_cmask_ref_shard - Read one shard from @ref into @out | 987 | * scx_cmask_ref_shard - Read one shard from @ref into @out |
| 956 | * @ref: validated ref | 988 | * @ref: validated ref |
| @@ -1032,6 +1064,44 @@ void scx_cmask_ref_copy(const struct scx_cmask_ref *ref, const struct scx_cmask · 75 unmodified lines | |||
| 1032 | src->bits, src->base, src->nr_cids, CMASK_OP2_REF_COPY); | 1064 | src->bits, src->base, src->nr_cids, CMASK_OP2_REF_COPY); |
| 1033 | } | 1065 | } |
| 1034 | 1066 | ||
| 1067 | /** | ||
| 1068 | * scx_cmask_ref_from_cpumask - Populate @ref's arena cmask from a cpumask | ||
| 1069 | * @ref: kern-bound ref, see scx_cmask_ref_init_kern() | ||
| 1070 | * @cpumask: cpus to translate into cids | ||
| 1071 | * | ||
| 1072 | * Write @ref's active range one word at a time, setting each cid's bit when | ||
| 1073 | * its cpu is in @cpumask. Offsets and length come from @ref's trusted geometry | ||
| 1074 | * and stores use WRITE_ONCE since BPF may read concurrently, so the arena | ||
| 1075 | * header is never read. | ||
| 1076 | */ | ||
| 1077 | void scx_cmask_ref_from_cpumask(const struct scx_cmask_ref *ref, | ||
| 1078 | const struct cpumask *cpumask) | ||
| 1079 | { | ||
| 1080 | struct scx_cmask *m = ref->src; | ||
| 1081 | u32 base = ref->base, nr_cids = ref->nr_cids; | ||
| 1082 | u32 wi, nr_words; | ||
| 1083 | |||
| 1084 | if (!nr_cids) | ||
| 1085 | return; | ||
| 1086 | |||
| 1087 | nr_words = (base + nr_cids - 1) / 64 - base / 64 + 1; | ||
| 1088 | for (wi = 0; wi < nr_words; wi++) { | ||
| 1089 | u32 word_first_cid = (base / 64 + wi) * 64; | ||
| 1090 | u64 word = 0; | ||
| 1091 | u32 bit; | ||
| 1092 | |||
| 1093 | for (bit = 0; bit < 64; bit++) { | ||
| 1094 | u32 cid = word_first_cid + bit; | ||
| 1095 | |||
| 1096 | if (cid < base || cid >= base + nr_cids) | ||
| 1097 | continue; | ||
| 1098 | if (cpumask_test_cpu(__scx_cid_to_cpu(cid), cpumask)) | ||
| 1099 | word |= BIT_U64(bit); | ||
| 1100 | } | ||
| 1101 | WRITE_ONCE(m->bits[wi], word); | ||
| 1102 | } | ||
| 1103 | } | ||
| 1104 | |||
| 1035 | int scx_cid_kfunc_init(void) | 1105 | int scx_cid_kfunc_init(void) |
| 1036 | { | 1106 | { |
| 1037 | return register_btf_kfunc_id_set(BPF_PROG_TYPE_STRUCT_OPS, &scx_kfunc_set_init_cids) ?: | 1107 | return register_btf_kfunc_id_set(BPF_PROG_TYPE_STRUCT_OPS, &scx_kfunc_set_init_cids) ?: |
| @@ -295,8 +295,12 @@ static inline s32 scx_cpu_ret(struct scx_sched *sch, s32 cpu_or_cid) · 294 unmodified lines | |||
| 295 | 295 | ||
| 296 | int scx_cmask_ref_init(struct scx_sched *sch, const struct scx_cmask *src, | 296 | int scx_cmask_ref_init(struct scx_sched *sch, const struct scx_cmask *src, |
| 297 | struct scx_cmask_ref *ref); | 297 | struct scx_cmask_ref *ref); |
| 298 | void scx_cmask_ref_init_kern(struct scx_sched *sch, struct scx_cmask *m, | ||
| 299 | u32 base, u32 nr_cids, struct scx_cmask_ref *ref); | ||
| 298 | void scx_cmask_ref_shard(const struct scx_cmask_ref *ref, s32 shard_idx, | 300 | void scx_cmask_ref_shard(const struct scx_cmask_ref *ref, s32 shard_idx, |
| 299 | struct scx_cmask *out); | 301 | struct scx_cmask *out); |
| 302 | void scx_cmask_ref_from_cpumask(const struct scx_cmask_ref *ref, | ||
| 303 | const struct cpumask *cpumask); | ||
| 300 | void scx_cmask_ref_or(const struct scx_cmask_ref *ref, const struct scx_cmask *src); | 304 | void scx_cmask_ref_or(const struct scx_cmask_ref *ref, const struct scx_cmask *src); |
| 301 | void scx_cmask_ref_copy(const struct scx_cmask_ref *ref, const struct scx_cmask *src); | 305 | void scx_cmask_ref_copy(const struct scx_cmask_ref *ref, const struct scx_cmask *src); |
| 302 | 306 | ||
| @@ -424,12 +424,15 @@ static inline void scx_call_op_set_cpumask(struct scx_sched *sch, struct rq *rq, · 423 unmodified lines | |||
| 424 | { | 424 | { |
| 425 | if (scx_is_cid_type()) { | 425 | if (scx_is_cid_type()) { |
| 426 | struct scx_cmask *kern_va = *this_cpu_ptr(sch->set_cmask_scratch); | 426 | struct scx_cmask *kern_va = *this_cpu_ptr(sch->set_cmask_scratch); |
| 427 | struct scx_cmask_ref ref; | ||
| 428 | |||
| 427 | /* | 429 | /* |
| 428 | * Build the per-CPU arena cmask and hand BPF its arena address. | 430 | * Build the per-cpu arena cmask from kernel geometry via @ref, |
| 429 | * Caller holds the rq lock with IRQs disabled, which makes us | 431 | * never reading its BPF-writable header, and hand BPF the arena |
| 430 | * the sole user of the scratch area. | 432 | * address. The rq lock makes this cpu the sole kernel writer. |
| 431 | */ | 433 | */ |
| 432 | scx_cpumask_to_cmask(cpumask, kern_va); | 434 | scx_cmask_ref_init_kern(sch, kern_va, 0, num_possible_cpus(), &ref); |
| 435 | scx_cmask_ref_from_cpumask(&ref, cpumask); | ||
| 433 | SCX_CALL_CID_OP_TASK(sch, set_cmask, rq, task, | 436 | SCX_CALL_CID_OP_TASK(sch, set_cmask, rq, task, |
| 434 | scx_kaddr_to_arena(sch, kern_va)); | 437 | scx_kaddr_to_arena(sch, kern_va)); |
| 435 | } else { | 438 | } else { |