Re: [PATCH] sched/core: Don't pin the idle task in migrate_disable_switch()
From: Peter Zijlstra
Date: Thu Aug 06 2026 - 03:24:47 EST
On Thu, Aug 06, 2026 at 09:17:40AM +0200, Krystian Slowik wrote:
> Since commit 650952d3fb38 ("sched: Make __do_set_cpus_allowed() use the
> sched_change pattern"), do_set_cpus_allowed() dequeues and re-enqueues
> the target task through the sched_change guard whenever it is queued.
>
> The idle task counts as queued: init_idle() sets
> idle->on_rq = TASK_ON_RQ_QUEUED. But the idle sched class implements no
> real dequeue_task() (only the "bad: scheduling from the idle thread!"
> debug stub, which even drops and re-takes the rq lock in the middle of
> the guarded section) and no enqueue_task() at all, so running the guard
> on the idle task jumps through a NULL pointer in sched_change_end():
>
> bad: scheduling from the idle thread!
> CPU: 3 UID: 0 PID: 0 Comm: swapper/3 Kdump: loaded Not tainted 7.0.0-28-generic #28-Ubuntu PREEMPT(lazy)
> Call Trace:
> dequeue_task_idle+0x29/0x50
> dequeue_task+0xfb/0x300
> sched_change_begin+0x1ff/0x240
> migrate_disable_switch.isra.0+0xf8/0x190
> __schedule+0xdd/0x650
> schedule_idle+0x22/0x40
> BUG: kernel NULL pointer dereference, address: 0000000000000000
> #PF: supervisor instruction fetch in kernel mode
> RIP: 0010:0x0
> Call Trace:
> enqueue_task+0x89/0x1d0
> sched_change_end+0x18e/0x1d0
> migrate_disable_switch.isra.0+0x11e/0x190
> __schedule+0xdd/0x650
> schedule_idle+0x22/0x40
> do_idle+0xb6/0xf0
> cpu_startup_entry+0x29/0x30
> start_secondary+0x125/0x180
>
> The path is reachable since commit 942b8db96500 ("sched: Fix
> migrate_disable_switch() locking") moved migrate_disable_switch() to
> the top of __schedule(), where it runs on every schedule out of the
> idle loop rather than only on an actual context switch: any
> migrate_disable() taken in the idle loop (e.g. from a tracing or BPF
> callback) that is still held when the idle task schedules triggers the
> pinning path.
>
> Pinning the idle task is meaningless to begin with: it is a per-CPU
> task that can never migrate. Skip it. This also keeps
> ___migrate_enable() unreachable for the idle task, since its cpus_ptr
> is never repointed.
>
> The check uses p == rq->idle rather than is_idle_task(), because the
> latter also matches idle-injection threads (PF_IDLE), which are
> ordinary queueable tasks.
>
> Observed in production on two separate x86-64 machines running the
> Ubuntu 7.0.0-28 kernel, both panicking from the idle loop with the
> oops above.
What is actually doing migrate_disable() here? Why would the idle thread
ever hit this...