[PATCH 3/5] cgroup/cpuset: Protect current->mems_allowed read in cpuset_fork()

From: Waiman Long

Date: Mon Nov 09 2026 - 11:10:54 EST


Reading mems_allowed of the current task outside of cpuset_mutex critical
section should be protected by using mems_allowed_seq seqlock as the
nodemask can be updated concurrently leading to a torn read. Such
protection is currently not implemented in cpuset_fork() for the
!CLONE_INTO_CGROUP case. As a result, the child task may inherit an empty
or corrupted mems_allowed nodemask. This violates NUMA memory policy
constraints and causes subsequent page allocations to unconditionally
fail, potentially leading to crashes or spurious OOM killer invocations
for the child task.

Fix that by using a read_mems_allowed_begin/read_mems_allowed_retry()
loop to protect the mems_allowed read.

Fixes: 06f4e9489891 ("cpuset: make sure new tasks conform to the current config of the cpuset")
Reported-by: Sashiko <sashiko-bot@xxxxxxxxxx>
Link: https://sashiko.dev/#/bug/linux-601100b4-fa77-45fd-99f9-bfbd4385201f
Signed-off-by: Waiman Long <longman@xxxxxxxxxx>
---
kernel/cgroup/cpuset.c | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/kernel/cgroup/cpuset.c b/kernel/cgroup/cpuset.c
index e8169b739454..1f25f27e0e9b 100644
--- a/kernel/cgroup/cpuset.c
+++ b/kernel/cgroup/cpuset.c
@@ -3864,11 +3864,20 @@ static void cpuset_fork(struct task_struct *task)
rcu_read_unlock();

if (cs == oldcs) {
+ unsigned int seq;
+
if (cs == &top_cpuset)
return;

set_cpus_allowed_ptr(task, current->cpus_ptr);
- task->mems_allowed = current->mems_allowed;
+ /*
+ * A cpuset.mems update can update current->mems_allowed
+ * concurrently, so retry it again if it really happens.
+ */
+ do {
+ seq = read_mems_allowed_begin();
+ task->mems_allowed = current->mems_allowed;
+ } while (read_mems_allowed_retry(seq));
return;
}

--
2.56.0