* [PATCH v2] mm: memcontrol: fix NULL pointer deref from high_work_func() on dying memcg
@ 2026-10-28 18:35 T.J. Mercier
2026-10-28 22:12 ` Johannes Weiner
` (2 more replies)
0 siblings, 3 replies; 5+ messages in thread
From: T.J. Mercier @ 2026-10-28 18:35 UTC (permalink / raw)
To: hannes, mhocko, roman.gushchin, shakeel.butt, muchun.song
Cc: zhengqi.arch, akpm, cgroups, linux-mm, linux-kernel, stable,
T.J. Mercier
When memory.high is exceeded in non-task context, try_charge_memcg()
schedules memcg->high_work without taking a reference on the memcg, and
high_work_func() invokes reclaim_high() without pinning the memcg.
cancel_work_sync(&memcg->high_work) only runs at the end of teardown in
mem_cgroup_css_free(), so high_work_func() can start or still be running
reclaim_high() after mem_cgroup_css_released() or shrink_many() has
called lru_gen_release_memcg() and unhashed lruvec->lrugen.list. If
reclaim tries to rotate the memcg, lru_gen_rotate_memcg() dereferences a
NULL pprev pointer.
With obj_cgroup reparenting, introduced in commit bf4f059954dc ("mm:
memcg/slab: obj_cgroup API"), objects charged to a child memcg are
reparented to its parent in mem_cgroup_css_offline() instead of holding
a reference on the child until uncharged. Subsequent uncharges of those
objects go to the parent, which can leave the child's memory use
permanently above its high limit, so any later charge on the offlined
memcg in non-task context schedules high_work.
This problem was first exposed when commit e4dde56cd208 ("mm: multi-gen
LRU: per-node lru_gen_folio lists") introduced the memcg LRU, and was
later widened from kernel memory to LRU folios when
commit f1cf8d2f36dc ("mm: memcontrol: eliminate the problem of dying
memory cgroup for LRU folios") converted LRU folios to use obj_cgroup
reparenting.
Fix this by acquiring a reference with css_tryget_online() in
high_work_func() before calling reclaim_high(), and by resetting
memory.high and swap.high to PAGE_COUNTER_MAX in
mem_cgroup_css_offline() so that charges via references on an offlined
memcg do not trigger high_work on the dead child or skip checking live
ancestors in try_charge_memcg().
Oops: general protection fault, probably for non-canonical address 0xdffffc0000000000: 0000 [#1] SMP KASAN NOPTI
KASAN: null-ptr-deref in range [0x0000000000000000-0x0000000000000007]
CPU: 1 UID: 0 PID: 27 Comm: kworker/1:0 Tainted: G B W 7.3.0-rc5-g2bba28e98578 #284 PREEMPT(full)
Tainted: [B]=BAD_PAGE, [W]=WARN <- Taint from lru_gen_rotate_memcg() VM_WARN_ON_ONCE
Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.17.0-debian-1.17.0-1 04/01/2014
Workqueue: events high_work_func
RIP: 0010:lru_gen_rotate_memcg+0x1a3/0xe90
Call Trace:
<TASK>
? read_ctrl_pos+0x22f/0x3f0
shrink_lruvec+0x16cb/0x1b80
? ret_from_fork_asm+0x1a/0x30
? lapic_next_event+0x15/0x20
? clockevents_program_event+0x2bb/0x6f0
? lapic_next_event+0x15/0x20
? __pfx_shrink_lruvec+0x10/0x10
? __pfx_clockevents_program_event+0x10/0x10
? _raw_spin_lock+0x84/0xe0
? __pfx__raw_spin_lock+0x10/0x10
? kasan_save_stack+0x30/0x50
? psi_group_change+0x381/0x740
? __pfx_sched_clock_cpu+0x10/0x10
? save_fpregs_to_fpstate+0xc4/0x2c0
? finish_task_switch.isra.0+0x1de/0xac0
? __schedule+0xf62/0x42c0
? __pfx___schedule+0x10/0x10
? __pfx___irq_work_queue_local+0x10/0x10
? shrink_node+0x626/0x31e0
shrink_node+0x626/0x31e0
? ring_buffer_unlock_commit+0x11d/0x5c0
? kswapd_clear_hopeless+0xa0/0x150
? __pfx_shrink_node+0x10/0x10
? 0xffffffffc0000095
do_try_to_free_pages+0x255/0x13b0
? ring_buffer_unlock_commit+0x11d/0x5c0
? reclaim_high+0x10a/0x1b0
try_to_free_mem_cgroup_pages+0x1f9/0x5a0
? __pfx_try_to_free_mem_cgroup_pages+0x10/0x10
? irqentry_exit+0xc6/0x670
reclaim_high+0x10a/0x1b0
? __pfx_reclaim_high+0x10/0x10
? process_one_work+0x5ff/0x1030
? __pfx_high_work_func+0x10/0x10
process_one_work+0x6e2/0x1030
? __pfx___schedule+0x10/0x10
? __pfx_process_one_work+0x10/0x10
worker_thread+0x422/0xbb0
? __pfx_worker_thread+0x10/0x10
kthread+0x2cd/0x3a0
? recalc_sigpending+0x15c/0x200
? __pfx_kthread+0x10/0x10
ret_from_fork+0x368/0x590
? __pfx_ret_from_fork+0x10/0x10
? __switch_to+0x588/0xf50
? __pfx_kthread+0x10/0x10
ret_from_fork_asm+0x1a/0x30
</TASK>
Fixes: e4dde56cd208 ("mm: multi-gen LRU: per-node lru_gen_folio lists")
Cc: stable@vger.kernel.org
Signed-off-by: T.J. Mercier <tjmercier@google.com>
---
Changes since V1 (https://lore.kernel.org/all/20261023222313.3979455-1-tjmercier@google.com/)
- Switch to Fixes: e4dde56cd208 ("mm: multi-gen LRU: per-node lru_gen_folio lists")
from Fixes: f1cf8d2f36dc ("mm: memcontrol: eliminate the problem of dying memory cgroup for LRU folios")
per Muchun Song.
- Reorder and update commit message content to describe reparenting
separately from the commits that exposed and then widened the problem.
- Trim the backtrace a bit.
mm/memcontrol.c | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/mm/memcontrol.c b/mm/memcontrol.c
index a05d9a8baa1b..84f4a8fd4d16 100644
--- a/mm/memcontrol.c
+++ b/mm/memcontrol.c
@@ -2457,7 +2457,10 @@ static void high_work_func(struct work_struct *work)
struct mem_cgroup *memcg;
memcg = container_of(work, struct mem_cgroup, high_work);
+ if (!css_tryget_online(&memcg->css))
+ return;
reclaim_high(memcg, MEMCG_CHARGE_BATCH, GFP_KERNEL);
+ css_put(&memcg->css);
}
static void high_irq_work_func(struct irq_work *work)
@@ -4463,6 +4466,8 @@ static void mem_cgroup_css_offline(struct cgroup_subsys_state *css)
page_counter_set_min(&memcg->memory, 0);
page_counter_set_low(&memcg->memory, 0);
+ page_counter_set_high(&memcg->memory, PAGE_COUNTER_MAX);
+ page_counter_set_high(&memcg->swap, PAGE_COUNTER_MAX);
zswap_memcg_offline_cleanup(memcg);
base-commit: 2bba28e98578de622ef307d00875aa8f8bca3d10
--
2.56.0.313.g2479322e68-goog
^ permalink raw reply [flat|nested] 5+ messages in thread* Re: [PATCH v2] mm: memcontrol: fix NULL pointer deref from high_work_func() on dying memcg
2026-10-28 18:35 [PATCH v2] mm: memcontrol: fix NULL pointer deref from high_work_func() on dying memcg T.J. Mercier
@ 2026-10-28 22:12 ` Johannes Weiner
2026-10-28 22:21 ` Shakeel Butt
2026-10-28 22:17 ` Shakeel Butt
2026-10-29 2:11 ` Muchun Song
2 siblings, 1 reply; 5+ messages in thread
From: Johannes Weiner @ 2026-10-28 22:12 UTC (permalink / raw)
To: T.J. Mercier
Cc: mhocko, roman.gushchin, shakeel.butt, muchun.song, zhengqi.arch,
akpm, cgroups, linux-mm, linux-kernel, stable
On Wed, Oct 28, 2026 at 11:35:37AM -0700, T.J. Mercier wrote:
> When memory.high is exceeded in non-task context, try_charge_memcg()
> schedules memcg->high_work without taking a reference on the memcg, and
> high_work_func() invokes reclaim_high() without pinning the memcg.
>
> cancel_work_sync(&memcg->high_work) only runs at the end of teardown in
> mem_cgroup_css_free(), so high_work_func() can start or still be running
> reclaim_high() after mem_cgroup_css_released() or shrink_many() has
> called lru_gen_release_memcg() and unhashed lruvec->lrugen.list. If
> reclaim tries to rotate the memcg, lru_gen_rotate_memcg() dereferences a
> NULL pprev pointer.
>
> With obj_cgroup reparenting, introduced in commit bf4f059954dc ("mm:
> memcg/slab: obj_cgroup API"), objects charged to a child memcg are
> reparented to its parent in mem_cgroup_css_offline() instead of holding
> a reference on the child until uncharged. Subsequent uncharges of those
> objects go to the parent, which can leave the child's memory use
> permanently above its high limit, so any later charge on the offlined
> memcg in non-task context schedules high_work.
>
> This problem was first exposed when commit e4dde56cd208 ("mm: multi-gen
> LRU: per-node lru_gen_folio lists") introduced the memcg LRU, and was
> later widened from kernel memory to LRU folios when
> commit f1cf8d2f36dc ("mm: memcontrol: eliminate the problem of dying
> memory cgroup for LRU folios") converted LRU folios to use obj_cgroup
> reparenting.
>
> Fix this by acquiring a reference with css_tryget_online() in
> high_work_func() before calling reclaim_high(), and by resetting
> memory.high and swap.high to PAGE_COUNTER_MAX in
> mem_cgroup_css_offline() so that charges via references on an offlined
> memcg do not trigger high_work on the dead child or skip checking live
> ancestors in try_charge_memcg().
>
> Oops: general protection fault, probably for non-canonical address 0xdffffc0000000000: 0000 [#1] SMP KASAN NOPTI
> KASAN: null-ptr-deref in range [0x0000000000000000-0x0000000000000007]
> CPU: 1 UID: 0 PID: 27 Comm: kworker/1:0 Tainted: G B W 7.3.0-rc5-g2bba28e98578 #284 PREEMPT(full)
> Tainted: [B]=BAD_PAGE, [W]=WARN <- Taint from lru_gen_rotate_memcg() VM_WARN_ON_ONCE
> Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.17.0-debian-1.17.0-1 04/01/2014
> Workqueue: events high_work_func
> RIP: 0010:lru_gen_rotate_memcg+0x1a3/0xe90
> Call Trace:
> <TASK>
> ? read_ctrl_pos+0x22f/0x3f0
> shrink_lruvec+0x16cb/0x1b80
> ? ret_from_fork_asm+0x1a/0x30
> ? lapic_next_event+0x15/0x20
> ? clockevents_program_event+0x2bb/0x6f0
> ? lapic_next_event+0x15/0x20
> ? __pfx_shrink_lruvec+0x10/0x10
> ? __pfx_clockevents_program_event+0x10/0x10
> ? _raw_spin_lock+0x84/0xe0
> ? __pfx__raw_spin_lock+0x10/0x10
> ? kasan_save_stack+0x30/0x50
> ? psi_group_change+0x381/0x740
> ? __pfx_sched_clock_cpu+0x10/0x10
> ? save_fpregs_to_fpstate+0xc4/0x2c0
> ? finish_task_switch.isra.0+0x1de/0xac0
> ? __schedule+0xf62/0x42c0
> ? __pfx___schedule+0x10/0x10
> ? __pfx___irq_work_queue_local+0x10/0x10
> ? shrink_node+0x626/0x31e0
> shrink_node+0x626/0x31e0
> ? ring_buffer_unlock_commit+0x11d/0x5c0
> ? kswapd_clear_hopeless+0xa0/0x150
> ? __pfx_shrink_node+0x10/0x10
> ? 0xffffffffc0000095
> do_try_to_free_pages+0x255/0x13b0
> ? ring_buffer_unlock_commit+0x11d/0x5c0
> ? reclaim_high+0x10a/0x1b0
> try_to_free_mem_cgroup_pages+0x1f9/0x5a0
> ? __pfx_try_to_free_mem_cgroup_pages+0x10/0x10
> ? irqentry_exit+0xc6/0x670
> reclaim_high+0x10a/0x1b0
> ? __pfx_reclaim_high+0x10/0x10
> ? process_one_work+0x5ff/0x1030
> ? __pfx_high_work_func+0x10/0x10
> process_one_work+0x6e2/0x1030
> ? __pfx___schedule+0x10/0x10
> ? __pfx_process_one_work+0x10/0x10
> worker_thread+0x422/0xbb0
> ? __pfx_worker_thread+0x10/0x10
> kthread+0x2cd/0x3a0
> ? recalc_sigpending+0x15c/0x200
> ? __pfx_kthread+0x10/0x10
> ret_from_fork+0x368/0x590
> ? __pfx_ret_from_fork+0x10/0x10
> ? __switch_to+0x588/0xf50
> ? __pfx_kthread+0x10/0x10
> ret_from_fork_asm+0x1a/0x30
> </TASK>
>
> Fixes: e4dde56cd208 ("mm: multi-gen LRU: per-node lru_gen_folio lists")
> Cc: stable@vger.kernel.org
> Signed-off-by: T.J. Mercier <tjmercier@google.com>
Hrm, yes I suppose the work itself keeps the memcg allocated, but not
from going deep into the destruction sequence. I was briefly wondering
whether mglru shouldn't move its callback from css_released into
css_free, but I think your fix is more robust after all: don't send a
barely allocated memcg down the reclaim stack.
The _online variant of the tryget seems somewhat unnecessary. There is
no real harm in reclaiming an offline group - and it was the standard
way, before reparenting, to garbage collect an offline group with
lingering pages. With the updated Fixes, this would look odd in
backports to kernels without reparenting. The PAGE_COUNTER_MAX reset
would also cause reclaim_high to bail.
So I wonder if css_tryget() would be simpler and just as correct. But
no strong feelings either way:
Reviewed-by: Johannes Weiner <hannes@cmpxchg.org>
^ permalink raw reply [flat|nested] 5+ messages in thread* Re: [PATCH v2] mm: memcontrol: fix NULL pointer deref from high_work_func() on dying memcg
2026-10-28 22:12 ` Johannes Weiner
@ 2026-10-28 22:21 ` Shakeel Butt
0 siblings, 0 replies; 5+ messages in thread
From: Shakeel Butt @ 2026-10-28 22:21 UTC (permalink / raw)
To: Johannes Weiner
Cc: T.J. Mercier, mhocko, roman.gushchin, muchun.song, zhengqi.arch,
akpm, cgroups, linux-mm, linux-kernel, stable
On Wed, Oct 28, 2026 at 06:12:45PM -0400, Johannes Weiner wrote:
> On Wed, Oct 28, 2026 at 11:35:37AM -0700, T.J. Mercier wrote:
> > When memory.high is exceeded in non-task context, try_charge_memcg()
> > schedules memcg->high_work without taking a reference on the memcg, and
> > high_work_func() invokes reclaim_high() without pinning the memcg.
> >
> > cancel_work_sync(&memcg->high_work) only runs at the end of teardown in
> > mem_cgroup_css_free(), so high_work_func() can start or still be running
> > reclaim_high() after mem_cgroup_css_released() or shrink_many() has
> > called lru_gen_release_memcg() and unhashed lruvec->lrugen.list. If
> > reclaim tries to rotate the memcg, lru_gen_rotate_memcg() dereferences a
> > NULL pprev pointer.
> >
> > With obj_cgroup reparenting, introduced in commit bf4f059954dc ("mm:
> > memcg/slab: obj_cgroup API"), objects charged to a child memcg are
> > reparented to its parent in mem_cgroup_css_offline() instead of holding
> > a reference on the child until uncharged. Subsequent uncharges of those
> > objects go to the parent, which can leave the child's memory use
> > permanently above its high limit, so any later charge on the offlined
> > memcg in non-task context schedules high_work.
> >
> > This problem was first exposed when commit e4dde56cd208 ("mm: multi-gen
> > LRU: per-node lru_gen_folio lists") introduced the memcg LRU, and was
> > later widened from kernel memory to LRU folios when
> > commit f1cf8d2f36dc ("mm: memcontrol: eliminate the problem of dying
> > memory cgroup for LRU folios") converted LRU folios to use obj_cgroup
> > reparenting.
> >
> > Fix this by acquiring a reference with css_tryget_online() in
> > high_work_func() before calling reclaim_high(), and by resetting
> > memory.high and swap.high to PAGE_COUNTER_MAX in
> > mem_cgroup_css_offline() so that charges via references on an offlined
> > memcg do not trigger high_work on the dead child or skip checking live
> > ancestors in try_charge_memcg().
> >
> > Oops: general protection fault, probably for non-canonical address 0xdffffc0000000000: 0000 [#1] SMP KASAN NOPTI
> > KASAN: null-ptr-deref in range [0x0000000000000000-0x0000000000000007]
> > CPU: 1 UID: 0 PID: 27 Comm: kworker/1:0 Tainted: G B W 7.3.0-rc5-g2bba28e98578 #284 PREEMPT(full)
> > Tainted: [B]=BAD_PAGE, [W]=WARN <- Taint from lru_gen_rotate_memcg() VM_WARN_ON_ONCE
> > Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.17.0-debian-1.17.0-1 04/01/2014
> > Workqueue: events high_work_func
> > RIP: 0010:lru_gen_rotate_memcg+0x1a3/0xe90
> > Call Trace:
> > <TASK>
> > ? read_ctrl_pos+0x22f/0x3f0
> > shrink_lruvec+0x16cb/0x1b80
> > ? ret_from_fork_asm+0x1a/0x30
> > ? lapic_next_event+0x15/0x20
> > ? clockevents_program_event+0x2bb/0x6f0
> > ? lapic_next_event+0x15/0x20
> > ? __pfx_shrink_lruvec+0x10/0x10
> > ? __pfx_clockevents_program_event+0x10/0x10
> > ? _raw_spin_lock+0x84/0xe0
> > ? __pfx__raw_spin_lock+0x10/0x10
> > ? kasan_save_stack+0x30/0x50
> > ? psi_group_change+0x381/0x740
> > ? __pfx_sched_clock_cpu+0x10/0x10
> > ? save_fpregs_to_fpstate+0xc4/0x2c0
> > ? finish_task_switch.isra.0+0x1de/0xac0
> > ? __schedule+0xf62/0x42c0
> > ? __pfx___schedule+0x10/0x10
> > ? __pfx___irq_work_queue_local+0x10/0x10
> > ? shrink_node+0x626/0x31e0
> > shrink_node+0x626/0x31e0
> > ? ring_buffer_unlock_commit+0x11d/0x5c0
> > ? kswapd_clear_hopeless+0xa0/0x150
> > ? __pfx_shrink_node+0x10/0x10
> > ? 0xffffffffc0000095
> > do_try_to_free_pages+0x255/0x13b0
> > ? ring_buffer_unlock_commit+0x11d/0x5c0
> > ? reclaim_high+0x10a/0x1b0
> > try_to_free_mem_cgroup_pages+0x1f9/0x5a0
> > ? __pfx_try_to_free_mem_cgroup_pages+0x10/0x10
> > ? irqentry_exit+0xc6/0x670
> > reclaim_high+0x10a/0x1b0
> > ? __pfx_reclaim_high+0x10/0x10
> > ? process_one_work+0x5ff/0x1030
> > ? __pfx_high_work_func+0x10/0x10
> > process_one_work+0x6e2/0x1030
> > ? __pfx___schedule+0x10/0x10
> > ? __pfx_process_one_work+0x10/0x10
> > worker_thread+0x422/0xbb0
> > ? __pfx_worker_thread+0x10/0x10
> > kthread+0x2cd/0x3a0
> > ? recalc_sigpending+0x15c/0x200
> > ? __pfx_kthread+0x10/0x10
> > ret_from_fork+0x368/0x590
> > ? __pfx_ret_from_fork+0x10/0x10
> > ? __switch_to+0x588/0xf50
> > ? __pfx_kthread+0x10/0x10
> > ret_from_fork_asm+0x1a/0x30
> > </TASK>
> >
> > Fixes: e4dde56cd208 ("mm: multi-gen LRU: per-node lru_gen_folio lists")
> > Cc: stable@vger.kernel.org
> > Signed-off-by: T.J. Mercier <tjmercier@google.com>
>
> Hrm, yes I suppose the work itself keeps the memcg allocated, but not
> from going deep into the destruction sequence. I was briefly wondering
> whether mglru shouldn't move its callback from css_released into
> css_free, but I think your fix is more robust after all: don't send a
> barely allocated memcg down the reclaim stack.
>
> The _online variant of the tryget seems somewhat unnecessary. There is
> no real harm in reclaiming an offline group - and it was the standard
> way, before reparenting, to garbage collect an offline group with
> lingering pages. With the updated Fixes, this would look odd in
> backports to kernels without reparenting. The PAGE_COUNTER_MAX reset
> would also cause reclaim_high to bail.
>
> So I wonder if css_tryget() would be simpler and just as correct. But
> no strong feelings either way:
css_tryget() is not safe after percpu_ref_exit() which can happen here. Only
css_tryget_online() is safe.
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v2] mm: memcontrol: fix NULL pointer deref from high_work_func() on dying memcg
2026-10-28 18:35 [PATCH v2] mm: memcontrol: fix NULL pointer deref from high_work_func() on dying memcg T.J. Mercier
2026-10-28 22:12 ` Johannes Weiner
@ 2026-10-28 22:17 ` Shakeel Butt
2026-10-29 2:11 ` Muchun Song
2 siblings, 0 replies; 5+ messages in thread
From: Shakeel Butt @ 2026-10-28 22:17 UTC (permalink / raw)
To: T.J. Mercier
Cc: hannes, mhocko, roman.gushchin, muchun.song, zhengqi.arch, akpm,
cgroups, linux-mm, linux-kernel, stable
On Wed, Oct 28, 2026 at 11:35:37AM -0700, T.J. Mercier wrote:
> When memory.high is exceeded in non-task context, try_charge_memcg()
> schedules memcg->high_work without taking a reference on the memcg, and
> high_work_func() invokes reclaim_high() without pinning the memcg.
>
> cancel_work_sync(&memcg->high_work) only runs at the end of teardown in
> mem_cgroup_css_free(), so high_work_func() can start or still be running
> reclaim_high() after mem_cgroup_css_released() or shrink_many() has
> called lru_gen_release_memcg() and unhashed lruvec->lrugen.list. If
> reclaim tries to rotate the memcg, lru_gen_rotate_memcg() dereferences a
> NULL pprev pointer.
>
> With obj_cgroup reparenting, introduced in commit bf4f059954dc ("mm:
> memcg/slab: obj_cgroup API"), objects charged to a child memcg are
> reparented to its parent in mem_cgroup_css_offline() instead of holding
> a reference on the child until uncharged. Subsequent uncharges of those
> objects go to the parent, which can leave the child's memory use
> permanently above its high limit, so any later charge on the offlined
> memcg in non-task context schedules high_work.
>
> This problem was first exposed when commit e4dde56cd208 ("mm: multi-gen
> LRU: per-node lru_gen_folio lists") introduced the memcg LRU, and was
> later widened from kernel memory to LRU folios when
> commit f1cf8d2f36dc ("mm: memcontrol: eliminate the problem of dying
> memory cgroup for LRU folios") converted LRU folios to use obj_cgroup
> reparenting.
>
> Fix this by acquiring a reference with css_tryget_online() in
> high_work_func() before calling reclaim_high(), and by resetting
> memory.high and swap.high to PAGE_COUNTER_MAX in
> mem_cgroup_css_offline() so that charges via references on an offlined
> memcg do not trigger high_work on the dead child or skip checking live
> ancestors in try_charge_memcg().
>
> Oops: general protection fault, probably for non-canonical address 0xdffffc0000000000: 0000 [#1] SMP KASAN NOPTI
> KASAN: null-ptr-deref in range [0x0000000000000000-0x0000000000000007]
> CPU: 1 UID: 0 PID: 27 Comm: kworker/1:0 Tainted: G B W 7.3.0-rc5-g2bba28e98578 #284 PREEMPT(full)
> Tainted: [B]=BAD_PAGE, [W]=WARN <- Taint from lru_gen_rotate_memcg() VM_WARN_ON_ONCE
> Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.17.0-debian-1.17.0-1 04/01/2014
> Workqueue: events high_work_func
> RIP: 0010:lru_gen_rotate_memcg+0x1a3/0xe90
> Call Trace:
> <TASK>
> ? read_ctrl_pos+0x22f/0x3f0
> shrink_lruvec+0x16cb/0x1b80
> ? ret_from_fork_asm+0x1a/0x30
> ? lapic_next_event+0x15/0x20
> ? clockevents_program_event+0x2bb/0x6f0
> ? lapic_next_event+0x15/0x20
> ? __pfx_shrink_lruvec+0x10/0x10
> ? __pfx_clockevents_program_event+0x10/0x10
> ? _raw_spin_lock+0x84/0xe0
> ? __pfx__raw_spin_lock+0x10/0x10
> ? kasan_save_stack+0x30/0x50
> ? psi_group_change+0x381/0x740
> ? __pfx_sched_clock_cpu+0x10/0x10
> ? save_fpregs_to_fpstate+0xc4/0x2c0
> ? finish_task_switch.isra.0+0x1de/0xac0
> ? __schedule+0xf62/0x42c0
> ? __pfx___schedule+0x10/0x10
> ? __pfx___irq_work_queue_local+0x10/0x10
> ? shrink_node+0x626/0x31e0
> shrink_node+0x626/0x31e0
> ? ring_buffer_unlock_commit+0x11d/0x5c0
> ? kswapd_clear_hopeless+0xa0/0x150
> ? __pfx_shrink_node+0x10/0x10
> ? 0xffffffffc0000095
> do_try_to_free_pages+0x255/0x13b0
> ? ring_buffer_unlock_commit+0x11d/0x5c0
> ? reclaim_high+0x10a/0x1b0
> try_to_free_mem_cgroup_pages+0x1f9/0x5a0
> ? __pfx_try_to_free_mem_cgroup_pages+0x10/0x10
> ? irqentry_exit+0xc6/0x670
> reclaim_high+0x10a/0x1b0
> ? __pfx_reclaim_high+0x10/0x10
> ? process_one_work+0x5ff/0x1030
> ? __pfx_high_work_func+0x10/0x10
> process_one_work+0x6e2/0x1030
> ? __pfx___schedule+0x10/0x10
> ? __pfx_process_one_work+0x10/0x10
> worker_thread+0x422/0xbb0
> ? __pfx_worker_thread+0x10/0x10
> kthread+0x2cd/0x3a0
> ? recalc_sigpending+0x15c/0x200
> ? __pfx_kthread+0x10/0x10
> ret_from_fork+0x368/0x590
> ? __pfx_ret_from_fork+0x10/0x10
> ? __switch_to+0x588/0xf50
> ? __pfx_kthread+0x10/0x10
> ret_from_fork_asm+0x1a/0x30
> </TASK>
>
> Fixes: e4dde56cd208 ("mm: multi-gen LRU: per-node lru_gen_folio lists")
> Cc: stable@vger.kernel.org
> Signed-off-by: T.J. Mercier <tjmercier@google.com>
> ---
> Changes since V1 (https://lore.kernel.org/all/20261023222313.3979455-1-tjmercier@google.com/)
> - Switch to Fixes: e4dde56cd208 ("mm: multi-gen LRU: per-node lru_gen_folio lists")
> from Fixes: f1cf8d2f36dc ("mm: memcontrol: eliminate the problem of dying memory cgroup for LRU folios")
> per Muchun Song.
> - Reorder and update commit message content to describe reparenting
> separately from the commits that exposed and then widened the problem.
> - Trim the backtrace a bit.
>
> mm/memcontrol.c | 5 +++++
> 1 file changed, 5 insertions(+)
>
> diff --git a/mm/memcontrol.c b/mm/memcontrol.c
> index a05d9a8baa1b..84f4a8fd4d16 100644
> --- a/mm/memcontrol.c
> +++ b/mm/memcontrol.c
> @@ -2457,7 +2457,10 @@ static void high_work_func(struct work_struct *work)
> struct mem_cgroup *memcg;
>
> memcg = container_of(work, struct mem_cgroup, high_work);
> + if (!css_tryget_online(&memcg->css))
> + return;
> reclaim_high(memcg, MEMCG_CHARGE_BATCH, GFP_KERNEL);
The patch is correct but I think there are two very specific subtleties which
the commit message is not very explicit about.
First, I was wondering shouldn't we take reference before schedule_work() and
why this is safe. This is safe due to cancel_work_sync() in the
mem_cgroup_css_free() which will not free memcg until this there is no high
worker.
Second, cgroup core calls css_free() after percpu_ref_exit(). So, is it safe to
call css_tryget_online() on such css->refcnt. Yes css_tryget_online() is safe
but css_tryget() is not safe.
^ permalink raw reply [flat|nested] 5+ messages in thread* Re: [PATCH v2] mm: memcontrol: fix NULL pointer deref from high_work_func() on dying memcg
2026-10-28 18:35 [PATCH v2] mm: memcontrol: fix NULL pointer deref from high_work_func() on dying memcg T.J. Mercier
2026-10-28 22:12 ` Johannes Weiner
2026-10-28 22:17 ` Shakeel Butt
@ 2026-10-29 2:11 ` Muchun Song
2 siblings, 0 replies; 5+ messages in thread
From: Muchun Song @ 2026-10-29 2:11 UTC (permalink / raw)
To: T.J. Mercier
Cc: hannes, mhocko, roman.gushchin, shakeel.butt, zhengqi.arch, akpm,
cgroups, linux-mm, linux-kernel, stable
> On Oct 29, 2026, at 02:35, T.J. Mercier <tjmercier@google.com> wrote:
>
> When memory.high is exceeded in non-task context, try_charge_memcg()
> schedules memcg->high_work without taking a reference on the memcg, and
> high_work_func() invokes reclaim_high() without pinning the memcg.
>
> cancel_work_sync(&memcg->high_work) only runs at the end of teardown in
> mem_cgroup_css_free(), so high_work_func() can start or still be running
> reclaim_high() after mem_cgroup_css_released() or shrink_many() has
> called lru_gen_release_memcg() and unhashed lruvec->lrugen.list. If
> reclaim tries to rotate the memcg, lru_gen_rotate_memcg() dereferences a
> NULL pprev pointer.
>
> With obj_cgroup reparenting, introduced in commit bf4f059954dc ("mm:
> memcg/slab: obj_cgroup API"), objects charged to a child memcg are
> reparented to its parent in mem_cgroup_css_offline() instead of holding
> a reference on the child until uncharged. Subsequent uncharges of those
> objects go to the parent, which can leave the child's memory use
> permanently above its high limit, so any later charge on the offlined
> memcg in non-task context schedules high_work.
>
> This problem was first exposed when commit e4dde56cd208 ("mm: multi-gen
> LRU: per-node lru_gen_folio lists") introduced the memcg LRU, and was
> later widened from kernel memory to LRU folios when
> commit f1cf8d2f36dc ("mm: memcontrol: eliminate the problem of dying
> memory cgroup for LRU folios") converted LRU folios to use obj_cgroup
> reparenting.
>
> Fix this by acquiring a reference with css_tryget_online() in
> high_work_func() before calling reclaim_high(), and by resetting
> memory.high and swap.high to PAGE_COUNTER_MAX in
> mem_cgroup_css_offline() so that charges via references on an offlined
> memcg do not trigger high_work on the dead child or skip checking live
> ancestors in try_charge_memcg().
>
> Oops: general protection fault, probably for non-canonical address 0xdffffc0000000000: 0000 [#1] SMP KASAN NOPTI
> KASAN: null-ptr-deref in range [0x0000000000000000-0x0000000000000007]
> CPU: 1 UID: 0 PID: 27 Comm: kworker/1:0 Tainted: G B W 7.3.0-rc5-g2bba28e98578 #284 PREEMPT(full)
> Tainted: [B]=BAD_PAGE, [W]=WARN <- Taint from lru_gen_rotate_memcg() VM_WARN_ON_ONCE
> Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.17.0-debian-1.17.0-1 04/01/2014
> Workqueue: events high_work_func
> RIP: 0010:lru_gen_rotate_memcg+0x1a3/0xe90
> Call Trace:
> <TASK>
> ? read_ctrl_pos+0x22f/0x3f0
> shrink_lruvec+0x16cb/0x1b80
> ? ret_from_fork_asm+0x1a/0x30
> ? lapic_next_event+0x15/0x20
> ? clockevents_program_event+0x2bb/0x6f0
> ? lapic_next_event+0x15/0x20
> ? __pfx_shrink_lruvec+0x10/0x10
> ? __pfx_clockevents_program_event+0x10/0x10
> ? _raw_spin_lock+0x84/0xe0
> ? __pfx__raw_spin_lock+0x10/0x10
> ? kasan_save_stack+0x30/0x50
> ? psi_group_change+0x381/0x740
> ? __pfx_sched_clock_cpu+0x10/0x10
> ? save_fpregs_to_fpstate+0xc4/0x2c0
> ? finish_task_switch.isra.0+0x1de/0xac0
> ? __schedule+0xf62/0x42c0
> ? __pfx___schedule+0x10/0x10
> ? __pfx___irq_work_queue_local+0x10/0x10
> ? shrink_node+0x626/0x31e0
> shrink_node+0x626/0x31e0
> ? ring_buffer_unlock_commit+0x11d/0x5c0
> ? kswapd_clear_hopeless+0xa0/0x150
> ? __pfx_shrink_node+0x10/0x10
> ? 0xffffffffc0000095
> do_try_to_free_pages+0x255/0x13b0
> ? ring_buffer_unlock_commit+0x11d/0x5c0
> ? reclaim_high+0x10a/0x1b0
> try_to_free_mem_cgroup_pages+0x1f9/0x5a0
> ? __pfx_try_to_free_mem_cgroup_pages+0x10/0x10
> ? irqentry_exit+0xc6/0x670
> reclaim_high+0x10a/0x1b0
> ? __pfx_reclaim_high+0x10/0x10
> ? process_one_work+0x5ff/0x1030
> ? __pfx_high_work_func+0x10/0x10
> process_one_work+0x6e2/0x1030
> ? __pfx___schedule+0x10/0x10
> ? __pfx_process_one_work+0x10/0x10
> worker_thread+0x422/0xbb0
> ? __pfx_worker_thread+0x10/0x10
> kthread+0x2cd/0x3a0
> ? recalc_sigpending+0x15c/0x200
> ? __pfx_kthread+0x10/0x10
> ret_from_fork+0x368/0x590
> ? __pfx_ret_from_fork+0x10/0x10
> ? __switch_to+0x588/0xf50
> ? __pfx_kthread+0x10/0x10
> ret_from_fork_asm+0x1a/0x30
> </TASK>
>
> Fixes: e4dde56cd208 ("mm: multi-gen LRU: per-node lru_gen_folio lists")
> Cc: stable@vger.kernel.org
> Signed-off-by: T.J. Mercier <tjmercier@google.com>
Acked-by: Muchun Song <muchun.song@linux.dev>
Thanks.
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-10-29 2:11 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-28 18:35 [PATCH v2] mm: memcontrol: fix NULL pointer deref from high_work_func() on dying memcg T.J. Mercier
2026-10-28 22:12 ` Johannes Weiner
2026-10-28 22:21 ` Shakeel Butt
2026-10-28 22:17 ` Shakeel Butt
2026-10-29 2:11 ` Muchun Song
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox
all inboxes | Powered by JetHome®