[PATCH v4 0/5] sched/debug: Introduce per-CPU debugfs files
From: Aaron Tomlin
Date: Sun Aug 09 2026 - 21:58:29 EST
Hi Peter, Juri, Ingo, Vincent,
This patch series addresses a few pre-existing memory safety and list
traversal concurrency issues in scheduler debugfs handlers, and introduces
per-CPU debugfs files under /sys/kernel/debug/sched/cpu/cpu<N>/debug.
Patch 1 fixes a potential use-after-free in print_cpu() where rq->curr is
dereferenced locklessly to output the running task's PID. If the task exits
concurrently and its reference count drops to zero, put_task_struct()
schedules __put_task_struct_rcu_cb() via call_rcu(). Without holding an RCU
read lock, an RCU grace period can elapse concurrently and free the task
structure via free_task(), leading to a use-after-free race condition. This
patch protects rq->curr access using rcu_dereference() inside an RCU
read-side critical section, ensuring sparse compliance.
Patch 2 fixes a use-after-free in print_dl_rq() where cpu_rq(cpu)->rd is
dereferenced locklessly to display deadline bandwidth statistics. During
CPU hot-unplug or cgroup cpuset repartitioning events,
partition_sched_domains() calls rq_attach_root() to detach the CPU from its
root_domain and schedules free_rootdomain() via call_rcu(). Without an RCU
read lock, an RCU grace period can resolve concurrently while debugfs reads
the file, allowing free_rootdomain() to execute kfree() and causing a UAF
when reading dl_bw->bw. This patch adds rcu_assign_pointer() on the writer
side in rq_attach_root() and fetches rq->rd using READ_ONCE() inside an RCU
read-side critical section in print_dl_rq().
Patch 3 fixes a time-of-check to time-of-use race condition in
sched_show_numa(), where p->mm is checked locklessly and then passed to
P(mm->numa_scan_seq), causing a double-evaluation of p->mm. If the task
exits concurrently (exit_mm(p)), p->mm can be set to NULL on another CPU
between the check and the macro expansion, causing a NULL-pointer
dereference crash. This patch reads p->mm once into a local variable using
READ_ONCE(p->mm) before checking and dereferencing its numa_scan_seq field.
Patch 4 fixes an RCU traversal violation in print_cfs_stats() where
rq->leaf_cfs_rq_list is traversed locklessly using
for_each_leaf_cfs_rq_safe(), which expands to list_for_each_entry_safe().
Although leaf_cfs_rq_list is modified using list_add_rcu(),
list_for_each_entry_safe() lacks READ_ONCE(), allowing compiler reordering
or re-fetching that can cause readers to observe newly inserted cfs_rq
nodes before internal fields are initialised. This patch introduces
for_each_leaf_cfs_rq_rcu() using list_for_each_entry_rcu() and adds a hard
iteration ceiling to prevent RCU stalls under list churn.
Patch 5 introduces per-CPU debugfs entries under
/sys/kernel/debug/sched/cpu/, allowing targeted inspection of an individual
CPU's runqueue on demand. If the target CPU is currently offline, reading
its file returns -ENODEV.
Changes since v3:
- Updated Patch 1 to use rcu_dereference(rq->curr) instead of READ_ONCE()
to preserve __rcu
- Added missing writer-side RCU publication barrier (rcu_assign_pointer())
in rq_attach_root() for Patch 2
- Added Patch 3 to fix a TOCTOU condition in sched_show_numa() using
READ_ONCE(p->mm)
- Added a safety iteration ceiling in print_cfs_stats() for Patch 4 to
prevent unbounded list iteration and RCU stalls under heavy
leaf_cfs_rq_list churn
- Linked to v3: https://lore.kernel.org/lkml/20260808235522.380038-1-atomlin@xxxxxxxxxxx/
Changes since v2:
- Protected lockless rq->curr dereferencing in print_cpu() with
rcu_read_lock() and READ_ONCE()
- Protected lockless rq->rd dereferencing in print_dl_rq() against CPU
hot-unplug and cgroup cpuset repartitioning races
- Introduced for_each_leaf_cfs_rq_rcu() using list_for_each_entry_rcu()
for lockless leaf_cfs_rq_list iteration
- Linked to v2: https://lore.kernel.org/lkml/20260728205238.18447-1-atomlin@xxxxxxxxxxx/
Changes since v1:
- Reframed commit message motivation around targeted interactive
debugging on large SMP topologies (Peter Zijlstra and Zhan Xusheng)
- Gated sched_debug_cpu_show() with a cpu_online(cpu) check
returning -ENODEV when target CPU is offline (Zhan Xusheng)
- Linked to v1: https://lore.kernel.org/lkml/20260728020309.6169-1-atomlin@xxxxxxxxxxx/
Aaron Tomlin (5):
sched/debug: Protect lockless rq->rd access in print_dl_rq()
sched/debug: Protect lockless rq->curr access in print_cpu()
sched/debug: Protect lockless p->mm access in sched_show_numa()
sched/fair: Use list_for_each_entry_rcu() in print_cfs_stats()
sched/debug: Introduce per-CPU debugfs files
kernel/sched/debug.c | 66 +++++++++++++++++++++++++++++++++++++----
kernel/sched/fair.c | 17 +++++++++--
kernel/sched/topology.c | 2 +-
3 files changed, 76 insertions(+), 9 deletions(-)
--
2.55.0