Linux-mm Archive on lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 1/2] mm: multi-gen LRU: fix NULL lruvec->pgdat deref with memcg disabled on cmdline
@ 2026-10-17  5:09 Siddharth Chintamaneni
  2026-10-17  5:09 ` [PATCH v2 2/2] mm: multi-gen LRU: fix NULL lruvec->pgdat deref in get_lruvec() Siddharth Chintamaneni
                   ` (3 more replies)
  0 siblings, 4 replies; 16+ messages in thread
From: Siddharth Chintamaneni @ 2026-10-17  5:09 UTC (permalink / raw)
  To: akpm, hannes
  Cc: david, mhocko, qi.zheng, shakeel.butt, ljs, kasong, baohua,
	axelrasmussen, yuanchu, weixugc, baoquan.he, baolin.wang, yuzhao,
	linux-mm, linux-kernel, apais, rlmenge, Siddharth Chintamaneni,
	stable, Hardik Garg

When CONFIG_MEMCG=y, lruvec_pgdat() returns lruvec->pgdat, which for the
per-node pgdat->__lruvec is left NULL by lruvec_init() and only filled in
lazily by mem_cgroup_lruvec(). Normally that happens early, e.g. on the
first folio_add_lru().

With cgroup_disable=memory, lru_gen_shrink_node() passes &pgdat->__lruvec
directly to shrink_one(). If direct reclaim runs before anything has
called mem_cgroup_lruvec() for the node, which can happen during early
boot under memory pressure (e.g. a kdump kernel), get_swappiness() ends
up dereferencing a NULL pgdat:

  Unable to handle kernel NULL pointer dereference at virtual address 0000000000005040
  pc : get_swappiness+0x68/0xb0
  lr : try_to_shrink_lruvec+0x54/0x2c0
  Call trace:
   get_swappiness+0x68/0xb0 (P)
   try_to_shrink_lruvec+0x54/0x2c0
   shrink_one+0x8c/0x240
   shrink_node+0xc6c/0xe08
   do_try_to_free_pages+0xdc/0x570
   try_to_free_pages+0x124/0x278
   __alloc_frozen_pages_noprof+0x6e8/0xf78
   alloc_pages_mpol+0xc4/0x1c0
   alloc_frozen_pages_noprof+0x54/0xe8
   alloc_pages_noprof+0x1c/0x40
   atomic_pool_expand+0x98/0x300
   __dma_atomic_pool_init+0x58/0xd8
   dma_atomic_pool_init+0x154/0x170
   do_one_initcall+0x64/0x350
   kernel_init_freeable+0x214/0x430
   kernel_init+0x2c/0x208
   ret_from_fork+0x10/0x20

The classic LRU path and MGLRU before per-node lru_gen_folio lists
obtained the lruvec through mem_cgroup_lruvec(), which sets
lruvec->pgdat.

The above was hit on an arm64 kdump kernel (6.18-based) booted with
cgroup_disable=memory nr_cpus=4. It also reproduces on mainline on an
arm64 VM with CONFIG_ARM64_64K_PAGES=y, CONFIG_MEMCG=y,
CONFIG_LRU_GEN_ENABLED=y and:

  mem=160M nr_cpus=4 coherent_pool=512M cgroup_disable=memory

These options only serve to force direct reclaim early in boot; with
160M the boot eventually ends in OOM regardless. On mainline the first
LRU reclaim happens while unpacking the initramfs, and faults the same
way:

  Unable to handle kernel NULL pointer dereference at virtual address 0000000000005040
  pc : get_swappiness+0xd4/0x150
  Call trace:
   get_swappiness+0xd4/0x150 (P)
   try_to_shrink_lruvec+0x44/0x2b8
   shrink_one+0x80/0x1a0
   shrink_node+0xd98/0xea8
   do_try_to_free_pages+0xf4/0x4b0
   try_to_free_pages+0x110/0x360
   __alloc_frozen_pages_noprof+0x8b0/0x1718
   ...
   __filemap_get_folio_mpol+0x2ac/0x548
   simple_write_begin+0x44/0xf8
   page_symlink+0x178/0x198
   ...
   do_populate_rootfs+0x13c/0x200

Fixes: e4dde56cd208 ("mm: multi-gen LRU: per-node lru_gen_folio lists")
Cc: stable@vger.kernel.org # 6.3+
Assisted-by: LLM
Signed-off-by: Siddharth Chintamaneni <sidchintamaneni@gmail.com>
Signed-off-by: Hardik Garg <hargar@linux.microsoft.com>
---
 mm/vmscan.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/mm/vmscan.c b/mm/vmscan.c
