cgroup: Fixes for v7.3-rc6

- During CPU offline, the active mask drops the CPU before cpuset updates
   the effective CPUs, so a task placement in that window could find no
   active CPU in the top cpuset and dereference NULL. Restore the NULL check.
 
 - The cpuset v2-mode test read the subsystem's root pointer, which is stale
   during a cgroup filesystem rebind, and the hotplug handler evaluated it
   before taking the cpuset mutex. Record the mode in a flag and test it
   under the mutex.
 -----BEGIN PGP SIGNATURE-----
 
 iIQEABYKACwWIQTfIjM1kS57o3GsC/uxYfJx3gVYGQUCasUtqA4cdGpAa2VybmVs
 Lm9yZwAKCRCxYfJx3gVYGf+HAP9wEWWAp5XQUuDFaWg6Ft8iPw4Tm+b1d2w4c5il
 j+EGQgD/ce2FLEIu2642xuvQB4XaScJMT7G+3XZplgBsT4/jrA4=
 =Qa2B
 -----END PGP SIGNATURE-----

Merge tag 'cgroup-for-7.3-rc6-fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/tj/cgroup

Pull cgroup fixes from Tejun Heo:

 - During CPU offline, the active mask drops the CPU before cpuset
   updates the effective CPUs, so a task placement in that window could
   find no active CPU in the top cpuset and dereference NULL. Restore
   the NULL check.

 - The cpuset v2-mode test read the subsystem's root pointer, which is
   stale during a cgroup filesystem rebind, and the hotplug handler
   evaluated it before taking the cpuset mutex. Record the mode in a
   flag and test it under the mutex.

* tag 'cgroup-for-7.3-rc6-fixes' of git://git.kernel.org/pub/scm/linux/kernel/git/tj/cgroup:
  cgroup/cpuset: Call is_in_v2_mode() after acquiring cpuset_mutex in cpuset_handle_hotplug()
  cgroup/cpuset: Handle cpu hotplug race in guarantee_active_cpus()
  cgroup/cpuset: Don't access cpuset_cgrp_subsys.root in is_in_v2_mode()
This commit is contained in:
Linus Torvalds 2026-10-07 02:19:02 +02:00
commit 0d32b3ec5f

View File

@ -152,6 +152,12 @@ static cpumask_var_t isolated_cpus; /* CSCB */
*/
static bool update_housekeeping; /* RWCS */
/*
* Set if "cpuset_v2_mode" mount option is used
* Cached at bind time and not lock protected; accessed via {READ,WRITE}_ONCE
*/
static bool cpuset_v2_mode;
/*
* Copy of isolated_cpus to be passed to housekeeping_update()
*/
@ -439,8 +445,7 @@ static inline bool cpuset_v2(void)
*/
static inline bool is_in_v2_mode(void)
{
return cpuset_v2() ||
(cpuset_cgrp_subsys.root->flags & CGRP_ROOT_CPUSET_V2_MODE);
return cpuset_v2() || READ_ONCE(cpuset_v2_mode);
}
/**
@ -513,10 +518,26 @@ static void guarantee_active_cpus(struct task_struct *tsk,
rcu_read_lock();
cs = task_cs(tsk);
while (!cpumask_intersects(cs->effective_cpus, pmask))
while (!cpumask_intersects(cs->effective_cpus, pmask)) {
cs = parent_cs(cs);
if (unlikely(!cs)) {
/*
* The top cpuset doesn't have any active cpu as a
* consequence of a race between its caller and the cpu
* hotplug operation where cpu_active_mask is updated
* asynchronously before cpuset_handle_hotplug() is
* being called to adjust the effective_cpus of the
* affected cpusets. But we know the top cpuset's
* effective_cpus is on its way to be identical to
* cpu_active_mask minus the exclusive CPUs dedicated
* to other valid cpuset partitions. Just pass back
* the filtered cpu_active_mask in this case.
*/
goto out_unlock;
}
}
cpumask_and(pmask, pmask, cs->effective_cpus);
out_unlock:
rcu_read_unlock();
}
@ -3732,6 +3753,8 @@ static void cpuset_bind(struct cgroup_subsys_state *root_css)
mutex_lock(&cpuset_mutex);
spin_lock_irq(&callback_lock);
WRITE_ONCE(cpuset_v2_mode,
!!(cpuset_cgrp_subsys.root->flags & CGRP_ROOT_CPUSET_V2_MODE));
if (is_in_v2_mode()) {
cpumask_copy(top_cpuset.cpus_allowed, cpu_possible_mask);
cpumask_copy(top_cpuset.effective_xcpus, cpu_possible_mask);
@ -4044,15 +4067,16 @@ static void cpuset_handle_hotplug(void)
static cpumask_t new_cpus;
static nodemask_t new_mems;
bool cpus_updated, mems_updated;
bool on_dfl = is_in_v2_mode();
bool on_dfl;
struct tmpmasks tmp, *ptmp = NULL;
if (on_dfl && !alloc_tmpmasks(&tmp))
ptmp = &tmp;
lockdep_assert_cpus_held();
mutex_lock(&cpuset_mutex);
on_dfl = is_in_v2_mode();
if (on_dfl && !alloc_tmpmasks(&tmp))
ptmp = &tmp;
/* fetch the available cpus/mems and find out which changed how */
cpumask_copy(&new_cpus, cpu_active_mask);
new_mems = node_states[N_MEMORY];