From 30a397468e10076969c8df128d09986d718d50b4 Mon Sep 17 00:00:00 2001 From: Joshua Hahn Date: Tue, 20 Oct 2026 18:00:50 -0700 Subject: [PATCH] mm/memcontrol: publish null pointer before dropping css reference struct memcg_stock_pcp hold references to cgroups that it tracks to make sure that the memcg never gets freed with unaccounted charges still living in the stock. When the cached stock goes to 0 for a memcg, its reference is dropped and the cached memcg pointer is set to NULL. If that was the last reference, the css_put would free the associated cgroup before publishing the NULL pointer, which can be racy on PREEMPT_RT. To summarize Shakeel Butt's analysis in [2], CPU 1 CPU 2 local_trylock() takes a per-CPU spinlock, which is an rcu read lock along with migrate_disable drain_stock css_put drops the last reference release work is queued kworker runs css_release_work_fn() call_rcu() starts grace period G which waits for the refill task. drain_all_stock is_memcg_drain_needed() enters rcu_read_lock(), but grace period G does not wait since it started after G did stock->cached[i] is cleared and unlocks. Grace period G ends, old is freed mem_cgroup_is_descendant() reads old->css.cgroup which has just been freed. To close this gap on PREEMPT_RT, switch the order of the referene drop and the publishing so that we publish the NULL pointer before dropping the reference. Note that this can only happen from drain_stock who can hold the last reference, but not on consume_stock since another reference will always be held. But fix this in both sites to maintain consistency. This is based on a report from Sashiko, who found this pre-existing issue in my page_counter_stock series [1]. Link: https://lore.kernel.org/20261021010051.1529261-1-joshua.hahnjy@gmail.com Link: https://sashiko.dev/#/patchset/20260928192349.3432886-1-joshua.hahnjy%40gmail.com [1] Link: https://lore.kernel.org/all/atfMngq7nyZpt4u-@linux.dev/ [2] Fixes: e1a366be5cb4 ("mm: memcontrol: switch to rcu protection in drain_all_stock()") Signed-off-by: Joshua Hahn Signed-off-by: Andrew Morton Acked-by: Shakeel Butt Acked-by: Muchun Song Cc: Johannes Weiner Cc: Michal Hocko Cc: Roman Gushchin --- mm/memcontrol.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mm/memcontrol.c b/mm/memcontrol.c index e7d7db5718f9..a05d9a8baa1b 100644 --- a/mm/memcontrol.c +++ b/mm/memcontrol.c @@ -2172,8 +2172,8 @@ static bool consume_stock(struct mem_cgroup *memcg, unsigned int nr_pages) stock_pages -= nr_pages; WRITE_ONCE(stock->nr_pages[i], stock_pages); if (!stock_pages) { - css_put(&memcg->css); WRITE_ONCE(stock->cached[i], NULL); + css_put(&memcg->css); } ret = true; } @@ -2209,8 +2209,8 @@ static void drain_stock(struct memcg_stock_pcp *stock, int i) WRITE_ONCE(stock->nr_pages[i], 0); } - css_put(&old->css); WRITE_ONCE(stock->cached[i], NULL); + css_put(&old->css); } static void drain_stock_fully(struct memcg_stock_pcp *stock)