index f11491ee9ed5..0c57e4f1eb15 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -5253,7 +5253,7 @@ static void lru_gen_shrink_node(struct pglist_data *pgdat, struct scan_control *
 		sc->nr_reclaimed = 0;
 
 	if (mem_cgroup_disabled())
-		shrink_one(&pgdat->__lruvec, sc);
+		shrink_one(mem_cgroup_lruvec(NULL, pgdat), sc);
 	else
 		shrink_many(pgdat, sc);
 
-- 
2.45.4



^ permalink raw reply related	[flat|nested] 16+ messages in thread

* [PATCH v2 2/2] mm: multi-gen LRU: fix NULL lruvec->pgdat deref in get_lruvec()
  2026-10-17  5:09 [PATCH v2 1/2] mm: multi-gen LRU: fix NULL lruvec->pgdat deref with memcg disabled on cmdline Siddharth Chintamaneni
@ 2026-10-17  5:09 ` Siddharth Chintamaneni
  2026-10-18 21:24   ` Barry Song
  2026-10-17 20:18 ` [PATCH v2 1/2] mm: multi-gen LRU: fix NULL lruvec->pgdat deref with memcg disabled on cmdline Andrew Morton
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 16+ messages in thread
From: Siddharth Chintamaneni @ 2026-10-17  5:09 UTC (permalink / raw)
  To: akpm, hannes
  Cc: david, mhocko, qi.zheng, shakeel.butt, ljs, kasong, baohua,
	axelrasmussen, yuanchu, weixugc, baoquan.he, baolin.wang, yuzhao,
	linux-mm, linux-kernel, apais, rlmenge, Siddharth Chintamaneni,
	stable, Hardik Garg

Andrew Morton reported that get_lruvec() has the same problem as
lru_gen_shrink_node(): when memcg is disabled on the command line
(cgroup_disable=memory), it returns &pgdat->__lruvec directly and so
bypasses the initialization of lruvec->pgdat. The MGLRU debugfs
interface calls get_lruvec(), then dereferences the pgdat pointer in
lru_gen_seq_show() and other paths, so these can hit the same NULL
dereference.

Fixes: ec1c86b25f4b ("mm: multi-gen LRU: groundwork")
Cc: stable@vger.kernel.org # 6.2+
Assisted-by: LLM
Signed-off-by: Siddharth Chintamaneni <sidchintamaneni@gmail.com>
Signed-off-by: Hardik Garg <hargar@linux.microsoft.com>
---
 mm/vmscan.c | 15 ++-------------
 1 file changed, 2 insertions(+), 13 deletions(-)

diff --git a/mm/vmscan.c b/mm/vmscan.c
index 0c57e4f1eb15..99e9f07fa2e1 100644
--- a/mm/vmscan.c
+++ b/mm/vmscan.c
@@ -2745,20 +2745,9 @@ static struct lruvec *get_lruvec(struct mem_cgroup *memcg, int nid)
 {
 	struct pglist_data *pgdat = NODE_DATA(nid);
 
-#ifdef CONFIG_MEMCG
-	if (memcg) {
-		struct lruvec *lruvec = &memcg->nodeinfo[nid]->lruvec;
-
-		/* see the comment in mem_cgroup_lruvec() */
-		if (!lruvec->pgdat)
-			lruvec->pgdat = pgdat;
-
-		return lruvec;
-	}
-#endif
-	VM_WARN_ON_ONCE(!mem_cgroup_disabled());
+	VM_WARN_ON_ONCE(!memcg && !mem_cgroup_disabled());
 
-	return &pgdat->__lruvec;
+	return mem_cgroup_lruvec(memcg, pgdat);
 }
 
 static int get_swappiness(struct lruvec *lruvec, struct scan_control *sc)
-- 
2.45.4



^ permalink raw reply related	[flat|nested] 16+ messages in thread

* Re: [PATCH v2 1/2] mm: multi-gen LRU: fix NULL lruvec->pgdat deref with memcg disabled on cmdline
  2026-10-17  5:09 [PATCH v2 1/2] mm: multi-gen LRU: fix NULL lruvec->pgdat deref with memcg disabled on cmdline Siddharth Chintamaneni
  2026-10-17  5:09 ` [PATCH v2 2/2] mm: multi-gen LRU: fix NULL lruvec->pgdat deref in get_lruvec() Siddharth Chintamaneni
@ 2026-10-17 20:18 ` Andrew Morton
  2026-10-18 21:04 ` Barry Song
  2026-10-21  6:15 ` Baolin Wang
  3 siblings, 0 replies; 16+ messages in thread
From: Andrew Morton @ 2026-10-17 20:18 UTC (permalink / raw)
  To: Siddharth Chintamaneni
  Cc: hannes, david, mhocko, qi.zheng, shakeel.butt, ljs, kasong,
	baohua, axelrasmussen, yuanchu, weixugc, baoquan.he, baolin.wang,
	yuzhao, linux-mm, linux-kernel, apais, rlmenge, stable,
	Hardik Garg

On Sat, 17 Oct 2026 05:09:27 +0000 Siddharth Chintamaneni <sidchintamaneni@gmail.com> wrote:

> When CONFIG_MEMCG=y, lruvec_pgdat() returns lruvec->pgdat, which for the
> per-node pgdat->__lruvec is left NULL by lruvec_init() and only filled in
> lazily by mem_cgroup_lruvec(). Normally that happens early, e.g. on the
> first folio_add_lru().
> 
> With cgroup_disable=memory, lru_gen_shrink_node() passes &pgdat->__lruvec
> directly to shrink_one(). If direct reclaim runs before anything has
> called mem_cgroup_lruvec() for the node, which can happen during early
> boot under memory pressure (e.g. a kdump kernel), get_swappiness() ends
> up dereferencing a NULL pgdat:

Thanks, v2 lgtm.

Maintainers: please review and let us know whether you agree with the
cc:stable on both.


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH v2 1/2] mm: multi-gen LRU: fix NULL lruvec->pgdat deref with memcg disabled on cmdline
  2026-10-17  5:09 [PATCH v2 1/2] mm: multi-gen LRU: fix NULL lruvec->pgdat deref with memcg disabled on cmdline Siddharth Chintamaneni
  2026-10-17  5:09 ` [PATCH v2 2/2] mm: multi-gen LRU: fix NULL lruvec->pgdat deref in get_lruvec() Siddharth Chintamaneni
  2026-10-17 20:18 ` [PATCH v2 1/2] mm: multi-gen LRU: fix NULL lruvec->pgdat deref with memcg disabled on cmdline Andrew Morton
@ 2026-10-18 21:04 ` Barry Song
  2026-10-19 20:37   ` Siddharth Chintamaneni
  2026-10-21  6:15 ` Baolin Wang
  3 siblings, 1 reply; 16+ messages in thread
From: Barry Song @ 2026-10-18 21:04 UTC (permalink / raw)
  To: Siddharth Chintamaneni
  Cc: akpm, hannes, david, mhocko, qi.zheng, shakeel.butt, ljs, kasong,
	axelrasmussen, yuanchu, weixugc, baoquan.he, baolin.wang, yuzhao,
	linux-mm, linux-kernel, apais, rlmenge, stable, Hardik Garg

On Sat, Oct 17, 2026 at 1:09 PM Siddharth Chintamaneni
<sidchintamaneni@gmail.com> wrote:
>
> When CONFIG_MEMCG=y, lruvec_pgdat() returns lruvec->pgdat, which for the
> per-node pgdat->__lruvec is left NULL by lruvec_init() and only filled in
> lazily by mem_cgroup_lruvec(). Normally that happens early, e.g. on the
> first folio_add_lru().
>
> With cgroup_disable=memory, lru_gen_shrink_node() passes &pgdat->__lruvec
> directly to shrink_one(). If direct reclaim runs before anything has
> called mem_cgroup_lruvec() for the node, which can happen during early
> boot under memory pressure (e.g. a kdump kernel), get_swappiness() ends
> up dereferencing a NULL pgdat:
>
>   Unable to handle kernel NULL pointer dereference at virtual address 0000000000005040
>   pc : get_swappiness+0x68/0xb0
>   lr : try_to_shrink_lruvec+0x54/0x2c0
>   Call trace:
>    get_swappiness+0x68/0xb0 (P)
>    try_to_shrink_lruvec+0x54/0x2c0
>    shrink_one+0x8c/0x240
>    shrink_node+0xc6c/0xe08
>    do_try_to_free_pages+0xdc/0x570
>    try_to_free_pages+0x124/0x278
>    __alloc_frozen_pages_noprof+0x6e8/0xf78
>    alloc_pages_mpol+0xc4/0x1c0
>    alloc_frozen_pages_noprof+0x54/0xe8
>    alloc_pages_noprof+0x1c/0x40
>    atomic_pool_expand+0x98/0x300
>    __dma_atomic_pool_init+0x58/0xd8
>    dma_atomic_pool_init+0x154/0x170
>    do_one_initcall+0x64/0x350
>    kernel_init_freeable+0x214/0x430
>    kernel_init+0x2c/0x208
>    ret_from_fork+0x10/0x20
>
> The classic LRU path and MGLRU before per-node lru_gen_folio lists
> obtained the lruvec through mem_cgroup_lruvec(), which sets
> lruvec->pgdat.
>
> The above was hit on an arm64 kdump kernel (6.18-based) booted with
> cgroup_disable=memory nr_cpus=4. It also reproduces on mainline on an
> arm64 VM with CONFIG_ARM64_64K_PAGES=y, CONFIG_MEMCG=y,
> CONFIG_LRU_GEN_ENABLED=y and:
>
>   mem=160M nr_cpus=4 coherent_pool=512M cgroup_disable=memory
>
> These options only serve to force direct reclaim early in boot; with
> 160M the boot eventually ends in OOM regardless. On mainline the first
> LRU reclaim happens while unpacking the initramfs, and faults the same
> way:
>
>   Unable to handle kernel NULL pointer dereference at virtual address 0000000000005040
>   pc : get_swappiness+0xd4/0x150
>   Call trace:
>    get_swappiness+0xd4/0x150 (P)
>    try_to_shrink_lruvec+0x44/0x2b8
>    shrink_one+0x80/0x1a0
>    shrink_node+0xd98/0xea8
>    do_try_to_free_pages+0xf4/0x4b0
>    try_to_free_pages+0x110/0x360
>    __alloc_frozen_pages_noprof+0x8b0/0x1718
>    ...
>    __filemap_get_folio_mpol+0x2ac/0x548
>    simple_write_begin+0x44/0xf8
>    page_symlink+0x178/0x198
>    ...
>    do_populate_rootfs+0x13c/0x200
>

So this happens before any folios are added to the LRU and before
the userspace init process starts?

> Fixes: e4dde56cd208 ("mm: multi-gen LRU: per-node lru_gen_folio lists")
> Cc: stable@vger.kernel.org # 6.3+
> Assisted-by: LLM
> Signed-off-by: Siddharth Chintamaneni <sidchintamaneni@gmail.com>
> Signed-off-by: Hardik Garg <hargar@linux.microsoft.com>
> ---
>  mm/vmscan.c | 2 +-
>  1 file changed, 1 insertion(+), 1 deletion(-)
>
> diff --git a/mm/vmscan.c b/mm/vmscan.c
> index f11491ee9ed5..0c57e4f1eb15 100644
> --- a/mm/vmscan.c
> +++ b/mm/vmscan.c
> @@ -5253,7 +5253,7 @@ static void lru_gen_shrink_node(struct pglist_data *pgdat, struct scan_control *
>                 sc->nr_reclaimed = 0;
>
>         if (mem_cgroup_disabled())
> -               shrink_one(&pgdat->__lruvec, sc);
> +               shrink_one(mem_cgroup_lruvec(NULL, pgdat), sc);

What's a bit weird is that even if we fix this here, with an empty
lruvec, we still won't be able to reclaim a single page. But it's
hard to see where we could break out early, right?

>         else
>                 shrink_many(pgdat, sc);

Best Regards
Barry


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH v2 2/2] mm: multi-gen LRU: fix NULL lruvec->pgdat deref in get_lruvec()
  2026-10-17  5:09 ` [PATCH v2 2/2] mm: multi-gen LRU: fix NULL lruvec->pgdat deref in get_lruvec() Siddharth Chintamaneni
@ 2026-10-18 21:24   ` Barry Song
  2026-10-19 20:33     ` Siddharth Chintamaneni
  0 siblings, 1 reply; 16+ messages in thread
From: Barry Song @ 2026-10-18 21:24 UTC (permalink / raw)
  To: Siddharth Chintamaneni
  Cc: akpm, hannes, david, mhocko, qi.zheng, shakeel.butt, ljs, kasong,
	axelrasmussen, yuanchu, weixugc, baoquan.he, baolin.wang, yuzhao,
	linux-mm, linux-kernel, apais, rlmenge, stable, Hardik Garg

On Sat, Oct 17, 2026 at 1:09 PM Siddharth Chintamaneni
<sidchintamaneni@gmail.com> wrote:
>
> Andrew Morton reported that get_lruvec() has the same problem as
> lru_gen_shrink_node(): when memcg is disabled on the command line
> (cgroup_disable=memory), it returns &pgdat->__lruvec directly and so
> bypasses the initialization of lruvec->pgdat. The MGLRU debugfs
> interface calls get_lruvec(), then dereferences the pgdat pointer in
> lru_gen_seq_show() and other paths, so these can hit the same NULL
> dereference.

So userspace must have booted, since otherwise we couldn't read
debugfs. The reachable case is therefore a memory node with no
LRU folios when debugfs is read? So this could happen on a multi-node
system where folios are landed on other nodes but not on this node?

>
> Fixes: ec1c86b25f4b ("mm: multi-gen LRU: groundwork")
> Cc: stable@vger.kernel.org # 6.2+
> Assisted-by: LLM
> Signed-off-by: Siddharth Chintamaneni <sidchintamaneni@gmail.com>
> Signed-off-by: Hardik Garg <hargar@linux.microsoft.com>
> ---
>  mm/vmscan.c | 15 ++-------------
>  1 file changed, 2 insertions(+), 13 deletions(-)
>
> diff --git a/mm/vmscan.c b/mm/vmscan.c
> index 0c57e4f1eb15..99e9f07fa2e1 100644
> --- a/mm/vmscan.c
> +++ b/mm/vmscan.c
> @@ -2745,20 +2745,9 @@ static struct lruvec *get_lruvec(struct mem_cgroup *memcg, int nid)
>  {
>         struct pglist_data *pgdat = NODE_DATA(nid);
>
> -#ifdef CONFIG_MEMCG
> -       if (memcg) {
> -               struct lruvec *lruvec = &memcg->nodeinfo[nid]->lruvec;
> -
> -               /* see the comment in mem_cgroup_lruvec() */
> -               if (!lruvec->pgdat)
> -                       lruvec->pgdat = pgdat;
> -
> -               return lruvec;
> -       }
> -#endif
> -       VM_WARN_ON_ONCE(!mem_cgroup_disabled());
> +       VM_WARN_ON_ONCE(!memcg && !mem_cgroup_disabled());
>
> -       return &pgdat->__lruvec;
> +       return mem_cgroup_lruvec(memcg, pgdat);
>  }

I've been struggling to understand why MGLRU needs its own
get_lruvec() when mem_cgroup_lruvec() already exists.
With your patch, it feels like we're one step closer to
getting rid of MGLRU's own variant in the future.

>
>  static int get_swappiness(struct lruvec *lruvec, struct scan_control *sc)
> --
> 2.45.4

Best Regards
Barry


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH v2 2/2] mm: multi-gen LRU: fix NULL lruvec->pgdat deref in get_lruvec()
  2026-10-18 21:24   ` Barry Song
@ 2026-10-19 20:33     ` Siddharth Chintamaneni
  2026-10-19 23:35       ` Barry Song
  0 siblings, 1 reply; 16+ messages in thread
From: Siddharth Chintamaneni @ 2026-10-19 20:33 UTC (permalink / raw)
  To: Barry Song
  Cc: akpm, hannes, david, mhocko, qi.zheng, shakeel.butt, ljs, kasong,
	axelrasmussen, yuanchu, weixugc, baoquan.he, baolin.wang, yuzhao,
	linux-mm, linux-kernel, apais, rlmenge, stable, Hardik Garg

On Sun, 18 Oct 2026 at 14:24, Barry Song <baohua@kernel.org> wrote:
>
> On Sat, Oct 17, 2026 at 1:09 PM Siddharth Chintamaneni
> <sidchintamaneni@gmail.com> wrote:
> >
> > Andrew Morton reported that get_lruvec() has the same problem as
> > lru_gen_shrink_node(): when memcg is disabled on the command line
> > (cgroup_disable=memory), it returns &pgdat->__lruvec directly and so
> > bypasses the initialization of lruvec->pgdat. The MGLRU debugfs
> > interface calls get_lruvec(), then dereferences the pgdat pointer in
> > lru_gen_seq_show() and other paths, so these can hit the same NULL
> > dereference.
>
> So userspace must have booted, since otherwise we couldn't read
> debugfs. The reachable case is therefore a memory node with no
> LRU folios when debugfs is read? So this could happen on a multi-node
> system where folios are landed on other nodes but not on this node?
>

That is my high level understanding. I'll let Andrew to comment if I
am missing something,

> >
> > Fixes: ec1c86b25f4b ("mm: multi-gen LRU: groundwork")
> > Cc: stable@vger.kernel.org # 6.2+
> > Assisted-by: LLM
> > Signed-off-by: Siddharth Chintamaneni <sidchintamaneni@gmail.com>
> > Signed-off-by: Hardik Garg <hargar@linux.microsoft.com>
> > ---
> >  mm/vmscan.c | 15 ++-------------
> >  1 file changed, 2 insertions(+), 13 deletions(-)
> >
> > diff --git a/mm/vmscan.c b/mm/vmscan.c
> > index 0c57e4f1eb15..99e9f07fa2e1 100644
> > --- a/mm/vmscan.c
> > +++ b/mm/vmscan.c
> > @@ -2745,20 +2745,9 @@ static struct lruvec *get_lruvec(struct mem_cgroup *memcg, int nid)
> >  {
> >         struct pglist_data *pgdat = NODE_DATA(nid);
> >
> > -#ifdef CONFIG_MEMCG
> > -       if (memcg) {
> > -               struct lruvec *lruvec = &memcg->nodeinfo[nid]->lruvec;
> > -
> > -               /* see the comment in mem_cgroup_lruvec() */
> > -               if (!lruvec->pgdat)
> > -                       lruvec->pgdat = pgdat;
> > -
> > -               return lruvec;
> > -       }
> > -#endif
> > -       VM_WARN_ON_ONCE(!mem_cgroup_disabled());
> > +       VM_WARN_ON_ONCE(!memcg && !mem_cgroup_disabled());
> >
> > -       return &pgdat->__lruvec;
> > +       return mem_cgroup_lruvec(memcg, pgdat);
> >  }
>
> I've been struggling to understand why MGLRU needs its own
> get_lruvec() when mem_cgroup_lruvec() already exists.
> With your patch, it feels like we're one step closer to
> getting rid of MGLRU's own variant in the future.
>

I agree with that! I'm happy to send a follow-up cleanup patch to
remove get_lruvec if that makes sense. Not sure how it will affect
stable backports.

> >
> >  static int get_swappiness(struct lruvec *lruvec, struct scan_control *sc)
> > --
> > 2.45.4
>
> Best Regards
> Barry


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH v2 1/2] mm: multi-gen LRU: fix NULL lruvec->pgdat deref with memcg disabled on cmdline
  2026-10-18 21:04 ` Barry Song
@ 2026-10-19 20:37   ` Siddharth Chintamaneni
  2026-10-19 22:59     ` Barry Song
  0 siblings, 1 reply; 16+ messages in thread
From: Siddharth Chintamaneni @ 2026-10-19 20:37 UTC (permalink / raw)
  To: Barry Song
  Cc: akpm, hannes, david, mhocko, qi.zheng, shakeel.butt, ljs, kasong,
	axelrasmussen, yuanchu, weixugc, baoquan.he, baolin.wang, yuzhao,
	linux-mm, linux-kernel, apais, rlmenge, stable, Hardik Garg

On Sun, 18 Oct 2026 at 14:05, Barry Song <baohua@kernel.org> wrote:
>
> On Sat, Oct 17, 2026 at 1:09 PM Siddharth Chintamaneni
> <sidchintamaneni@gmail.com> wrote:
> >
> > When CONFIG_MEMCG=y, lruvec_pgdat() returns lruvec->pgdat, which for the
> > per-node pgdat->__lruvec is left NULL by lruvec_init() and only filled in
> > lazily by mem_cgroup_lruvec(). Normally that happens early, e.g. on the
> > first folio_add_lru().
> >
> > With cgroup_disable=memory, lru_gen_shrink_node() passes &pgdat->__lruvec
> > directly to shrink_one(). If direct reclaim runs before anything has
> > called mem_cgroup_lruvec() for the node, which can happen during early
> > boot under memory pressure (e.g. a kdump kernel), get_swappiness() ends
> > up dereferencing a NULL pgdat:
> >
> >   Unable to handle kernel NULL pointer dereference at virtual address 0000000000005040
> >   pc : get_swappiness+0x68/0xb0
> >   lr : try_to_shrink_lruvec+0x54/0x2c0
> >   Call trace:
> >    get_swappiness+0x68/0xb0 (P)
> >    try_to_shrink_lruvec+0x54/0x2c0
> >    shrink_one+0x8c/0x240
> >    shrink_node+0xc6c/0xe08
> >    do_try_to_free_pages+0xdc/0x570
> >    try_to_free_pages+0x124/0x278
> >    __alloc_frozen_pages_noprof+0x6e8/0xf78
> >    alloc_pages_mpol+0xc4/0x1c0
> >    alloc_frozen_pages_noprof+0x54/0xe8
> >    alloc_pages_noprof+0x1c/0x40
> >    atomic_pool_expand+0x98/0x300
> >    __dma_atomic_pool_init+0x58/0xd8
> >    dma_atomic_pool_init+0x154/0x170
> >    do_one_initcall+0x64/0x350
> >    kernel_init_freeable+0x214/0x430
> >    kernel_init+0x2c/0x208
> >    ret_from_fork+0x10/0x20
> >
> > The classic LRU path and MGLRU before per-node lru_gen_folio lists
> > obtained the lruvec through mem_cgroup_lruvec(), which sets
> > lruvec->pgdat.
> >
> > The above was hit on an arm64 kdump kernel (6.18-based) booted with
> > cgroup_disable=memory nr_cpus=4. It also reproduces on mainline on an
> > arm64 VM with CONFIG_ARM64_64K_PAGES=y, CONFIG_MEMCG=y,
> > CONFIG_LRU_GEN_ENABLED=y and:
> >
> >   mem=160M nr_cpus=4 coherent_pool=512M cgroup_disable=memory
> >
> > These options only serve to force direct reclaim early in boot; with
> > 160M the boot eventually ends in OOM regardless. On mainline the first
> > LRU reclaim happens while unpacking the initramfs, and faults the same
> > way:
> >
> >   Unable to handle kernel NULL pointer dereference at virtual address 0000000000005040
> >   pc : get_swappiness+0xd4/0x150
> >   Call trace:
> >    get_swappiness+0xd4/0x150 (P)
> >    try_to_shrink_lruvec+0x44/0x2b8
> >    shrink_one+0x80/0x1a0
> >    shrink_node+0xd98/0xea8
> >    do_try_to_free_pages+0xf4/0x4b0
> >    try_to_free_pages+0x110/0x360
> >    __alloc_frozen_pages_noprof+0x8b0/0x1718
> >    ...
> >    __filemap_get_folio_mpol+0x2ac/0x548
> >    simple_write_begin+0x44/0xf8
> >    page_symlink+0x178/0x198
> >    ...
> >    do_populate_rootfs+0x13c/0x200
> >
>
> So this happens before any folios are added to the LRU and before
> the userspace init process starts?
>

Yes!

> > Fixes: e4dde56cd208 ("mm: multi-gen LRU: per-node lru_gen_folio lists")
> > Cc: stable@vger.kernel.org # 6.3+
> > Assisted-by: LLM
> > Signed-off-by: Siddharth Chintamaneni <sidchintamaneni@gmail.com>
> > Signed-off-by: Hardik Garg <hargar@linux.microsoft.com>
> > ---
> >  mm/vmscan.c | 2 +-
> >  1 file changed, 1 insertion(+), 1 deletion(-)
> >
> > diff --git a/mm/vmscan.c b/mm/vmscan.c
> > index f11491ee9ed5..0c57e4f1eb15 100644
> > --- a/mm/vmscan.c
> > +++ b/mm/vmscan.c
> > @@ -5253,7 +5253,7 @@ static void lru_gen_shrink_node(struct pglist_data *pgdat, struct scan_control *
> >                 sc->nr_reclaimed = 0;
> >
> >         if (mem_cgroup_disabled())
> > -               shrink_one(&pgdat->__lruvec, sc);
> > +               shrink_one(mem_cgroup_lruvec(NULL, pgdat), sc);
>
> What's a bit weird is that even if we fix this here, with an empty
> lruvec, we still won't be able to reclaim a single page. But it's
> hard to see where we could break out early, right?
>

I am not really sure about simple bailout logic. Please let me know if
anyone have any thoughts.

> >         else
> >                 shrink_many(pgdat, sc);
>
> Best Regards
> Barry


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH v2 1/2] mm: multi-gen LRU: fix NULL lruvec->pgdat deref with memcg disabled on cmdline
  2026-10-19 20:37   ` Siddharth Chintamaneni
