* [PATCH v4 0/5] sched/debug: Introduce per-CPU debugfs files
@ 2026-08-10 1:58 Aaron Tomlin
2026-08-10 1:58 ` [PATCH v4 1/5] sched/debug: Protect lockless rq->rd access in print_dl_rq() Aaron Tomlin
` (4 more replies)
0 siblings, 5 replies; 8+ messages in thread
From: Aaron Tomlin @ 2026-08-10 1:58 UTC (permalink / raw)
To: mingo, peterz, juri.lelli, vincent.guittot
Cc: dietmar.eggemann, rostedt, bsegall, mgorman, vschneid,
kprateek.nayak, zhanxusheng1024, neelx, chjohnst, mproche, sean,
steve, linux-kernel
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@atomlin.com/
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@atomlin.com/
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@atomlin.com/
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
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH v4 1/5] sched/debug: Protect lockless rq->rd access in print_dl_rq()
2026-08-10 1:58 [PATCH v4 0/5] sched/debug: Introduce per-CPU debugfs files Aaron Tomlin
@ 2026-08-10 1:58 ` Aaron Tomlin
2026-08-10 6:50 ` K Prateek Nayak
2026-08-10 13:53 ` Daniel Vacek
2026-08-10 1:58 ` [PATCH v4 2/5] sched/debug: Protect lockless rq->curr access in print_cpu() Aaron Tomlin
` (3 subsequent siblings)
4 siblings, 2 replies; 8+ messages in thread
From: Aaron Tomlin @ 2026-08-10 1:58 UTC (permalink / raw)
To: mingo, peterz, juri.lelli, vincent.guittot
Cc: dietmar.eggemann, rostedt, bsegall, mgorman, vschneid,
kprateek.nayak, zhanxusheng1024, neelx, chjohnst, mproche, sean,
steve, linux-kernel
In print_dl_rq(), 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 cpu_attach_domain(), which executes
rq_attach_root() to detach the CPU from its root_domain. When the
reference count of the detached root_domain drops to zero,
rq_attach_root() calls call_rcu(&old_rd->rcu, free_rootdomain) to
schedule memory teardown after an RCU grace period.
However, rq_attach_root() previously updated rq->rd using a plain C store
without an RCU publication barrier (i.e., rcu_assign_pointer()). Without a
release memory barrier on the writer side, CPU or compiler reordering could
allow the new rq->rd pointer store to become visible to other CPUs before
the initialization writes to rd->dl_bw are committed.
Furthermore, because print_dl_rq() did not hold an RCU read lock while
dereferencing cpu_rq(cpu)->rd, an RCU grace period could elapse
concurrently while debugfs is reading the file, allowing
free_rootdomain() to execute kfree(old_rd) and causing a use-after-free
race condition when print_dl_rq() reads dl_bw->bw.
Resolve this by using rcu_assign_pointer(rq->rd, rd) in rq_attach_root() to
guarantee a release memory barrier when publishing a root_domain.
Finally, fetch rq->rd using READ_ONCE() inside an RCU read-side critical
section in print_dl_rq().
Fixes: 02968ccf7b80 ("sched: add /proc/sched_debug file")
Reported-by: sashiko-bot <sashiko-bot@kernel.org>
Signed-off-by: Aaron Tomlin <atomlin@atomlin.com>
---
kernel/sched/debug.c | 12 +++++++++---
kernel/sched/topology.c | 2 +-
2 files changed, 10 insertions(+), 4 deletions(-)
diff --git a/kernel/sched/debug.c b/kernel/sched/debug.c
index 40584b27ea0c..2c2156dfab00 100644
--- a/kernel/sched/debug.c
+++ b/kernel/sched/debug.c
@@ -1081,6 +1081,7 @@ void print_rt_rq(struct seq_file *m, int cpu, struct rt_rq *rt_rq)
void print_dl_rq(struct seq_file *m, int cpu, struct dl_rq *dl_rq)
{
struct dl_bw *dl_bw;
+ struct root_domain *rd;
SEQ_printf(m, "\n");
SEQ_printf(m, "dl_rq[%d]:\n", cpu);
@@ -1089,9 +1090,14 @@ void print_dl_rq(struct seq_file *m, int cpu, struct dl_rq *dl_rq)
SEQ_printf(m, " .%-30s: %lu\n", #x, (unsigned long)(dl_rq->x))
PU(dl_nr_running);
- dl_bw = &cpu_rq(cpu)->rd->dl_bw;
- SEQ_printf(m, " .%-30s: %lld\n", "dl_bw->bw", dl_bw->bw);
- SEQ_printf(m, " .%-30s: %lld\n", "dl_bw->total_bw", dl_bw->total_bw);
+ rcu_read_lock();
+ rd = READ_ONCE(cpu_rq(cpu)->rd);
+ if (rd) {
+ dl_bw = &rd->dl_bw;
+ SEQ_printf(m, " .%-30s: %lld\n", "dl_bw->bw", dl_bw->bw);
+ SEQ_printf(m, " .%-30s: %lld\n", "dl_bw->total_bw", dl_bw->total_bw);
+ }
+ rcu_read_unlock();
#undef PU
}
diff --git a/kernel/sched/topology.c b/kernel/sched/topology.c
index 622e2e01974c..b411cc00029c 100644
--- a/kernel/sched/topology.c
+++ b/kernel/sched/topology.c
@@ -496,7 +496,7 @@ void rq_attach_root(struct rq *rq, struct root_domain *rd)
}
atomic_inc(&rd->refcount);
- rq->rd = rd;
+ rcu_assign_pointer(rq->rd, rd);
cpumask_set_cpu(rq->cpu, rd->span);
if (cpumask_test_cpu(rq->cpu, cpu_active_mask))
--
2.55.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v4 2/5] sched/debug: Protect lockless rq->curr access in print_cpu()
2026-08-10 1:58 [PATCH v4 0/5] sched/debug: Introduce per-CPU debugfs files Aaron Tomlin
2026-08-10 1:58 ` [PATCH v4 1/5] sched/debug: Protect lockless rq->rd access in print_dl_rq() Aaron Tomlin
@ 2026-08-10 1:58 ` Aaron Tomlin
2026-08-10 1:58 ` [PATCH v4 3/5] sched/debug: Protect lockless p->mm access in sched_show_numa() Aaron Tomlin
` (2 subsequent siblings)
4 siblings, 0 replies; 8+ messages in thread
From: Aaron Tomlin @ 2026-08-10 1:58 UTC (permalink / raw)
To: mingo, peterz, juri.lelli, vincent.guittot
Cc: dietmar.eggemann, rostedt, bsegall, mgorman, vschneid,
kprateek.nayak, zhanxusheng1024, neelx, chjohnst, mproche, sean,
steve, linux-kernel
In print_cpu(), rq->curr is dereferenced locklessly to print the current
task's PID via task_pid_nr(rq->curr).
While accessing /sys/kernel/debug/sched/debug is inherently best-effort
only; rq->curr is indeed expected to change dynamically while
print_cpu() is executing. However, if the task currently running on the
CPU exits concurrently and its reference count drops to zero,
put_task_struct() calls call_rcu() to schedule
__put_task_struct_rcu_cb(). Because print_cpu() does not hold the RCU
read lock while dereferencing rq->curr, an RCU grace period can complete
concurrently and free the task structure via free_task(), creating a
potential use-after-free race condition.
Resolve this by reading rq->curr using rcu_dereference(rq->curr) inside an
RCU read-side critical section. Holding the RCU read lock delays the
invocation of __put_task_struct_rcu_cb() until after rcu_read_unlock(),
ensuring that the struct task_struct memory remains valid while being
accessed.
Fixes: 02968ccf7b80 ("sched: add /proc/sched_debug file")
Reported-by: sashiko-bot <sashiko-bot@kernel.org>
Signed-off-by: Aaron Tomlin <atomlin@atomlin.com>
---
kernel/sched/debug.c | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/kernel/sched/debug.c b/kernel/sched/debug.c
index 2c2156dfab00..c3409166a288 100644
--- a/kernel/sched/debug.c
+++ b/kernel/sched/debug.c
@@ -1132,7 +1132,10 @@ do { \
P(nr_switches);
P(nr_uninterruptible);
PN(next_balance);
- SEQ_printf(m, " .%-30s: %ld\n", "curr->pid", (long)(task_pid_nr(rq->curr)));
+ rcu_read_lock();
+ SEQ_printf(m, " .%-30s: %ld\n", "curr->pid",
+ (long)(task_pid_nr(rcu_dereference(rq->curr))));
+ rcu_read_unlock();
PN(clock);
PN(clock_task);
#undef P
--
2.55.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v4 3/5] sched/debug: Protect lockless p->mm access in sched_show_numa()
2026-08-10 1:58 [PATCH v4 0/5] sched/debug: Introduce per-CPU debugfs files Aaron Tomlin
2026-08-10 1:58 ` [PATCH v4 1/5] sched/debug: Protect lockless rq->rd access in print_dl_rq() Aaron Tomlin
2026-08-10 1:58 ` [PATCH v4 2/5] sched/debug: Protect lockless rq->curr access in print_cpu() Aaron Tomlin
@ 2026-08-10 1:58 ` Aaron Tomlin
2026-08-10 1:58 ` [PATCH v4 4/5] sched/fair: Use list_for_each_entry_rcu() in print_cfs_stats() Aaron Tomlin
2026-08-10 1:58 ` [PATCH v4 5/5] sched/debug: Introduce per-CPU debugfs files Aaron Tomlin
4 siblings, 0 replies; 8+ messages in thread
From: Aaron Tomlin @ 2026-08-10 1:58 UTC (permalink / raw)
To: mingo, peterz, juri.lelli, vincent.guittot
Cc: dietmar.eggemann, rostedt, bsegall, mgorman, vschneid,
kprateek.nayak, zhanxusheng1024, neelx, chjohnst, mproche, sean,
steve, linux-kernel
In sched_show_numa(), p->mm is checked locklessly and then passed to the
P(mm->numa_scan_seq) macro. This results in a double-evaluation of p->mm.
If the task exits concurrently via exit_mm(p) between the check and the
macro expansion, p->mm can be set to NULL on another CPU. The second
evaluation then dereferences a NULL pointer.
Fix this TOCTOU race by reading p->mm once into a local variable using
READ_ONCE(p->mm) before checking and dereferencing its numa_scan_seq
field.
Fixes: b32e86b4301e ("sched/numa: Add debugging")
Reported-by: sashiko-bot <sashiko-bot@kernel.org>
Signed-off-by: Aaron Tomlin <atomlin@atomlin.com>
---
kernel/sched/debug.c | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/kernel/sched/debug.c b/kernel/sched/debug.c
index c3409166a288..f5494e02f600 100644
--- a/kernel/sched/debug.c
+++ b/kernel/sched/debug.c
@@ -1317,8 +1317,10 @@ void print_numa_stats(struct seq_file *m, int node, unsigned long tsf,
static void sched_show_numa(struct task_struct *p, struct seq_file *m)
{
#ifdef CONFIG_NUMA_BALANCING
- if (p->mm)
- P(mm->numa_scan_seq);
+ struct mm_struct *mm = READ_ONCE(p->mm);
+
+ if (mm)
+ __PS("mm->numa_scan_seq", mm->numa_scan_seq);
P(numa_pages_migrated);
P(numa_preferred_nid);
--
2.55.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v4 4/5] sched/fair: Use list_for_each_entry_rcu() in print_cfs_stats()
2026-08-10 1:58 [PATCH v4 0/5] sched/debug: Introduce per-CPU debugfs files Aaron Tomlin
` (2 preceding siblings ...)
2026-08-10 1:58 ` [PATCH v4 3/5] sched/debug: Protect lockless p->mm access in sched_show_numa() Aaron Tomlin
@ 2026-08-10 1:58 ` Aaron Tomlin
2026-08-10 1:58 ` [PATCH v4 5/5] sched/debug: Introduce per-CPU debugfs files Aaron Tomlin
4 siblings, 0 replies; 8+ messages in thread
From: Aaron Tomlin @ 2026-08-10 1:58 UTC (permalink / raw)
To: mingo, peterz, juri.lelli, vincent.guittot
Cc: dietmar.eggemann, rostedt, bsegall, mgorman, vschneid,
kprateek.nayak, zhanxusheng1024, neelx, chjohnst, mproche, sean,
steve, linux-kernel
In print_cfs_stats(), rq->leaf_cfs_rq_list is traversed using
for_each_leaf_cfs_rq_safe(), which expands to list_for_each_entry_safe().
Although rq->leaf_cfs_rq_list is RCU-protected, list_for_each_entry_safe()
is a non-RCU iteration macro. It dereferences pointer links without
READ_ONCE() and pre-fetches the next pointer.
When a writer concurrently adds a new cfs_rq to the list using
list_add_rcu(), a reader traversing with list_for_each_entry_safe()
lacks READ_ONCE() protection. Without READ_ONCE(), the compiler is free
to re-fetch pointers or reorder instructions. As a result, the reader
can observe a newly inserted cfs_rq's pointer before its internal fields
are fully visible, leading to reading uninitialised data or
dereferencing invalid pointers.
Additionally, because print_cfs_rq() drops rq->lock during seq_file I/O,
concurrent cfs_rq list removals and re-insertions can modify
cfs_rq->next. If cfs_rq is re-inserted near the head of the list while
rq->lock is dropped, lockless readers can jump backward in the list.
Under sufficient load it could lead to unbounded list traversal inside
the RCU read-side critical section and trigger an RCU stall.
Fix this by introducing for_each_leaf_cfs_rq_rcu(), which expands to
list_for_each_entry_rcu(). This uses READ_ONCE() during list traversal.
Finally, capping print_cfs_stats() lockless list traversal with a hard
iteration ceiling to guarantee loop termination and prevent RCU stalls
under continuous list churn.
Fixes: 0601267ca673 ("sched/fair: Rewrite list_add_leaf_cfs_rq()")
Reported-by: sashiko-bot <sashiko-bot@kernel.org>
Signed-off-by: Aaron Tomlin <atomlin@atomlin.com>
---
kernel/sched/fair.c | 17 +++++++++++++++--
1 file changed, 15 insertions(+), 2 deletions(-)
diff --git a/kernel/sched/fair.c b/kernel/sched/fair.c
index d78467ec6ee1..eb03323332b0 100644
--- a/kernel/sched/fair.c
+++ b/kernel/sched/fair.c
@@ -412,6 +412,10 @@ static inline void assert_list_leaf_cfs_rq(struct rq *rq)
list_for_each_entry_safe(cfs_rq, pos, &rq->leaf_cfs_rq_list, \
leaf_cfs_rq_list)
+#define for_each_leaf_cfs_rq_rcu(rq, cfs_rq) \
+ list_for_each_entry_rcu(cfs_rq, &(rq)->leaf_cfs_rq_list, \
+ leaf_cfs_rq_list)
+
/* Do the two (enqueued) entities belong to the same group ? */
static inline struct cfs_rq *
is_same_group(struct sched_entity *se, struct sched_entity *pse)
@@ -497,6 +501,9 @@ static inline void assert_list_leaf_cfs_rq(struct rq *rq)
#define for_each_leaf_cfs_rq_safe(rq, cfs_rq, pos) \
for (cfs_rq = &rq->cfs, pos = NULL; cfs_rq; cfs_rq = pos)
+#define for_each_leaf_cfs_rq_rcu(rq, cfs_rq) \
+ for (cfs_rq = &rq->cfs; cfs_rq; cfs_rq = NULL)
+
static inline struct sched_entity *parent_entity(struct sched_entity *se)
{
return NULL;
@@ -15399,13 +15406,19 @@ DEFINE_SCHED_CLASS(fair) = {
#endif
};
+#define SCHED_DEBUG_MAX_ITER 1024
+
void print_cfs_stats(struct seq_file *m, int cpu)
{
- struct cfs_rq *cfs_rq, *pos;
+ struct cfs_rq *cfs_rq;
+ int max_iter = SCHED_DEBUG_MAX_ITER;
rcu_read_lock();
- for_each_leaf_cfs_rq_safe(cpu_rq(cpu), cfs_rq, pos)
+ for_each_leaf_cfs_rq_rcu(cpu_rq(cpu), cfs_rq) {
+ if (--max_iter < 0)
+ break;
print_cfs_rq(m, cpu, cfs_rq);
+ }
rcu_read_unlock();
}
--
2.55.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* [PATCH v4 5/5] sched/debug: Introduce per-CPU debugfs files
2026-08-10 1:58 [PATCH v4 0/5] sched/debug: Introduce per-CPU debugfs files Aaron Tomlin
` (3 preceding siblings ...)
2026-08-10 1:58 ` [PATCH v4 4/5] sched/fair: Use list_for_each_entry_rcu() in print_cfs_stats() Aaron Tomlin
@ 2026-08-10 1:58 ` Aaron Tomlin
4 siblings, 0 replies; 8+ messages in thread
From: Aaron Tomlin @ 2026-08-10 1:58 UTC (permalink / raw)
To: mingo, peterz, juri.lelli, vincent.guittot
Cc: dietmar.eggemann, rostedt, bsegall, mgorman, vschneid,
kprateek.nayak, zhanxusheng1024, neelx, chjohnst, mproche, sean,
steve, linux-kernel
Currently, accessing scheduler debugging details for a specific CPU
requires reading /sys/kernel/debug/sched/debug, which outputs
information for all online CPUs. When investigating a latency anomaly or
scheduling issue isolated to a specific CPU, accessing
/sys/kernel/debug/sched/cpu/cpu<N>/debug provides an immediate, targeted
view of that runqueue.
Add support for per-CPU debug files under:
/sys/kernel/debug/sched/cpu/cpu<N>/debug. Reading
/sys/kernel/debug/sched/cpu/cpu<N>/debug calls print_cpu() specifically
for CPU <N>, exposing CPU-specific runqueue details on demand. If the
target CPU is currently offline, reading its file returns -ENODEV.
Signed-off-by: Aaron Tomlin <atomlin@atomlin.com>
---
kernel/sched/debug.c | 43 +++++++++++++++++++++++++++++++++++++++++++
1 file changed, 43 insertions(+)
diff --git a/kernel/sched/debug.c b/kernel/sched/debug.c
index f5494e02f600..22e917db86fa 100644
--- a/kernel/sched/debug.c
+++ b/kernel/sched/debug.c
@@ -356,6 +356,7 @@ static const struct file_operations sched_verbose_fops = {
};
static const struct seq_operations sched_debug_sops;
+static void print_cpu(struct seq_file *m, int cpu);
static int sched_debug_open(struct inode *inode, struct file *filp)
{
@@ -633,6 +634,47 @@ static void debugfs_fair_server_init(void)
}
}
+static int sched_debug_cpu_show(struct seq_file *m, void *v)
+{
+ unsigned long cpu = (unsigned long) m->private;
+
+ if (!cpu_online(cpu))
+ return -ENODEV;
+
+ print_cpu(m, cpu);
+ return 0;
+}
+
+static int sched_debug_cpu_open(struct inode *inode, struct file *filp)
+{
+ return single_open(filp, sched_debug_cpu_show, inode->i_private);
+}
+
+static const struct file_operations sched_debug_cpu_fops = {
+ .open = sched_debug_cpu_open,
+ .read = seq_read,
+ .llseek = seq_lseek,
+ .release = single_release,
+};
+
+static __init void debugfs_cpu_init(void)
+{
+ struct dentry *d_cpu_dir;
+ unsigned long cpu;
+ char buf[16];
+
+ d_cpu_dir = debugfs_create_dir("cpu", debugfs_sched);
+
+ for_each_possible_cpu(cpu) {
+ struct dentry *d_cpu;
+
+ snprintf(buf, sizeof(buf), "cpu%lu", cpu);
+ d_cpu = debugfs_create_dir(buf, d_cpu_dir);
+
+ debugfs_create_file("debug", 0444, d_cpu, (void *) cpu, &sched_debug_cpu_fops);
+ }
+}
+
static __init int sched_init_debug(void)
{
struct dentry __maybe_unused *numa, *llc;
@@ -690,6 +732,7 @@ static __init int sched_init_debug(void)
#ifdef CONFIG_SCHED_CLASS_EXT
debugfs_ext_server_init();
#endif
+ debugfs_cpu_init();
return 0;
}
--
2.55.0
^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH v4 1/5] sched/debug: Protect lockless rq->rd access in print_dl_rq()
2026-08-10 1:58 ` [PATCH v4 1/5] sched/debug: Protect lockless rq->rd access in print_dl_rq() Aaron Tomlin
@ 2026-08-10 6:50 ` K Prateek Nayak
2026-08-10 13:53 ` Daniel Vacek
1 sibling, 0 replies; 8+ messages in thread
From: K Prateek Nayak @ 2026-08-10 6:50 UTC (permalink / raw)
To: Aaron Tomlin, mingo, peterz, juri.lelli, vincent.guittot
Cc: dietmar.eggemann, rostedt, bsegall, mgorman, vschneid,
zhanxusheng1024, neelx, chjohnst, mproche, sean, steve,
linux-kernel
Hello Aaron,
On 8/10/2026 7:28 AM, Aaron Tomlin wrote:
> @@ -1089,9 +1090,14 @@ void print_dl_rq(struct seq_file *m, int cpu, struct dl_rq *dl_rq)
> SEQ_printf(m, " .%-30s: %lu\n", #x, (unsigned long)(dl_rq->x))
>
> PU(dl_nr_running);
> - dl_bw = &cpu_rq(cpu)->rd->dl_bw;
> - SEQ_printf(m, " .%-30s: %lld\n", "dl_bw->bw", dl_bw->bw);
> - SEQ_printf(m, " .%-30s: %lld\n", "dl_bw->total_bw", dl_bw->total_bw);
> + rcu_read_lock();
> + rd = READ_ONCE(cpu_rq(cpu)->rd);
nit. Why not rcu_dereference_all() here to keep symmetry with the
update side that now uses rcu_assign_pointer()?
> + if (rd) {
I don't think rq->rd can ever be NULL after the scheduler has
initialized.
> + dl_bw = &rd->dl_bw;
> + SEQ_printf(m, " .%-30s: %lld\n", "dl_bw->bw", dl_bw->bw);
> + SEQ_printf(m, " .%-30s: %lld\n", "dl_bw->total_bw", dl_bw->total_bw);
> + }
> + rcu_read_unlock();
>
> #undef PU
> }
--
Thanks and Regards,
Prateek
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v4 1/5] sched/debug: Protect lockless rq->rd access in print_dl_rq()
2026-08-10 1:58 ` [PATCH v4 1/5] sched/debug: Protect lockless rq->rd access in print_dl_rq() Aaron Tomlin
2026-08-10 6:50 ` K Prateek Nayak
@ 2026-08-10 13:53 ` Daniel Vacek
1 sibling, 0 replies; 8+ messages in thread
From: Daniel Vacek @ 2026-08-10 13:53 UTC (permalink / raw)
To: Aaron Tomlin
Cc: mingo, peterz, juri.lelli, vincent.guittot, dietmar.eggemann,
rostedt, bsegall, mgorman, vschneid, kprateek.nayak,
zhanxusheng1024, chjohnst, mproche, sean, steve, linux-kernel
On Mon, 10 Aug 2026 at 03:58, Aaron Tomlin <atomlin@atomlin.com> wrote:
> In print_dl_rq(), 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 cpu_attach_domain(), which executes
> rq_attach_root() to detach the CPU from its root_domain. When the
> reference count of the detached root_domain drops to zero,
> rq_attach_root() calls call_rcu(&old_rd->rcu, free_rootdomain) to
> schedule memory teardown after an RCU grace period.
>
> However, rq_attach_root() previously updated rq->rd using a plain C store
> without an RCU publication barrier (i.e., rcu_assign_pointer()). Without a
> release memory barrier on the writer side, CPU or compiler reordering could
> allow the new rq->rd pointer store to become visible to other CPUs before
> the initialization writes to rd->dl_bw are committed.
>
> Furthermore, because print_dl_rq() did not hold an RCU read lock while
> dereferencing cpu_rq(cpu)->rd, an RCU grace period could elapse
> concurrently while debugfs is reading the file, allowing
> free_rootdomain() to execute kfree(old_rd) and causing a use-after-free
> race condition when print_dl_rq() reads dl_bw->bw.
>
> Resolve this by using rcu_assign_pointer(rq->rd, rd) in rq_attach_root() to
> guarantee a release memory barrier when publishing a root_domain.
> Finally, fetch rq->rd using READ_ONCE() inside an RCU read-side critical
> section in print_dl_rq().
>
> Fixes: 02968ccf7b80 ("sched: add /proc/sched_debug file")
> Reported-by: sashiko-bot <sashiko-bot@kernel.org>
> Signed-off-by: Aaron Tomlin <atomlin@atomlin.com>
> ---
> kernel/sched/debug.c | 12 +++++++++---
> kernel/sched/topology.c | 2 +-
> 2 files changed, 10 insertions(+), 4 deletions(-)
>
> diff --git a/kernel/sched/debug.c b/kernel/sched/debug.c
> index 40584b27ea0c..2c2156dfab00 100644
> --- a/kernel/sched/debug.c
> +++ b/kernel/sched/debug.c
> @@ -1081,6 +1081,7 @@ void print_rt_rq(struct seq_file *m, int cpu, struct rt_rq *rt_rq)
> void print_dl_rq(struct seq_file *m, int cpu, struct dl_rq *dl_rq)
> {
> struct dl_bw *dl_bw;
> + struct root_domain *rd;
>
> SEQ_printf(m, "\n");
> SEQ_printf(m, "dl_rq[%d]:\n", cpu);
> @@ -1089,9 +1090,14 @@ void print_dl_rq(struct seq_file *m, int cpu, struct dl_rq *dl_rq)
> SEQ_printf(m, " .%-30s: %lu\n", #x, (unsigned long)(dl_rq->x))
>
> PU(dl_nr_running);
> - dl_bw = &cpu_rq(cpu)->rd->dl_bw;
> - SEQ_printf(m, " .%-30s: %lld\n", "dl_bw->bw", dl_bw->bw);
> - SEQ_printf(m, " .%-30s: %lld\n", "dl_bw->total_bw", dl_bw->total_bw);
> + rcu_read_lock();
> + rd = READ_ONCE(cpu_rq(cpu)->rd);
> + if (rd) {
> + dl_bw = &rd->dl_bw;
> + SEQ_printf(m, " .%-30s: %lld\n", "dl_bw->bw", dl_bw->bw);
> + SEQ_printf(m, " .%-30s: %lld\n", "dl_bw->total_bw", dl_bw->total_bw);
> + }
> + rcu_read_unlock();
Hey Aaron. How about simple
@@ -1089,7 +1089,8 @@ void print_dl_rq(struct seq_file *m, int cpu,
struct dl_rq *dl_rq)
SEQ_printf(m, " .%-30s: %lu\n", #x, (unsigned long)(dl_rq->x))
PU(dl_nr_running);
- dl_bw = &cpu_rq(cpu)->rd->dl_bw;
+ guard(rcu)();
+ dl_bw = &rcu_dereference(cpu_rq(cpu)->rd)->dl_bw;
SEQ_printf(m, " .%-30s: %lld\n", "dl_bw->bw", dl_bw->bw);
SEQ_printf(m, " .%-30s: %lld\n", "dl_bw->total_bw", dl_bw->total_bw);
>
> #undef PU
> }
> diff --git a/kernel/sched/topology.c b/kernel/sched/topology.c
> index 622e2e01974c..b411cc00029c 100644
> --- a/kernel/sched/topology.c
> +++ b/kernel/sched/topology.c
> @@ -496,7 +496,7 @@ void rq_attach_root(struct rq *rq, struct root_domain *rd)
> }
>
> atomic_inc(&rd->refcount);
> - rq->rd = rd;
> + rcu_assign_pointer(rq->rd, rd);
>
> cpumask_set_cpu(rq->cpu, rd->span);
> if (cpumask_test_cpu(rq->cpu, cpu_active_mask))
> --
> 2.55.0
>
^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2026-08-10 13:53 UTC | newest]
Thread overview: 8+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-10 1:58 [PATCH v4 0/5] sched/debug: Introduce per-CPU debugfs files Aaron Tomlin
2026-08-10 1:58 ` [PATCH v4 1/5] sched/debug: Protect lockless rq->rd access in print_dl_rq() Aaron Tomlin
2026-08-10 6:50 ` K Prateek Nayak
2026-08-10 13:53 ` Daniel Vacek
2026-08-10 1:58 ` [PATCH v4 2/5] sched/debug: Protect lockless rq->curr access in print_cpu() Aaron Tomlin
2026-08-10 1:58 ` [PATCH v4 3/5] sched/debug: Protect lockless p->mm access in sched_show_numa() Aaron Tomlin
2026-08-10 1:58 ` [PATCH v4 4/5] sched/fair: Use list_for_each_entry_rcu() in print_cfs_stats() Aaron Tomlin
2026-08-10 1:58 ` [PATCH v4 5/5] sched/debug: Introduce per-CPU debugfs files Aaron Tomlin
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox