Re: [PATCH 04/10] sched_ext: Split curr|donor references properly

[email protected] Mon, 13 Jul 2026 16:48:06 +0000
Newsgroups dev.linux.lists.sched-ext
Message-ID <[email protected]>
Thank you for your contribution! Sashiko AI review found 4 potential issue(s) to consider:
- [High] Missing conversion from `task_current()` to `task_current_donor()` in sched_ext task state management skips BPF callbacks and corrupts task state.
- [High] BPF kfuncs expose the physical execution context instead of the scheduling context, violating the commit's design principle.
- [High] `dequeue_task_scx` unconditionally calls `update_curr_scx`, which steals execution time from non-EXT proxy donors and corrupts their accounting.
- [High] scx_can_stop_tick() stops the tick for FAIR donors executing via EXT owners, bypassing CFS bandwidth constraints.
--

--- Patch [4]: [PATCH 04/10] sched_ext: Split curr|donor references properly ---
commit ed60d9105c70ea6983b5253a3067b0440935ec2f
Author: Andrea Righi <[email protected]>

sched_ext: Split curr|donor references properly

With proxy execution, the task selected by the scheduler and the task
physically executing can differ. A blocked mutex waiter donates its
scheduling context to the lock owner.

> diff --git a/kernel/sched/ext/ext.c b/kernel/sched/ext/ext.c
> index 1412239d37f52..755ea5cc6169f 100644
> --- a/kernel/sched/ext/ext.c
> +++ b/kernel/sched/ext/ext.c
> @@ -1170,17 +1170,24 @@ static void touch_core_sched_dispatch(struct rq *rq, struct task_struct *p)
>  
>  static void update_curr_scx(struct rq *rq)
>  {
> -	struct task_struct *curr = rq->curr;
> +	struct task_struct *donor;
>  	s64 delta_exec;
>  
> +	/*
> +	 * update_curr_scx() is selected through rq->donor->sched_class, not
> +	 * rq->curr->sched_class, so @donor is always an EXT task here. If an EXT
> +	 * owner executes for a FAIR donor, FAIR's update_curr() runs instead.
> +	 */
> +	donor = rq->donor;
> +
>  	delta_exec = update_curr_common(rq);
>  	if (unlikely(delta_exec <= 0))
>  		return;
>  
> -	if (curr->scx.slice != SCX_SLICE_INF) {
> -		curr->scx.slice -= min_t(u64, curr->scx.slice, delta_exec);
> -		if (!curr->scx.slice)
> -			touch_core_sched(rq, curr);
> +	if (donor->scx.slice != SCX_SLICE_INF) {
> +		donor->scx.slice -= min_t(u64, donor->scx.slice, delta_exec);
> +		if (!donor->scx.slice)
> +			touch_core_sched(rq, donor);
>  	}

[Severity: High]
Does this steal execution time from non-EXT proxy donors?

If an EXT task executes on behalf of a FAIR donor, dequeuing the EXT owner
calls update_curr_scx() from dequeue_task_scx() because it evaluates
task_current(rq, p) which matches the EXT owner.

This code advances rq->donor->se.exec_start via update_curr_common() and
mutates the non-EXT donor's slice. Will this bypass CFS vruntime and
bandwidth (throttling) accounting, starving the FAIR task of its tracked
CPU time?

[ ... ]

> @@ -4081,14 +4092,14 @@ static void run_deferred(struct rq *rq)
>  #ifdef CONFIG_NO_HZ_FULL
>  bool scx_can_stop_tick(struct rq *rq)
>  {
> -	struct task_struct *p = rq->curr;
> +	struct task_struct *p = rq->donor;
>  	struct scx_sched *sch = scx_task_sched(p);
>  
>  	if (p->sched_class != &ext_sched_class)
>  		return true;

[Severity: High]
Does this incorrectly bypass CFS bandwidth constraints for FAIR donors?

If a CPU is running a bandwidth-constrained FAIR donor through an EXT owner
on a NOHZ CPU, this returns true.

This delegates tick responsibility to core generic checks, which mistakenly
evaluate the EXT owner (rq->curr) instead of the FAIR donor in
__need_bw_check. Can this allow the FAIR task to bypass CFS quota limits?

[ ... ]

[Severity: High]
Are we missing conversions from task_current() to checking the donor in the
enqueue and dequeue paths?

In dequeue_task_scx(), if we have a blocked EXT proxy donor task (where
rq->donor == p but rq->curr != p) that is dequeued, task_current(rq, p)
evaluates to false:

    if (task_current(rq, p) &&
        (p->scx.flags & SCX_TASK_RUN_TRACKED)) {

Does this incorrectly skip the BPF stopping() callback and leak the
SCX_TASK_RUN_TRACKED state?

Similarly, in enqueue_task_scx():

    if (unlikely(enq_flags & ENQUEUE_RESTORE) && task_current(rq, p))
        sticky_cpu = cpu_of(rq);

Does this fail to preserve sticky_cpu for an actively running donor because
task_current(rq, p) is false, breaking the restore invariants?

[Severity: High]
Do the BPF kfuncs expose the physical execution context instead of the
scheduling context?

In scx_bpf_task_running():

    return task_rq(p)->curr == p;

This returns false for an active EXT donor because it checks the owner
(rq->curr) instead of the donor.

In scx_bpf_cpu_curr():

    return rcu_dereference(cpu_rq(cpu)->curr);

And in scx_bpf_cid_curr():

    return rcu_dereference(cpu_rq(cpu)->curr);

Do these expose the physical non-EXT owner to the BPF scheduler instead
of the expected EXT donor, causing the BPF scheduler to make incorrect
load-balancing decisions based on invalid task states?

-- 
Sashiko AI review ยท https://sashiko.dev/#/patchset/[email protected]?part=4