@ 2026-10-19 22:59     ` Barry Song
  0 siblings, 0 replies; 16+ messages in thread
From: Barry Song @ 2026-10-19 22:59 UTC (permalink / raw)
  To: Siddharth Chintamaneni
  Cc: akpm, hannes, david, mhocko, qi.zheng, shakeel.butt, ljs, kasong,
	axelrasmussen, yuanchu, weixugc, baoquan.he, baolin.wang, yuzhao,
	linux-mm, linux-kernel, apais, rlmenge, stable, Hardik Garg

On Tue, Oct 20, 2026 at 4:37 AM Siddharth Chintamaneni
<sidchintamaneni@gmail.com> wrote:
[...]
> >
> > So this happens before any folios are added to the LRU and before
> > the userspace init process starts?
> >
>
> Yes!
>

Thanks for confirming.

> > > Fixes: e4dde56cd208 ("mm: multi-gen LRU: per-node lru_gen_folio lists")
> > > Cc: stable@vger.kernel.org # 6.3+
> > > Assisted-by: LLM
> > > Signed-off-by: Siddharth Chintamaneni <sidchintamaneni@gmail.com>
> > > Signed-off-by: Hardik Garg <hargar@linux.microsoft.com>
> > > ---
> > >  mm/vmscan.c | 2 +-
> > >  1 file changed, 1 insertion(+), 1 deletion(-)
> > >
> > > diff --git a/mm/vmscan.c b/mm/vmscan.c
> > > index f11491ee9ed5..0c57e4f1eb15 100644
> > > --- a/mm/vmscan.c
> > > +++ b/mm/vmscan.c
> > > @@ -5253,7 +5253,7 @@ static void lru_gen_shrink_node(struct pglist_data *pgdat, struct scan_control *
> > >                 sc->nr_reclaimed = 0;
> > >
> > >         if (mem_cgroup_disabled())
> > > -               shrink_one(&pgdat->__lruvec, sc);
> > > +               shrink_one(mem_cgroup_lruvec(NULL, pgdat), sc);
> >
> > What's a bit weird is that even if we fix this here, with an empty
> > lruvec, we still won't be able to reclaim a single page. But it's
> > hard to see where we could break out early, right?
> >
>
> I am not really sure about simple bailout logic. Please let me know if
> anyone have any thoughts.

No idea. I also feel it might not be worth doing, as this
only affects very uncommon scenarios. Some unnecessary
reclaiming is unlikely to hurt real-world workloads, so
there's no need to escalate this into a fix.

For the patch,

Reviewed-by: Barry Song <baohua@kernel.org>


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH v2 2/2] mm: multi-gen LRU: fix NULL lruvec->pgdat deref in get_lruvec()
  2026-10-19 20:33     ` Siddharth Chintamaneni
@ 2026-10-19 23:35       ` Barry Song
  2026-10-20  0:16         ` Siddharth Chintamaneni
  0 siblings, 1 reply; 16+ messages in thread
From: Barry Song @ 2026-10-19 23:35 UTC (permalink / raw)
  To: Siddharth Chintamaneni
  Cc: akpm, hannes, david, mhocko, qi.zheng, shakeel.butt, ljs, kasong,
	axelrasmussen, yuanchu, weixugc, baoquan.he, baolin.wang, yuzhao,
	linux-mm, linux-kernel, apais, rlmenge, stable, Hardik Garg

On Tue, Oct 20, 2026 at 4:33 AM Siddharth Chintamaneni
<sidchintamaneni@gmail.com> wrote:
>
> On Sun, 18 Oct 2026 at 14:24, Barry Song <baohua@kernel.org> wrote:
> >
> > On Sat, Oct 17, 2026 at 1:09 PM Siddharth Chintamaneni
> > <sidchintamaneni@gmail.com> wrote:
> > >
> > > Andrew Morton reported that get_lruvec() has the same problem as
> > > lru_gen_shrink_node(): when memcg is disabled on the command line
> > > (cgroup_disable=memory), it returns &pgdat->__lruvec directly and so
> > > bypasses the initialization of lruvec->pgdat. The MGLRU debugfs
> > > interface calls get_lruvec(), then dereferences the pgdat pointer in
> > > lru_gen_seq_show() and other paths, so these can hit the same NULL
> > > dereference.
> >
> > So userspace must have booted, since otherwise we couldn't read
> > debugfs. The reachable case is therefore a memory node with no
> > LRU folios when debugfs is read? So this could happen on a multi-node
> > system where folios are landed on other nodes but not on this node?
> >
>
> That is my high level understanding. I'll let Andrew to comment if I
> am missing something,
>

