* Re: [PATCH] mm/memcg: fix UAF in drain_all_stock() async work during offline
2026-08-27 16:42 [PATCH] mm/memcg: fix UAF in drain_all_stock() async work during offline Rik van Riel
@ 2026-08-27 16:57 ` Shakeel Butt
2026-08-27 17:14 ` Johannes Weiner
` (4 subsequent siblings)
5 siblings, 0 replies; 8+ messages in thread
From: Shakeel Butt @ 2026-08-27 16:57 UTC (permalink / raw)
To: Rik van Riel
Cc: Johannes Weiner, Michal Hocko, Roman Gushchin, Muchun Song,
Andrew Morton, cgroups, linux-mm, linux-kernel, kernel-team
On Thu, Aug 27, 2026 at 12:42:11PM -0400, Rik van Riel wrote:
> drain_all_stock() queues drain work on remote CPUs via
> schedule_drain_work() -> queue_work_on(memcg_wq) and returns
> immediately without waiting. The worker, drain_local_memcg_stock()
> / drain_local_obj_stock(), dereferences per-CPU stock caches with
> READ_ONCE(stock->cached[i]) and does css_put() / obj_cgroup_put().
>
> mem_cgroup_css_offline() calls drain_all_stock(memcg) to
> optimize reclamation latency, but never flushes memcg_wq. If
> that races with cgroup removal, free can happen while workers
> are still pending, causing UAF. The drain work could also have
> been queued by somebody else before offline started (e.g. high
> throttling), not just by the offline path itself.
>
> Timeline illustrating the race:
>
> CPU0 (rmdir + offline) CPU1 (charge cache holder)
> ------------------------- ----------------------------
> cgroup_rmdir()
> cgroup_destroy_locked()
> kill_css_sync()
> ...
>
> refill_stock(victim)
> css_get(victim)
> WRITE_ONCE(cached[i]=victim)
>
> percpu_ref kill confirmed, css_killed_ref_fn() called
>
> css_killed_work_fn() [offline_wq]
> mem_cgroup_css_offline(victim)
> drain_all_stock(victim)
> is_memcg_drain_needed()
> READ_ONCE(cached) -> victim
> queue_work_on(CPU1, memcg_wq, work)
> // no flush!
> mem_cgroup_private_id_put()
> css_put() -> refcnt may hit 0
Why would refcnt hit 0? CPU1 stock has a reference.
>
> [RCU GP]
> css_free_rwork_fn()
> mem_cgroup_free(victim)
> // victim struct freed
>
> // worker delayed by scheduler/
> // WQ concurrency
> drain_local_memcg_stock()
> old = READ_ONCE(cached[i])
> // UAF: old == freed victim
> memcg_uncharge(old)
> css_put(&old->css)
>
> Fix by having the offline path wait for the workqueue to be
> done with the memcg, before freeing the memcg.
>
> Found through a code audit with kres.
>
> Fixes: 591edfb10a94 ("mm: drain memcg stocks on css offlining")
> Cc: stable@vger.kernel.org
> Assisted-by: Hermes:muse-spark-1.2 kres
> Signed-off-by: Rik van Riel <riel@surriel.com>
> ---
> mm/memcontrol.c | 29 +++++++++++++++++++++++++----
> 1 file changed, 25 insertions(+), 4 deletions(-)
>
> diff --git a/mm/memcontrol.c b/mm/memcontrol.c
> index 6dc4888a90f3..c95a1f6ec799 100644
> --- a/mm/memcontrol.c
> +++ b/mm/memcontrol.c
> @@ -2273,12 +2273,18 @@ static void schedule_drain_work(int cpu, struct work_struct *work)
> * Drains all per-CPU charge caches for given root_memcg resp. subtree
> * of the hierarchy under it.
> */
> -void drain_all_stock(struct mem_cgroup *root_memcg)
> +static void __drain_all_stock(struct mem_cgroup *root_memcg, bool sync)
> {
> int cpu, curcpu;
>
> - /* If someone's already draining, avoid adding running more workers. */
> - if (!mutex_trylock(&percpu_charge_mutex))
> + /*
> + * If someone's already draining, avoid starting more workers.
> + * Synchronous callers need to guarantee all the last things
> + * are flushed, e.g. before a memcg is removed.
> + */
> + if (sync)
> + mutex_lock(&percpu_charge_mutex);
> + else if (!mutex_trylock(&percpu_charge_mutex))
> return;
> /*
> * Notify other cpus that system-wide "drain" is running
> @@ -2316,6 +2322,21 @@ void drain_all_stock(struct mem_cgroup *root_memcg)
> mutex_unlock(&percpu_charge_mutex);
> }
>
> +void drain_all_stock(struct mem_cgroup *root_memcg)
> +{
> + __drain_all_stock(root_memcg, false);
> +}
> +
> +void drain_all_stock_sync(struct mem_cgroup *root_memcg)
> +{
> + /*
> + * Make sure the workqueue is done with this memcg
> + * before freeing it.
> + */
> + __drain_all_stock(root_memcg, true);
> + flush_workqueue(memcg_wq);
> +}
> +
> static int memcg_hotplug_cpu_dead(unsigned int cpu)
> {
> /* no need for the local lock */
> @@ -4305,7 +4326,7 @@ static void mem_cgroup_css_offline(struct cgroup_subsys_state *css)
> wb_memcg_offline(memcg);
> lru_gen_offline_memcg(memcg);
>
> - drain_all_stock(memcg);
> + drain_all_stock_sync(memcg);
>
> mem_cgroup_private_id_put(memcg, 1);
> }
> --
> 2.55.0
>
>
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH] mm/memcg: fix UAF in drain_all_stock() async work during offline
2026-08-27 16:42 [PATCH] mm/memcg: fix UAF in drain_all_stock() async work during offline Rik van Riel
2026-08-27 16:57 ` Shakeel Butt
@ 2026-08-27 17:14 ` Johannes Weiner
2026-08-27 22:35 ` Andrew Morton
` (3 subsequent siblings)
5 siblings, 0 replies; 8+ messages in thread
From: Johannes Weiner @ 2026-08-27 17:14 UTC (permalink / raw)
To: Rik van Riel
Cc: Michal Hocko, Roman Gushchin, Shakeel Butt, Muchun Song,
Andrew Morton, cgroups, linux-mm, linux-kernel, kernel-team
On Thu, Aug 27, 2026 at 12:42:11PM -0400, Rik van Riel wrote:
> drain_all_stock() queues drain work on remote CPUs via
> schedule_drain_work() -> queue_work_on(memcg_wq) and returns
> immediately without waiting. The worker, drain_local_memcg_stock()
> / drain_local_obj_stock(), dereferences per-CPU stock caches with
> READ_ONCE(stock->cached[i]) and does css_put() / obj_cgroup_put().
>
> mem_cgroup_css_offline() calls drain_all_stock(memcg) to
> optimize reclamation latency, but never flushes memcg_wq. If
> that races with cgroup removal, free can happen while workers
> are still pending, causing UAF. The drain work could also have
> been queued by somebody else before offline started (e.g. high
> throttling), not just by the offline path itself.
>
> Timeline illustrating the race:
>
> CPU0 (rmdir + offline) CPU1 (charge cache holder)
> ------------------------- ----------------------------
> cgroup_rmdir()
> cgroup_destroy_locked()
> kill_css_sync()
> ...
>
> refill_stock(victim)
> css_get(victim)
> WRITE_ONCE(cached[i]=victim)
I'm really confused. CPU1 acquires a ref for the cached[i] pointer ^
> percpu_ref kill confirmed, css_killed_ref_fn() called
>
> css_killed_work_fn() [offline_wq]
> mem_cgroup_css_offline(victim)
> drain_all_stock(victim)
> is_memcg_drain_needed()
> READ_ONCE(cached) -> victim
> queue_work_on(CPU1, memcg_wq, work)
> // no flush!
> mem_cgroup_private_id_put()
> css_put() -> refcnt may hit 0
So how can it hit 0 here?
> [RCU GP]
> css_free_rwork_fn()
> mem_cgroup_free(victim)
> // victim struct freed
This won't run until we hit zero...
>
> // worker delayed by scheduler/
> // WQ concurrency
> drain_local_memcg_stock()
> old = READ_ONCE(cached[i])
> // UAF: old == freed victim
> memcg_uncharge(old)
> css_put(&old->css)
...which is here.
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH] mm/memcg: fix UAF in drain_all_stock() async work during offline
2026-08-27 16:42 [PATCH] mm/memcg: fix UAF in drain_all_stock() async work during offline Rik van Riel
2026-08-27 16:57 ` Shakeel Butt
2026-08-27 17:14 ` Johannes Weiner
@ 2026-08-27 22:35 ` Andrew Morton
2026-08-28 2:33 ` Rik van Riel
2026-08-28 0:42 ` kernel test robot
` (2 subsequent siblings)
5 siblings, 1 reply; 8+ messages in thread
From: Andrew Morton @ 2026-08-27 22:35 UTC (permalink / raw)
To: Rik van Riel
Cc: Johannes Weiner, Michal Hocko, Roman Gushchin, Shakeel Butt,
Muchun Song, cgroups, linux-mm, linux-kernel, kernel-team
On Thu, 27 Aug 2026 12:42:11 -0400 Rik van Riel <riel@surriel.com> wrote:
> drain_all_stock() queues drain work on remote CPUs via
> schedule_drain_work() -> queue_work_on(memcg_wq) and returns
> immediately without waiting. The worker, drain_local_memcg_stock()
> / drain_local_obj_stock(), dereferences per-CPU stock caches with
> READ_ONCE(stock->cached[i]) and does css_put() / obj_cgroup_put().
>
> mem_cgroup_css_offline() calls drain_all_stock(memcg) to
> optimize reclamation latency, but never flushes memcg_wq. If
> that races with cgroup removal, free can happen while workers
> are still pending, causing UAF. The drain work could also have
> been queued by somebody else before offline started (e.g. high
> throttling), not just by the offline path itself.
>
> Timeline illustrating the race:
>
> ...
>
> Fix by having the offline path wait for the workqueue to be
> done with the memcg, before freeing the memcg.
>
> Found through a code audit with kres.
>
> Fixes: 591edfb10a94 ("mm: drain memcg stocks on css offlining")
> Cc: stable@vger.kernel.org
> Assisted-by: Hermes:muse-spark-1.2 kres
Sashiko might have found another thing in there:
https://sashiko.dev/#/patchset/20260827124211.3b94b103@fangorn
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH] mm/memcg: fix UAF in drain_all_stock() async work during offline
2026-08-27 22:35 ` Andrew Morton
@ 2026-08-28 2:33 ` Rik van Riel
0 siblings, 0 replies; 8+ messages in thread
From: Rik van Riel @ 2026-08-28 2:33 UTC (permalink / raw)
To: Andrew Morton
Cc: Johannes Weiner, Michal Hocko, Roman Gushchin, Shakeel Butt,
Muchun Song, cgroups, linux-mm, linux-kernel, kernel-team
On Thu, 2026-08-27 at 15:35 -0700, Andrew Morton wrote:
>
> Sashiko might have found another thing in there:
>
>
> https://sashiko.dev/#/patchset/20260827124211.3b94b103@fangorn
Coming up with a fix for that now, and some
adjustments to the kernel-style rules about
refactoring code to make it easier to read.
--
All Rights Reversed.
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH] mm/memcg: fix UAF in drain_all_stock() async work during offline
2026-08-27 16:42 [PATCH] mm/memcg: fix UAF in drain_all_stock() async work during offline Rik van Riel
` (2 preceding siblings ...)
2026-08-27 22:35 ` Andrew Morton
@ 2026-08-28 0:42 ` kernel test robot
2026-08-28 3:22 ` kernel test robot
2026-08-28 10:25 ` kernel test robot
5 siblings, 0 replies; 8+ messages in thread
From: kernel test robot @ 2026-08-28 0:42 UTC (permalink / raw)
To: Rik van Riel, Johannes Weiner
Cc: llvm, oe-kbuild-all, Michal Hocko, Roman Gushchin, Shakeel Butt,
Muchun Song, Andrew Morton, Linux Memory Management List, cgroups,
linux-kernel, kernel-team
Hi Rik,
kernel test robot noticed the following build warnings:
[auto build test WARNING on akpm-mm/mm-everything]
url: https://github.com/intel-lab-lkp/linux/commits/Rik-van-Riel/mm-memcg-fix-UAF-in-drain_all_stock-async-work-during-offline/20260827-124211
base: https://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm.git mm-everything
patch link: https://lore.kernel.org/r/20260827124211.3b94b103%40fangorn
patch subject: [PATCH] mm/memcg: fix UAF in drain_all_stock() async work during offline
config: x86_64-randconfig-074-20260828 (https://download.01.org/0day-ci/archive/20260828/202608280832.9NUcmGpu-lkp@intel.com/config)
compiler: clang version 22.1.3 (https://github.com/llvm/llvm-project e9846648fd6183ee6d8cbdb4502213fcf902a211)
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260828/202608280832.9NUcmGpu-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202608280832.9NUcmGpu-lkp@intel.com/
All warnings (new ones prefixed by >>):
>> mm/memcontrol.c:2380:6: warning: no previous prototype for function 'drain_all_stock_sync' [-Wmissing-prototypes]
2380 | void drain_all_stock_sync(struct mem_cgroup *root_memcg)
| ^
mm/memcontrol.c:2380:1: note: declare 'static' if the function is not intended to be used outside of this translation unit
2380 | void drain_all_stock_sync(struct mem_cgroup *root_memcg)
| ^
| static
1 warning generated.
vim +/drain_all_stock_sync +2380 mm/memcontrol.c
2379
> 2380 void drain_all_stock_sync(struct mem_cgroup *root_memcg)
2381 {
2382 /*
2383 * Make sure the workqueue is done with this memcg
2384 * before freeing it.
2385 */
2386 __drain_all_stock(root_memcg, true);
2387 flush_workqueue(memcg_wq);
2388 }
2389
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH] mm/memcg: fix UAF in drain_all_stock() async work during offline
2026-08-27 16:42 [PATCH] mm/memcg: fix UAF in drain_all_stock() async work during offline Rik van Riel
` (3 preceding siblings ...)
2026-08-28 0:42 ` kernel test robot
@ 2026-08-28 3:22 ` kernel test robot
2026-08-28 10:25 ` kernel test robot
5 siblings, 0 replies; 8+ messages in thread
From: kernel test robot @ 2026-08-28 3:22 UTC (permalink / raw)
To: Rik van Riel, Johannes Weiner
Cc: oe-kbuild-all, Michal Hocko, Roman Gushchin, Shakeel Butt,
Muchun Song, Andrew Morton, Linux Memory Management List, cgroups,
linux-kernel, kernel-team
Hi Rik,
kernel test robot noticed the following build warnings:
[auto build test WARNING on akpm-mm/mm-everything]
url: https://github.com/intel-lab-lkp/linux/commits/Rik-van-Riel/mm-memcg-fix-UAF-in-drain_all_stock-async-work-during-offline/20260827-124211
base: https://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm.git mm-everything
patch link: https://lore.kernel.org/r/20260827124211.3b94b103%40fangorn
patch subject: [PATCH] mm/memcg: fix UAF in drain_all_stock() async work during offline
config: xtensa-randconfig-r073-20260828 (https://download.01.org/0day-ci/archive/20260828/202608281149.3OqBLtQa-lkp@intel.com/config)
compiler: xtensa-linux-gcc (GCC) 16.1.0
smatch: v0.5.0-9187-g5189e3fb
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260828/202608281149.3OqBLtQa-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202608281149.3OqBLtQa-lkp@intel.com/
All warnings (new ones prefixed by >>):
>> mm/memcontrol.c:2380:6: warning: no previous prototype for 'drain_all_stock_sync' [-Wmissing-prototypes]
2380 | void drain_all_stock_sync(struct mem_cgroup *root_memcg)
| ^~~~~~~~~~~~~~~~~~~~
vim +/drain_all_stock_sync +2380 mm/memcontrol.c
2379
> 2380 void drain_all_stock_sync(struct mem_cgroup *root_memcg)
2381 {
2382 /*
2383 * Make sure the workqueue is done with this memcg
2384 * before freeing it.
2385 */
2386 __drain_all_stock(root_memcg, true);
2387 flush_workqueue(memcg_wq);
2388 }
2389
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH] mm/memcg: fix UAF in drain_all_stock() async work during offline
2026-08-27 16:42 [PATCH] mm/memcg: fix UAF in drain_all_stock() async work during offline Rik van Riel
` (4 preceding siblings ...)
2026-08-28 3:22 ` kernel test robot
@ 2026-08-28 10:25 ` kernel test robot
5 siblings, 0 replies; 8+ messages in thread
From: kernel test robot @ 2026-08-28 10:25 UTC (permalink / raw)
To: Rik van Riel, Johannes Weiner
Cc: oe-kbuild-all, Michal Hocko, Roman Gushchin, Shakeel Butt,
Muchun Song, Andrew Morton, Linux Memory Management List, cgroups,
linux-kernel, kernel-team
Hi Rik,
kernel test robot noticed the following build warnings:
[auto build test WARNING on akpm-mm/mm-everything]
url: https://github.com/intel-lab-lkp/linux/commits/Rik-van-Riel/mm-memcg-fix-UAF-in-drain_all_stock-async-work-during-offline/20260827-124211
base: https://git.kernel.org/pub/scm/linux/kernel/git/akpm/mm.git mm-everything
patch link: https://lore.kernel.org/r/20260827124211.3b94b103%40fangorn
patch subject: [PATCH] mm/memcg: fix UAF in drain_all_stock() async work during offline
config: nios2-randconfig-r121-20260828 (https://download.01.org/0day-ci/archive/20260828/202608281724.4MTLH1h2-lkp@intel.com/config)
compiler: nios2-linux-gcc (GCC) 8.5.0
sparse: v0.6.5-rc1
reproduce (this is a W=1 build): (https://download.01.org/0day-ci/archive/20260828/202608281724.4MTLH1h2-lkp@intel.com/reproduce)
If you fix the issue in a separate patch/commit (i.e. not just a new version of
the same patch/commit), kindly add following tags
| Reported-by: kernel test robot <lkp@intel.com>
| Closes: https://lore.kernel.org/oe-kbuild-all/202608281724.4MTLH1h2-lkp@intel.com/
sparse warnings: (new ones prefixed by >>)
>> mm/memcontrol.c:2380:6: sparse: sparse: symbol 'drain_all_stock_sync' was not declared. Should it be static?
mm/memcontrol.c:4678:52: sparse: sparse: incompatible types in comparison expression (different address spaces):
mm/memcontrol.c:4678:52: sparse: struct task_struct [noderef] __rcu *
mm/memcontrol.c:4678:52: sparse: struct task_struct *
vim +/drain_all_stock_sync +2380 mm/memcontrol.c
2379
> 2380 void drain_all_stock_sync(struct mem_cgroup *root_memcg)
2381 {
2382 /*
2383 * Make sure the workqueue is done with this memcg
2384 * before freeing it.
2385 */
2386 __drain_all_stock(root_memcg, true);
2387 flush_workqueue(memcg_wq);
2388 }
2389
--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests/wiki
^ permalink raw reply [flat|nested] 8+ messages in thread