[PATCH 0/2] mm: fix two allocator races with zonelist and cpuset updates
From: Gregory Price (Meta)
Date: Wed Oct 14 2026 - 18:25:07 EST
Two small fixes for lockless allocator checks that can act on an
inconsistent view of state another CPU is updating.
Neither of these are stable-worthy.
Patch 1: the zonelist_update_seq retry is compiled out without
CONFIG_MEMORY_HOTREMOVE, but memory hot-add can also cause the race.
A __GFP_THISNODE allocation can observe this and fail to allocate.
Patch 2: cpuset_nodemask_valid_mems_allowed() compares a mempolicy
nodemask with mems_allowed without mems_allowed_seq. A concurrent
cpuset update can cause a spurious empty intersection and end up
relaxing an MPOL_BIND needlessly. This only covers task policies.
Reproducers
===========
Both races were reproduced on an unmodified kernel in a QEMU guest with
three NUMA nodes:
node 0: 3G, CPUs 0-1
node 1: 1G ZONE_NORMAL, CPU 2
node 2: 1G ZONE_NORMAL, CPU 3
And a 512M pc-dimm cold-plugged on node 1, left offline at boot
(CONFIG_ACPI_HOTPLUG_MEMORY=y, CONFIG_MEMORY_HOTREMOVE=n).
The windows are a few instructions wide, so I used a test module to
widens them with a kprobe placed on a single instruction inside the
window. This basically emulates an interrupt or vCPU preemption.
Patch 1 (zonelist):
Online node 1's first memory block as ZONE_MOVABLE, which rebuilds
node 1's zonelists.
Stall the rebuild after it writes ZONE_MOVABLE into the first entry
but before it writes ZONE_NORMAL into the second.
On another CPU, allocate GFP_KERNEL | __GFP_THISNODE from node 1.
Before: the allocation fails, 3/3 runs.
After: the allocation waits for the rebuild and succeeds, 3/3 runs.
Patch 2 (cpuset):
Put a task in a cpuset with mems={0}
set_mempolicy(2) the task to MPOL_BIND={0}
Run the task on node2 (yes this is a little flagrant).
Fill node 0 with HugeTLB pages to make allocations fail.
Stall between the two loads in cpuset_nodemask_valid_mems_allowed().
While stalled, change cpuset.mems={1,2}, this rebinds the task
mempolicy to MPOL_BIND={1}.
The check compares the old mems_allowed (0) with the new policy (1),
finds no overlap, and drops the nodemask.
Before: the allocation lands on node 2, 3/3 runs.
After: the allocation lands on node 1, 3/3 runs.
Gregory Price (Meta) (2):
mm/page_alloc: detect zonelist rebuilds from memory hot-add
cgroup/cpuset: use mems_allowed_seq when checking a policy nodemask
kernel/cgroup/cpuset.c | 13 ++++++++++++-
mm/page_alloc.c | 4 ++--
2 files changed, 14 insertions(+), 3 deletions(-)
--
2.53.0-Meta