I made a silly toy setup by running two numa nodes with one of them has only 4KB
memory, the other one has 1G-4K:

qemu-system-aarch64 -M virt,gic-version=3,mte=off -cpu cortex-a53 -nographic \
-smp 4 \
-m 1G \
-object memory-backend-ram,id=ram0,size=1048572K \
-object memory-backend-ram,id=ram1,size=4K \
-numa node,nodeid=0,memdev=ram0,cpus=0-1 \
-numa node,nodeid=1,memdev=ram1,cpus=2-3 \
  -kernel arch/arm64/boot/Image \
 -nographic -append "cgroup_disable=memory
...

Then I got a machine where node1 is very difficult to have a page in lru.

then i ran the debugfs for mglru:

/ # cat /sys/kernel/debug/lru_gen
memcg 0
 node     0
          0      14972          0         953
          1      14972          0           1
          2      14972         18        3308
          3      14972          0           0
 node     1
          0      14972          0           0
          1      14972          0           0
          2      14972          0           0
          3      14972          0           0

now you see node 1 has no page in lru but yet i didn't have any crash.

i think i will need more time to figure out what is the problem and how to
reproduce it.


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH v2 2/2] mm: multi-gen LRU: fix NULL lruvec->pgdat deref in get_lruvec()
  2026-10-19 23:35       ` Barry Song
@ 2026-10-20  0:16         ` Siddharth Chintamaneni
  2026-10-20  0:43           ` Barry Song
  0 siblings, 1 reply; 16+ messages in thread
From: Siddharth Chintamaneni @ 2026-10-20  0:16 UTC (permalink / raw)
  To: Barry Song
  Cc: akpm, hannes, david, mhocko, qi.zheng, shakeel.butt, ljs, kasong,
	axelrasmussen, yuanchu, weixugc, baoquan.he, baolin.wang, yuzhao,
	linux-mm, linux-kernel, apais, rlmenge, stable, Hardik Garg

On Mon, 19 Oct 2026 at 16:36, Barry Song <baohua@kernel.org> wrote:
>
> On Tue, Oct 20, 2026 at 4:33 AM Siddharth Chintamaneni
> <sidchintamaneni@gmail.com> wrote:
> >
> > On Sun, 18 Oct 2026 at 14:24, Barry Song <baohua@kernel.org> wrote:
> > >
> > > On Sat, Oct 17, 2026 at 1:09 PM Siddharth Chintamaneni
> > > <sidchintamaneni@gmail.com> wrote:
> > > >
> > > > Andrew Morton reported that get_lruvec() has the same problem as
> > > > lru_gen_shrink_node(): when memcg is disabled on the command line
> > > > (cgroup_disable=memory), it returns &pgdat->__lruvec directly and so
> > > > bypasses the initialization of lruvec->pgdat. The MGLRU debugfs
> > > > interface calls get_lruvec(), then dereferences the pgdat pointer in
> > > > lru_gen_seq_show() and other paths, so these can hit the same NULL
> > > > dereference.
> > >
> > > So userspace must have booted, since otherwise we couldn't read
> > > debugfs. The reachable case is therefore a memory node with no
> > > LRU folios when debugfs is read? So this could happen on a multi-node
> > > system where folios are landed on other nodes but not on this node?
> > >
> >
> > That is my high level understanding. I'll let Andrew to comment if I
> > am missing something,
> >
>
> I made a silly toy setup by running two numa nodes with one of them has only 4KB
> memory, the other one has 1G-4K:
>
> qemu-system-aarch64 -M virt,gic-version=3,mte=off -cpu cortex-a53 -nographic \
> -smp 4 \
> -m 1G \
> -object memory-backend-ram,id=ram0,size=1048572K \
> -object memory-backend-ram,id=ram1,size=4K \
> -numa node,nodeid=0,memdev=ram0,cpus=0-1 \
> -numa node,nodeid=1,memdev=ram1,cpus=2-3 \
>   -kernel arch/arm64/boot/Image \
>  -nographic -append "cgroup_disable=memory
> ...
>
> Then I got a machine where node1 is very difficult to have a page in lru.
>
> then i ran the debugfs for mglru:
>
> / # cat /sys/kernel/debug/lru_gen
> memcg 0
>  node     0
>           0      14972          0         953
>           1      14972          0           1
>           2      14972         18        3308
>           3      14972          0           0
>  node     1
>           0      14972          0           0
>           1      14972          0           0
>           2      14972          0           0
>           3      14972          0           0
>
> now you see node 1 has no page in lru but yet i didn't have any crash.
>
> i think i will need more time to figure out what is the problem and how to
> reproduce it.

I've asked AI to repro the issue with the similar setup

qemu-system-aarch64 -M virt,gic-version=3,mte=off -cpu cortex-a53 -nographic \
  -smp 4 -m 1G \
  -object memory-backend-ram,id=ram0,size=1048512K \
  -object memory-backend-ram,id=ram1,size=64K \
  -numa node,nodeid=0,memdev=ram0,cpus=0-1 \
  -numa node,nodeid=1,memdev=ram1,cpus=2-3 \
  -kernel "$KERNEL" \
  -initrd "$INITRD" \
  -no-reboot \
  -append "console=ttyAMA0 cgroup_disable=memory
initcall_blacklist=kswapd_init panic=-1"

and it was able to repo it. AI identified that kswapd is initializing
the pgdat during boot, so blacklisting kswap_init was able to repro
the crash when reading /sys/kernel/debug/lru_gen

[    1.347868] Unable to handle kernel access to user memory outside
uaccess routines at virtual address 0000000000005040
[    1.347920] Mem abort info:
[    1.347929]   ESR = 0x0000000096000005
[    1.347950]   EC = 0x25: DABT (current EL), IL = 32 bits
[    1.347964]   SET = 0, FnV = 0
[    1.347974]   EA = 0, S1PTW = 0
[    1.347986]   FSC = 0x05: level 1 translation fault
[    1.348249] Data abort info:
[    1.348277]   ISV = 0, ISS = 0x00000005, ISS2 = 0x00000000
[    1.348291]   CM = 0, WnR = 0, TnD = 0, TagAccess = 0
[    1.348302]   GCS = 0, Overlay = 0, DirtyBit = 0
[    1.348345] user pgtable: 64k pages, 48-bit VAs, pgdp=00000000485f6600
[    1.348369] [0000000000005040] pgd=0800000043640403,
p4d=0800000043640403, pud=0800000043640403, pmd=0800000043620403,
pte=0000000000000000
[    1.349148] Internal error: Oops: 0000000096000005 [#1]  SMP
[    1.350414] Modules linked in:
[    1.350896] CPU: 0 UID: 0 PID: 1 Comm: init Not tainted 7.3.0-rc7+
#9 PREEMPT(full)
[    1.351111] Hardware name: linux,dummy-virt (DT)
[    1.351411] pstate: 80400005 (Nzcv daif +PAN -UAO -TCO -DIT -SSBS BTYPE=--)
[    1.351581] pc : lru_gen_seq_show+0x5c/0x460
[    1.351800] lr : lru_gen_seq_show+0x4c/0x460
[    1.351898] sp : ffff80008034f9a0
[    1.351975] x29: ffff80008034fa20 x28: ffff0000035e67e8 x27: 0000000000400cc0
[    1.352297] x26: 000000007fff0000 x25: ffff0000035e67d8 x24: 0000000000000000
[    1.352454] x23: ffff80008034fb00 x22: ffff80008034fb28 x21: 0000000000000000
[    1.352626] x20: ffff0000035e67b0 x19: ffff00003ffc2f20 x18: ffff800080360060
[    1.352775] x17: 0000000000000000 x16: 0000000000000000 x15: 0000000000000000
[    1.352915] x14: 0000000000000000 x13: 0000000000000000 x12: 0000000000000000
[    1.353058] x11: 0000000000000000 x10: 0000000000000000 x9 : ffffa3468a020c5c
[    1.353226] x8 : ffff000008b07000 x7 : 0000000000000000 x6 : 000000000000003f
[    1.353367] x5 : 00000000ffffffff x4 : 0000000000000003 x3 : 0000000000001000
[    1.353511] x2 : ffff00003ffc2f20 x1 : 0000000000004000 x0 : 0000000000000000
[    1.353732] Call trace:
[    1.353925]  lru_gen_seq_show+0x5c/0x460 (P)
[    1.354110]  seq_read_iter+0xec/0x4c0
[    1.354213]  seq_read+0xfc/0x148
[    1.354290]  full_proxy_read+0x78/0xc0
[    1.354377]  vfs_read+0xd0/0x338
[    1.354453]  ksys_read+0x7c/0x120
[    1.354528]  __arm64_sys_read+0x24/0x40
[    1.354611]  invoke_syscall+0xa8/0x120
[    1.354695]  el0_svc_common.constprop.0+0x4c/0x100
[    1.354791]  do_el0_svc+0x28/0x40
[    1.354865]  el0_svc+0x48/0x2b0
[    1.354939]  el0t_64_sync_handler+0xa8/0xf0
[    1.355026]  el0t_64_sync+0x1b8/0x1c0
[    1.355250] Code: f9000fe0 f9432661 aa1303e2 91401021 (b9504033)
[    1.355647] ---[ end trace 0000000000000000 ]---
[    1.356555] Kernel panic - not syncing: Attempted to kill init!
exitcode=0x0000000b
[    1.356888] SMP: stopping secondary CPUs
[    1.357525] Kernel Offset: 0x234609c00000 from 0xffff800080000000
[    1.357675] PHYS_OFFSET: 0x40000000
[    1.357762] CPU features: 0x0,00000000,09000020,00100002,0800821b
[    1.357901] Memory Limit: none


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH v2 2/2] mm: multi-gen LRU: fix NULL lruvec->pgdat deref in get_lruvec()
  2026-10-20  0:16         ` Siddharth Chintamaneni
