[PATCH v5 05/29] sched/deadline: Do not divide by zero active CPUs of a root domain
From: Mathieu Desnoyers
Date: Sun Nov 08 2026 - 13:56:43 EST
__dl_sub() and __dl_add() divide by their cpus argument. Most callers
pass dl_bw_cpus(), the number of active CPUs of a root domain. That
number can be 0, and the division then traps.
Commit daec57983670 ("sched/deadline: Reclaim bandwidth not used by dl
tasks") added extra_bw, the bandwidth that deadline tasks can reclaim
on a runqueue. A change of a root domain's total_bw is shared by the
extra_bw of its active CPUs.
During hotplug, a CPU sits in the default root domain, which can then
have no active CPU. This lasts from the rebuild in
sched_cpu_deactivate() to the offline, and from the online to
sched_cpu_activate(). A debugfs write to its fair server's runtime or
period divides there. Commit 4043f5498416 ("sched/deadline: Reject
debugfs dl_server writes for offline CPUs") refuses the write only once
the CPU is offline. A write of the same bandwidth passes admission in
the window. In 8-CPU VMs of v7.4-rc1, a loop of such writes oopses at
the first offline and online:
Oops: divide error: 0000 [#1] SMP NOPTI
RIP: 0010:dl_server_apply_params+0x335/0x380
Call Trace:
sched_server_write_common.isra.0+0x1c0/0x260
full_proxy_write+0x58/0x90
vfs_write+0xcb/0x550
By reading, two other paths would divide by zero:
- inactive_task_timer(), for a task that left SCHED_DEADLINE or died,
if its CPU went offline.
- sched_setattr(SCHED_DEADLINE) on a task asleep on an offline CPU,
with admission control off.
Fix this in two places:
- Refuse the debugfs write while the runqueue is offline. The server's
bandwidth is not counted then. rq->online is set under the runqueue
lock when the CPU joins a root domain that counts it, after
cpu_active() is set.
- With no active CPU, have __dl_sub() and __dl_add() change total_bw
alone. This covers the other paths.
With this patch, the reproducer survives 20 rounds with no warning.
Fixes: daec57983670 ("sched/deadline: Reclaim bandwidth not used by dl tasks")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Mathieu Desnoyers <mathieu.desnoyers@xxxxxxxxxxxx>
---
kernel/sched/deadline.c | 11 +++++++++--
kernel/sched/debug.c | 10 +++++++++-
2 files changed, 18 insertions(+), 3 deletions(-)
diff --git a/kernel/sched/deadline.c b/kernel/sched/deadline.c
index dfae08625342..b614d5fc1a1a 100644
--- a/kernel/sched/deadline.c
+++ b/kernel/sched/deadline.c
@@ -189,18 +189,25 @@ void __dl_update(struct dl_bw *dl_b, s64 bw)
}
}
+/*
+ * Spread a change of total_bw over the extra_bw of the active CPUs of the
+ * root domain. @cpus is their number. With no active CPU, as can happen
+ * during CPU hotplug, change total_bw alone.
+ */
static inline
void __dl_sub(struct dl_bw *dl_b, u64 tsk_bw, int cpus)
{
dl_b->total_bw -= tsk_bw;
- __dl_update(dl_b, (s32)tsk_bw / cpus);
+ if (cpus)
+ __dl_update(dl_b, (s32)tsk_bw / cpus);
}
static inline
void __dl_add(struct dl_bw *dl_b, u64 tsk_bw, int cpus)
{
dl_b->total_bw += tsk_bw;
- __dl_update(dl_b, -((s32)tsk_bw / cpus));
+ if (cpus)
+ __dl_update(dl_b, -((s32)tsk_bw / cpus));
}
static inline bool
diff --git a/kernel/sched/debug.c b/kernel/sched/debug.c
index e6a3b516c703..e0d6cd3bd607 100644
--- a/kernel/sched/debug.c
+++ b/kernel/sched/debug.c
@@ -410,7 +410,15 @@ static ssize_t sched_server_write_common(struct file *filp, const char __user *u
return -EINVAL;
}
- if (!cpu_online(cpu_of(rq)))
+ /*
+ * Refuse the write while the runqueue is offline. This
+ * includes the windows where the CPU goes offline or comes
+ * online. The root domain then does not count the server's
+ * bandwidth, and may have no active CPU. rq->online is set
+ * under this lock when the CPU joins a root domain that
+ * counts it. cpu_active() is set before that.
+ */
+ if (!rq->online)
return -EBUSY;
update_rq_clock(rq);
--
2.47.3