@ 2026-10-20  0:43           ` Barry Song
  2026-10-20  1:02             ` Siddharth Chintamaneni
  0 siblings, 1 reply; 16+ messages in thread
From: Barry Song @ 2026-10-20  0:43 UTC (permalink / raw)
  To: Siddharth Chintamaneni
  Cc: akpm, hannes, david, mhocko, qi.zheng, shakeel.butt, ljs, kasong,
	axelrasmussen, yuanchu, weixugc, baoquan.he, baolin.wang, yuzhao,
	linux-mm, linux-kernel, apais, rlmenge, stable, Hardik Garg

On Tue, Oct 20, 2026 at 8:16 AM Siddharth Chintamaneni
<sidchintamaneni@gmail.com> wrote:
>
> On Mon, 19 Oct 2026 at 16:36, Barry Song <baohua@kernel.org> wrote:
> >
> > On Tue, Oct 20, 2026 at 4:33 AM Siddharth Chintamaneni
> > <sidchintamaneni@gmail.com> wrote:
> > >
> > > On Sun, 18 Oct 2026 at 14:24, Barry Song <baohua@kernel.org> wrote:
> > > >
> > > > On Sat, Oct 17, 2026 at 1:09 PM Siddharth Chintamaneni
> > > > <sidchintamaneni@gmail.com> wrote:
> > > > >
> > > > > Andrew Morton reported that get_lruvec() has the same problem as
> > > > > lru_gen_shrink_node(): when memcg is disabled on the command line
> > > > > (cgroup_disable=memory), it returns &pgdat->__lruvec directly and so
> > > > > bypasses the initialization of lruvec->pgdat. The MGLRU debugfs
> > > > > interface calls get_lruvec(), then dereferences the pgdat pointer in
> > > > > lru_gen_seq_show() and other paths, so these can hit the same NULL
> > > > > dereference.
> > > >
> > > > So userspace must have booted, since otherwise we couldn't read
> > > > debugfs. The reachable case is therefore a memory node with no
> > > > LRU folios when debugfs is read? So this could happen on a multi-node
> > > > system where folios are landed on other nodes but not on this node?
> > > >
> > >
> > > That is my high level understanding. I'll let Andrew to comment if I
> > > am missing something,
> > >
> >
> > I made a silly toy setup by running two numa nodes with one of them has only 4KB
> > memory, the other one has 1G-4K:
> >
> > qemu-system-aarch64 -M virt,gic-version=3,mte=off -cpu cortex-a53 -nographic \
> > -smp 4 \
> > -m 1G \
> > -object memory-backend-ram,id=ram0,size=1048572K \
> > -object memory-backend-ram,id=ram1,size=4K \
> > -numa node,nodeid=0,memdev=ram0,cpus=0-1 \
> > -numa node,nodeid=1,memdev=ram1,cpus=2-3 \
> >   -kernel arch/arm64/boot/Image \
> >  -nographic -append "cgroup_disable=memory
> > ...
> >
> > Then I got a machine where node1 is very difficult to have a page in lru.
> >
> > then i ran the debugfs for mglru:
> >
> > / # cat /sys/kernel/debug/lru_gen
> > memcg 0
> >  node     0
> >           0      14972          0         953
> >           1      14972          0           1
> >           2      14972         18        3308
> >           3      14972          0           0
> >  node     1
> >           0      14972          0           0
> >           1      14972          0           0
> >           2      14972          0           0
> >           3      14972          0           0
> >
> > now you see node 1 has no page in lru but yet i didn't have any crash.
> >
> > i think i will need more time to figure out what is the problem and how to
> > reproduce it.
>
> I've asked AI to repro the issue with the similar setup
>
> qemu-system-aarch64 -M virt,gic-version=3,mte=off -cpu cortex-a53 -nographic \
>   -smp 4 -m 1G \
>   -object memory-backend-ram,id=ram0,size=1048512K \
>   -object memory-backend-ram,id=ram1,size=64K \
>   -numa node,nodeid=0,memdev=ram0,cpus=0-1 \
>   -numa node,nodeid=1,memdev=ram1,cpus=2-3 \
>   -kernel "$KERNEL" \
>   -initrd "$INITRD" \
>   -no-reboot \
>   -append "console=ttyAMA0 cgroup_disable=memory
> initcall_blacklist=kswapd_init panic=-1"
>
> and it was able to repo it. AI identified that kswapd is initializing
> the pgdat during boot, so blacklisting kswap_init was able to repro
> the crash when reading /sys/kernel/debug/lru_gen

Thanks!
I guess you mean the following:

-module_init(kswapd_init)
+//module_init(kswapd_init)

Yes, I can reproduce it with the above as well.

But kswapd_init() is definitely called before we enter userspace,
so this doesn't seem to be an issue here?

So I feel there is no bug here, but this cleanup is still welcome.
Am I missing any other scenarios?

Best Regards
Barry


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH v2 2/2] mm: multi-gen LRU: fix NULL lruvec->pgdat deref in get_lruvec()
  2026-10-20  0:43           ` Barry Song
@ 2026-10-20  1:02             ` Siddharth Chintamaneni
  2026-10-20  1:20               ` Barry Song
  0 siblings, 1 reply; 16+ messages in thread
From: Siddharth Chintamaneni @ 2026-10-20  1:02 UTC (permalink / raw)
  To: Barry Song
  Cc: akpm, hannes, david, mhocko, qi.zheng, shakeel.butt, ljs, kasong,
	axelrasmussen, yuanchu, weixugc, baoquan.he, baolin.wang, yuzhao,
	linux-mm, linux-kernel, apais, rlmenge, stable, Hardik Garg

On Mon, 19 Oct 2026 at 17:43, Barry Song <baohua@kernel.org> wrote:
>
> On Tue, Oct 20, 2026 at 8:16 AM Siddharth Chintamaneni
> <sidchintamaneni@gmail.com> wrote:
> >
> > On Mon, 19 Oct 2026 at 16:36, Barry Song <baohua@kernel.org> wrote:
> > >
> > > On Tue, Oct 20, 2026 at 4:33 AM Siddharth Chintamaneni
> > > <sidchintamaneni@gmail.com> wrote:
> > > >
> > > > On Sun, 18 Oct 2026 at 14:24, Barry Song <baohua@kernel.org> wrote:
> > > > >
> > > > > On Sat, Oct 17, 2026 at 1:09 PM Siddharth Chintamaneni
> > > > > <sidchintamaneni@gmail.com> wrote:
> > > > > >
> > > > > > Andrew Morton reported that get_lruvec() has the same problem as
> > > > > > lru_gen_shrink_node(): when memcg is disabled on the command line
> > > > > > (cgroup_disable=memory), it returns &pgdat->__lruvec directly and so
> > > > > > bypasses the initialization of lruvec->pgdat. The MGLRU debugfs
> > > > > > interface calls get_lruvec(), then dereferences the pgdat pointer in
> > > > > > lru_gen_seq_show() and other paths, so these can hit the same NULL
> > > > > > dereference.
> > > > >
> > > > > So userspace must have booted, since otherwise we couldn't read
> > > > > debugfs. The reachable case is therefore a memory node with no
> > > > > LRU folios when debugfs is read? So this could happen on a multi-node
> > > > > system where folios are landed on other nodes but not on this node?
> > > > >
> > > >
> > > > That is my high level understanding. I'll let Andrew to comment if I
> > > > am missing something,
> > > >
> > >
> > > I made a silly toy setup by running two numa nodes with one of them has only 4KB
> > > memory, the other one has 1G-4K:
> > >
> > > qemu-system-aarch64 -M virt,gic-version=3,mte=off -cpu cortex-a53 -nographic \
> > > -smp 4 \
> > > -m 1G \
> > > -object memory-backend-ram,id=ram0,size=1048572K \
> > > -object memory-backend-ram,id=ram1,size=4K \
> > > -numa node,nodeid=0,memdev=ram0,cpus=0-1 \
> > > -numa node,nodeid=1,memdev=ram1,cpus=2-3 \
> > >   -kernel arch/arm64/boot/Image \
> > >  -nographic -append "cgroup_disable=memory
> > > ...
> > >
> > > Then I got a machine where node1 is very difficult to have a page in lru.
> > >
> > > then i ran the debugfs for mglru:
> > >
> > > / # cat /sys/kernel/debug/lru_gen
> > > memcg 0
> > >  node     0
> > >           0      14972          0         953
> > >           1      14972          0           1
> > >           2      14972         18        3308
> > >           3      14972          0           0
> > >  node     1
> > >           0      14972          0           0
> > >           1      14972          0           0
> > >           2      14972          0           0
> > >           3      14972          0           0
> > >
> > > now you see node 1 has no page in lru but yet i didn't have any crash.
> > >
> > > i think i will need more time to figure out what is the problem and how to
> > > reproduce it.
> >
> > I've asked AI to repro the issue with the similar setup
> >
> > qemu-system-aarch64 -M virt,gic-version=3,mte=off -cpu cortex-a53 -nographic \
> >   -smp 4 -m 1G \
> >   -object memory-backend-ram,id=ram0,size=1048512K \
> >   -object memory-backend-ram,id=ram1,size=64K \
> >   -numa node,nodeid=0,memdev=ram0,cpus=0-1 \
> >   -numa node,nodeid=1,memdev=ram1,cpus=2-3 \
> >   -kernel "$KERNEL" \
> >   -initrd "$INITRD" \
> >   -no-reboot \
> >   -append "console=ttyAMA0 cgroup_disable=memory
> > initcall_blacklist=kswapd_init panic=-1"
> >
> > and it was able to repo it. AI identified that kswapd is initializing
> > the pgdat during boot, so blacklisting kswap_init was able to repro
> > the crash when reading /sys/kernel/debug/lru_gen
>
> Thanks!
> I guess you mean the following:
>
> -module_init(kswapd_init)
> +//module_init(kswapd_init)
>
> Yes, I can reproduce it with the above as well.
>
> But kswapd_init() is definitely called before we enter userspace,
> so this doesn't seem to be an issue here?
>

Looking at the callee sites of get_lruvec, all of them happen after
boot. So it should be fine. Anyway code clean up will address the
issue.

> So I feel there is no bug here, but this cleanup is still welcome.

Are you ok with the patch as is, or would you recommend  changing the
callsite of get_lruvec to mem_cgroup_lruvec?

> Am I missing any other scenarios?
>
> Best Regards
> Barry


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH v2 2/2] mm: multi-gen LRU: fix NULL lruvec->pgdat deref in get_lruvec()
  2026-10-20  1:02             ` Siddharth Chintamaneni
@ 2026-10-20  1:20               ` Barry Song
  2026-10-20  1:36                 ` Siddharth Chintamaneni
  0 siblings, 1 reply; 16+ messages in thread
From: Barry Song @ 2026-10-20  1:20 UTC (permalink / raw)
  To: Siddharth Chintamaneni
  Cc: akpm, hannes, david, mhocko, qi.zheng, shakeel.butt, ljs, kasong,
	axelrasmussen, yuanchu, weixugc, baoquan.he, baolin.wang, yuzhao,
	linux-mm, linux-kernel, apais, rlmenge, stable, Hardik Garg

On Tue, Oct 20, 2026 at 9:02 AM Siddharth Chintamaneni
<sidchintamaneni@gmail.com> wrote:
>
[...]
> >
> > Thanks!
> > I guess you mean the following:
> >
> > -module_init(kswapd_init)
> > +//module_init(kswapd_init)
> >
> > Yes, I can reproduce it with the above as well.
> >
> > But kswapd_init() is definitely called before we enter userspace,
> > so this doesn't seem to be an issue here?
> >
>
> Looking at the callee sites of get_lruvec, all of them happen after
> boot. So it should be fine. Anyway code clean up will address the
> issue.
>
> > So I feel there is no bug here, but this cleanup is still welcome.
>
> Are you ok with the patch as is, or would you recommend  changing the
> callsite of get_lruvec to mem_cgroup_lruvec?

Overall, I'd be happy to see MGLRU-specific code being removed.

If possible, could we split this into a two-patch series? The
first patch would be this one, and the second would drop
get_lruvec().

In any case, if this isn't a bug fix, there's no need for a Fixes tag or
a stable backport.

BTW, I've been struggling to understand why MGLRU introduced
its own version in the first place. Maybe your follow-up
patchset will help clarify that :-)

Best Regards
Barry


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH v2 2/2] mm: multi-gen LRU: fix NULL lruvec->pgdat deref in get_lruvec()
  2026-10-20  1:20               ` Barry Song
@ 2026-10-20  1:36                 ` Siddharth Chintamaneni
  2026-10-20  5:19                   ` Barry Song
  0 siblings, 1 reply; 16+ messages in thread
From: Siddharth Chintamaneni @ 2026-10-20  1:36 UTC (permalink / raw)
  To: Barry Song
  Cc: akpm, hannes, david, mhocko, qi.zheng, shakeel.butt, ljs, kasong,
	axelrasmussen, yuanchu, weixugc, baoquan.he, baolin.wang, yuzhao,
	linux-mm, linux-kernel, apais, rlmenge, stable, Hardik Garg

On Mon, 19 Oct 2026 at 18:20, Barry Song <baohua@kernel.org> wrote:
>
> On Tue, Oct 20, 2026 at 9:02 AM Siddharth Chintamaneni
> <sidchintamaneni@gmail.com> wrote:
> >
> [...]
> > >
> > > Thanks!
> > > I guess you mean the following:
> > >
> > > -module_init(kswapd_init)
> > > +//module_init(kswapd_init)
> > >
> > > Yes, I can reproduce it with the above as well.
> > >
> > > But kswapd_init() is definitely called before we enter userspace,
> > > so this doesn't seem to be an issue here?
> > >
> >
> > Looking at the callee sites of get_lruvec, all of them happen after
> > boot. So it should be fine. Anyway code clean up will address the
> > issue.
> >
> > > So I feel there is no bug here, but this cleanup is still welcome.
> >
> > Are you ok with the patch as is, or would you recommend  changing the
> > callsite of get_lruvec to mem_cgroup_lruvec?
>
> Overall, I'd be happy to see MGLRU-specific code being removed.
>
> If possible, could we split this into a two-patch series? The
> first patch would be this one, and the second would drop
> get_lruvec().
>
> In any case, if this isn't a bug fix, there's no need for a Fixes tag or
> a stable backport.
>

If that is the case, do we still need this patch?

> BTW, I've been struggling to understand why MGLRU introduced
> its own version in the first place. Maybe your follow-up
> patchset will help clarify that :-)
>

I have a theory -
when MGLRU was introduced lru_vec also returns NULL
https://elixir.bootlin.com/linux/v6.1.190/source/mm/vmscan.c#L3163
that is not the case anymore. I haven't looked at the refactoring that
happened later around it yet.
That might be the reason but I could be wrong.

> Best Regards
> Barry


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH v2 2/2] mm: multi-gen LRU: fix NULL lruvec->pgdat deref in get_lruvec()
  2026-10-20  1:36                 ` Siddharth Chintamaneni
@ 2026-10-20  5:19                   ` Barry Song
  0 siblings, 0 replies; 16+ messages in thread
From: Barry Song @ 2026-10-20  5:19 UTC (permalink / raw)
  To: Siddharth Chintamaneni
  Cc: akpm, hannes, david, mhocko, qi.zheng, shakeel.butt, ljs, kasong,
	axelrasmussen, yuanchu, weixugc, baoquan.he, baolin.wang, yuzhao,
	linux-mm, linux-kernel, apais, rlmenge, stable, Hardik Garg

On Tue, Oct 20, 2026 at 9:37 AM Siddharth Chintamaneni
<sidchintamaneni@gmail.com> wrote:
[..]
>
> If that is the case, do we still need this patch?
>

yes. We need it as cleanup patches. not bug fixes.

Best Regards
Barry


^ permalink raw reply	[flat|nested] 16+ messages in thread

* Re: [PATCH v2 1/2] mm: multi-gen LRU: fix NULL lruvec->pgdat deref with memcg disabled on cmdline
  2026-10-17  5:09 [PATCH v2 1/2] mm: multi-gen LRU: fix NULL lruvec->pgdat deref with memcg disabled on cmdline Siddharth Chintamaneni
                   ` (2 preceding siblings ...)
  2026-10-18 21:04 ` Barry Song
@ 2026-10-21  6:15 ` Baolin Wang
  3 siblings, 0 replies; 16+ messages in thread
From: Baolin Wang @ 2026-10-21  6:15 UTC (permalink / raw)
  To: Siddharth Chintamaneni, akpm, hannes
  Cc: david, mhocko, qi.zheng, shakeel.butt, ljs, kasong, baohua,
	axelrasmussen, yuanchu, weixugc, baoquan.he, yuzhao, linux-mm,
	linux-kernel, apais, rlmenge, stable, Hardik Garg



On 10/17/26 1:09 PM, Siddharth Chintamaneni wrote:
> When CONFIG_MEMCG=y, lruvec_pgdat() returns lruvec->pgdat, which for the
> per-node pgdat->__lruvec is left NULL by lruvec_init() and only filled in
> lazily by mem_cgroup_lruvec(). Normally that happens early, e.g. on the
> first folio_add_lru().
> 
> With cgroup_disable=memory, lru_gen_shrink_node() passes &pgdat->__lruvec
> directly to shrink_one(). If direct reclaim runs before anything has
> called mem_cgroup_lruvec() for the node, which can happen during early
> boot under memory pressure (e.g. a kdump kernel), get_swappiness() ends
> up dereferencing a NULL pgdat:
> 
>    Unable to handle kernel NULL pointer dereference at virtual address 0000000000005040
>    pc : get_swappiness+0x68/0xb0
>    lr : try_to_shrink_lruvec+0x54/0x2c0
>    Call trace:
>     get_swappiness+0x68/0xb0 (P)
>     try_to_shrink_lruvec+0x54/0x2c0
>     shrink_one+0x8c/0x240
>     shrink_node+0xc6c/0xe08
>     do_try_to_free_pages+0xdc/0x570
>     try_to_free_pages+0x124/0x278
>     __alloc_frozen_pages_noprof+0x6e8/0xf78
>     alloc_pages_mpol+0xc4/0x1c0
>     alloc_frozen_pages_noprof+0x54/0xe8
>     alloc_pages_noprof+0x1c/0x40
>     atomic_pool_expand+0x98/0x300
>     __dma_atomic_pool_init+0x58/0xd8
>     dma_atomic_pool_init+0x154/0x170
>     do_one_initcall+0x64/0x350
>     kernel_init_freeable+0x214/0x430
>     kernel_init+0x2c/0x208
>     ret_from_fork+0x10/0x20
> 
> The classic LRU path and MGLRU before per-node lru_gen_folio lists
> obtained the lruvec through mem_cgroup_lruvec(), which sets
> lruvec->pgdat.
> 
> The above was hit on an arm64 kdump kernel (6.18-based) booted with
> cgroup_disable=memory nr_cpus=4. It also reproduces on mainline on an
> arm64 VM with CONFIG_ARM64_64K_PAGES=y, CONFIG_MEMCG=y,
> CONFIG_LRU_GEN_ENABLED=y and:
> 
>    mem=160M nr_cpus=4 coherent_pool=512M cgroup_disable=memory
> 
> These options only serve to force direct reclaim early in boot; with
> 160M the boot eventually ends in OOM regardless. On mainline the first
> LRU reclaim happens while unpacking the initramfs, and faults the same
> way:
> 
>    Unable to handle kernel NULL pointer dereference at virtual address 0000000000005040
>    pc : get_swappiness+0xd4/0x150
>    Call trace:
>     get_swappiness+0xd4/0x150 (P)
>     try_to_shrink_lruvec+0x44/0x2b8
>     shrink_one+0x80/0x1a0
>     shrink_node+0xd98/0xea8
>     do_try_to_free_pages+0xf4/0x4b0
>     try_to_free_pages+0x110/0x360
>     __alloc_frozen_pages_noprof+0x8b0/0x1718
>     ...
>     __filemap_get_folio_mpol+0x2ac/0x548
>     simple_write_begin+0x44/0xf8
>     page_symlink+0x178/0x198
>     ...
>     do_populate_rootfs+0x13c/0x200
> 
> Fixes: e4dde56cd208 ("mm: multi-gen LRU: per-node lru_gen_folio lists")
> Cc: stable@vger.kernel.org # 6.3+
> Assisted-by: LLM
> Signed-off-by: Siddharth Chintamaneni <sidchintamaneni@gmail.com>
> Signed-off-by: Hardik Garg <hargar@linux.microsoft.com>
> ---

Good catch. LGTM.
Reviewed-by: Baolin Wang <baolin.wang@linux.alibaba.com>


^ permalink raw reply	[flat|nested] 16+ messages in thread

end of thread, other threads:[~2026-10-21  6:16 UTC | newest]

Thread overview: 16+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-17  5:09 [PATCH v2 1/2] mm: multi-gen LRU: fix NULL lruvec->pgdat deref with memcg disabled on cmdline Siddharth Chintamaneni
2026-10-17  5:09 ` [PATCH v2 2/2] mm: multi-gen LRU: fix NULL lruvec->pgdat deref in get_lruvec() Siddharth Chintamaneni
2026-10-18 21:24   ` Barry Song
2026-10-19 20:33     ` Siddharth Chintamaneni
2026-10-19 23:35       ` Barry Song
2026-10-20  0:16         ` Siddharth Chintamaneni
2026-10-20  0:43           ` Barry Song
2026-10-20  1:02             ` Siddharth Chintamaneni
2026-10-20  1:20               ` Barry Song
2026-10-20  1:36                 ` Siddharth Chintamaneni
2026-10-20  5:19                   ` Barry Song
2026-10-17 20:18 ` [PATCH v2 1/2] mm: multi-gen LRU: fix NULL lruvec->pgdat deref with memcg disabled on cmdline Andrew Morton
2026-10-18 21:04 ` Barry Song
2026-10-19 20:37   ` Siddharth Chintamaneni
2026-10-19 22:59     ` Barry Song
2026-10-21  6:15 ` Baolin Wang

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox