* [PATCH] mm: page_ext: validate section in for_each_page_ext iterator
@ 2026-06-17 15:09 Ketan
2026-06-17 15:52 ` Zi Yan
2026-06-18 9:04 ` David Hildenbrand (Arm)
0 siblings, 2 replies; 10+ messages in thread
From: Ketan @ 2026-06-17 15:09 UTC (permalink / raw)
To: Andrew Morton, Vlastimil Babka, Suren Baghdasaryan, Michal Hocko,
Brendan Jackman, Johannes Weiner, Zi Yan, Luiz Capitulino,
David Hildenbrand
Cc: kernel, linux-mm, linux-kernel, Ketan Kishore
The page_ext iteration API do not validate if the PFN still
belongs to a valid section while advancing the iterator.
When dynamically adding memory in hotplug path, it can lead
to a NULL pointer dereference during page_ext_lookup at the
boundary of the last valid section.
[ 14.555124][ T846] Call trace:
[ 14.555125][ T846] lookup_page_ext+0x6c/0x108 (P)
[ 14.555127][ T846] page_ext_lookup+0x30/0x3c
[ 14.555129][ T846] __reset_page_owner+0x11c/0x260
[ 14.571201][ T846] __free_pages_ok+0x5e8/0x8e0
[ 14.571204][ T846] __free_pages_core+0x78/0xf0
[ 14.571206][ T846] generic_online_page+0x14/0x24
[ 14.597782][ T846] online_pages+0x178/0x30c
[ 14.597784][ T846] memory_block_change_state+0x284/0x32c
[ 14.597787][ T846] memory_subsys_online+0x4c/0x64
[ 14.597789][ T846] device_online+0x88/0xb0
[ 14.597791][ T846] online_memory_block+0x30/0x40
[ 14.597793][ T846] walk_memory_blocks+0xac/0xe8
[ 14.597794][ T846] add_memory_resource+0x280/0x298
[ 14.656161][ T846] add_memory+0x60/0x98
Add a valid-section check before looking up the next page_ext
so the iterator stops cleanly at section boundaries.
Fixes: 9039b9096ea2 ("mm: page_owner: use new iteration API")
Signed-off-by: Ketan Kishore <ketan.kishore@oss.qualcomm.com>
---
mm/page_ext.c | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/mm/page_ext.c b/mm/page_ext.c
index e2e92bd27ebd..74067ea740fe 100644
--- a/mm/page_ext.c
+++ b/mm/page_ext.c
@@ -253,6 +253,18 @@ static struct page_ext *lookup_page_ext(const struct page *page)
{
unsigned long pfn = page_to_pfn(page);
struct mem_section *section = __pfn_to_section(pfn);
+
+ /*
+ * section can be NULL when the page_ext iterator's for-loop increment
+ * computes a PFN one step beyond the last registered section. This
+ * occurs because pfn_to_page() uses __nr_to_section() which succeeds
+ * for unregistered sections that share a root array with registered
+ * sections,while __pfn_to_section() returns NULL for them.
+ *
+ */
+ if (!section)
+ return NULL;
+
struct page_ext *page_ext = READ_ONCE(section->page_ext);
WARN_ON_ONCE(!rcu_read_lock_held());
---
base-commit: c425609d6ac4012c8bbf01ec2e10e801b1923a7b
change-id: 20260616-page_ext-31ef555456fc
Best regards,
--
Ketan Kishore <ketan.kishore@oss.qualcomm.com>
^ permalink raw reply related [flat|nested] 10+ messages in thread* Re: [PATCH] mm: page_ext: validate section in for_each_page_ext iterator 2026-06-17 15:09 [PATCH] mm: page_ext: validate section in for_each_page_ext iterator Ketan @ 2026-06-17 15:52 ` Zi Yan 2026-06-18 9:04 ` David Hildenbrand (Arm) 1 sibling, 0 replies; 10+ messages in thread From: Zi Yan @ 2026-06-17 15:52 UTC (permalink / raw) To: Ketan Cc: Andrew Morton, Vlastimil Babka, Suren Baghdasaryan, Michal Hocko, Brendan Jackman, Johannes Weiner, Luiz Capitulino, David Hildenbrand, kernel, linux-mm, linux-kernel On 17 Jun 2026, at 11:09, Ketan wrote: > The page_ext iteration API do not validate if the PFN still > belongs to a valid section while advancing the iterator. > > When dynamically adding memory in hotplug path, it can lead > to a NULL pointer dereference during page_ext_lookup at the > boundary of the last valid section. > > [ 14.555124][ T846] Call trace: > [ 14.555125][ T846] lookup_page_ext+0x6c/0x108 (P) > [ 14.555127][ T846] page_ext_lookup+0x30/0x3c > [ 14.555129][ T846] __reset_page_owner+0x11c/0x260 > [ 14.571201][ T846] __free_pages_ok+0x5e8/0x8e0 > [ 14.571204][ T846] __free_pages_core+0x78/0xf0 > [ 14.571206][ T846] generic_online_page+0x14/0x24 > [ 14.597782][ T846] online_pages+0x178/0x30c > [ 14.597784][ T846] memory_block_change_state+0x284/0x32c > [ 14.597787][ T846] memory_subsys_online+0x4c/0x64 > [ 14.597789][ T846] device_online+0x88/0xb0 > [ 14.597791][ T846] online_memory_block+0x30/0x40 > [ 14.597793][ T846] walk_memory_blocks+0xac/0xe8 > [ 14.597794][ T846] add_memory_resource+0x280/0x298 > [ 14.656161][ T846] add_memory+0x60/0x98 > > Add a valid-section check before looking up the next page_ext > so the iterator stops cleanly at section boundaries. > > Fixes: 9039b9096ea2 ("mm: page_owner: use new iteration API") > Signed-off-by: Ketan Kishore <ketan.kishore@oss.qualcomm.com> > --- > mm/page_ext.c | 12 ++++++++++++ > 1 file changed, 12 insertions(+) > > diff --git a/mm/page_ext.c b/mm/page_ext.c > index e2e92bd27ebd..74067ea740fe 100644 > --- a/mm/page_ext.c > +++ b/mm/page_ext.c > @@ -253,6 +253,18 @@ static struct page_ext *lookup_page_ext(const struct page *page) > { > unsigned long pfn = page_to_pfn(page); > struct mem_section *section = __pfn_to_section(pfn); > + > + /* > + * section can be NULL when the page_ext iterator's for-loop increment > + * computes a PFN one step beyond the last registered section. This > + * occurs because pfn_to_page() uses __nr_to_section() which succeeds > + * for unregistered sections that share a root array with registered > + * sections,while __pfn_to_section() returns NULL for them. > + * > + */ > + if (!section) > + return NULL; > + > struct page_ext *page_ext = READ_ONCE(section->page_ext); Please move up the definition of page_ext to make code clean, although I know it is supported to have a definition after a statement. > > WARN_ON_ONCE(!rcu_read_lock_held()); > > --- > base-commit: c425609d6ac4012c8bbf01ec2e10e801b1923a7b > change-id: 20260616-page_ext-31ef555456fc > > Best regards, > -- > Ketan Kishore <ketan.kishore@oss.qualcomm.com> Best Regards, Yan, Zi ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH] mm: page_ext: validate section in for_each_page_ext iterator 2026-06-17 15:09 [PATCH] mm: page_ext: validate section in for_each_page_ext iterator Ketan 2026-06-17 15:52 ` Zi Yan @ 2026-06-18 9:04 ` David Hildenbrand (Arm) 2026-06-19 2:38 ` Luiz Capitulino 1 sibling, 1 reply; 10+ messages in thread From: David Hildenbrand (Arm) @ 2026-06-18 9:04 UTC (permalink / raw) To: Ketan, Andrew Morton, Vlastimil Babka, Suren Baghdasaryan, Michal Hocko, Brendan Jackman, Johannes Weiner, Zi Yan, Luiz Capitulino Cc: kernel, linux-mm, linux-kernel On 6/17/26 17:09, Ketan wrote: > The page_ext iteration API do not validate if the PFN still > belongs to a valid section while advancing the iterator. > > When dynamically adding memory in hotplug path, it can lead > to a NULL pointer dereference during page_ext_lookup at the > boundary of the last valid section. > > [ 14.555124][ T846] Call trace: > [ 14.555125][ T846] lookup_page_ext+0x6c/0x108 (P) > [ 14.555127][ T846] page_ext_lookup+0x30/0x3c > [ 14.555129][ T846] +0x11c/0x260 > [ 14.571201][ T846] __free_pages_ok+0x5e8/0x8e0 > [ 14.571204][ T846] __free_pages_core+0x78/0xf0 > [ 14.571206][ T846] generic_online_page+0x14/0x24 > [ 14.597782][ T846] online_pages+0x178/0x30c > [ 14.597784][ T846] memory_block_change_state+0x284/0x32c > [ 14.597787][ T846] memory_subsys_online+0x4c/0x64 > [ 14.597789][ T846] device_online+0x88/0xb0 > [ 14.597791][ T846] online_memory_block+0x30/0x40 > [ 14.597793][ T846] walk_memory_blocks+0xac/0xe8 > [ 14.597794][ T846] add_memory_resource+0x280/0x298 > [ 14.656161][ T846] add_memory+0x60/0x98 So, we do allocate the page_ext in memory_notify(MEM_GOING_ONLINE) -> ... page_ext_callback() -> online_page_ext(). Which makes sure that all page_ext is actually allocated (for the full section). So when we later end up in generic_online_page(), the page_ext should be there and the memory section should be valid. How can online_pages(), which onlines memory part of present memory sections, online (free) memory that suddenly does not belong to a present memory section? Something doesn't make sense here. > > Add a valid-section check before looking up the next page_ext > so the iterator stops cleanly at section boundaries. > > Fixes: 9039b9096ea2 ("mm: page_owner: use new iteration API") If the problem is real, we'd want to CC stable. > Signed-off-by: Ketan Kishore <ketan.kishore@oss.qualcomm.com> > --- > mm/page_ext.c | 12 ++++++++++++ > 1 file changed, 12 insertions(+) > > diff --git a/mm/page_ext.c b/mm/page_ext.c > index e2e92bd27ebd..74067ea740fe 100644 > --- a/mm/page_ext.c > +++ b/mm/page_ext.c > @@ -253,6 +253,18 @@ static struct page_ext *lookup_page_ext(const struct page *page) > { > unsigned long pfn = page_to_pfn(page); > struct mem_section *section = __pfn_to_section(pfn); > + > + /* > + * section can be NULL when the page_ext iterator's for-loop increment > + * computes a PFN one step beyond the last registered section. This > + * occurs because pfn_to_page() uses __nr_to_section() which succeeds > + * for unregistered sections that share a root array with registered > + * sections,while __pfn_to_section() returns NULL for them. > + * > + */ > + if (!section) > + return NULL; > + > struct page_ext *page_ext = READ_ONCE(section->page_ext); In addition to what Zi says, which will end up as: if (!section) return NULL; page_ext = READ_ONCE(section->page_ext); drop that comment, not required. And likely best to just add a unlikely(), because this doesn't usually happen. -- Cheers, David ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH] mm: page_ext: validate section in for_each_page_ext iterator 2026-06-18 9:04 ` David Hildenbrand (Arm) @ 2026-06-19 2:38 ` Luiz Capitulino 2026-06-19 8:00 ` David Hildenbrand (Arm) 2026-06-19 20:31 ` Ketan Kishore 0 siblings, 2 replies; 10+ messages in thread From: Luiz Capitulino @ 2026-06-19 2:38 UTC (permalink / raw) To: David Hildenbrand (Arm), Ketan, Andrew Morton, Vlastimil Babka, Suren Baghdasaryan, Michal Hocko, Brendan Jackman, Johannes Weiner, Zi Yan Cc: kernel, linux-mm, linux-kernel On 2026-06-18 05:04, David Hildenbrand (Arm) wrote: > On 6/17/26 17:09, Ketan wrote: >> The page_ext iteration API do not validate if the PFN still >> belongs to a valid section while advancing the iterator. >> >> When dynamically adding memory in hotplug path, it can lead >> to a NULL pointer dereference during page_ext_lookup at the >> boundary of the last valid section. >> >> [ 14.555124][ T846] Call trace: >> [ 14.555125][ T846] lookup_page_ext+0x6c/0x108 (P) >> [ 14.555127][ T846] page_ext_lookup+0x30/0x3c >> [ 14.555129][ T846] +0x11c/0x260 >> [ 14.571201][ T846] __free_pages_ok+0x5e8/0x8e0 >> [ 14.571204][ T846] __free_pages_core+0x78/0xf0 >> [ 14.571206][ T846] generic_online_page+0x14/0x24 >> [ 14.597782][ T846] online_pages+0x178/0x30c >> [ 14.597784][ T846] memory_block_change_state+0x284/0x32c >> [ 14.597787][ T846] memory_subsys_online+0x4c/0x64 >> [ 14.597789][ T846] device_online+0x88/0xb0 >> [ 14.597791][ T846] online_memory_block+0x30/0x40 >> [ 14.597793][ T846] walk_memory_blocks+0xac/0xe8 >> [ 14.597794][ T846] add_memory_resource+0x280/0x298 >> [ 14.656161][ T846] add_memory+0x60/0x98 > > So, we do allocate the page_ext in memory_notify(MEM_GOING_ONLINE) -> ... > page_ext_callback() -> online_page_ext(). > > Which makes sure that all page_ext is actually allocated (for the full section). > > So when we later end up in generic_online_page(), the page_ext should be there > and the memory section should be valid. > > > How can online_pages(), which onlines memory part of present memory sections, > online (free) memory that suddenly does not belong to a present memory section? > > Something doesn't make sense here. Ketan, would you please share the full OOPs message and maybe tell us how you reproduce this issue? > >> >> Add a valid-section check before looking up the next page_ext >> so the iterator stops cleanly at section boundaries. >> >> Fixes: 9039b9096ea2 ("mm: page_owner: use new iteration API") > > If the problem is real, we'd want to CC stable. > >> Signed-off-by: Ketan Kishore <ketan.kishore@oss.qualcomm.com> >> --- >> mm/page_ext.c | 12 ++++++++++++ >> 1 file changed, 12 insertions(+) >> >> diff --git a/mm/page_ext.c b/mm/page_ext.c >> index e2e92bd27ebd..74067ea740fe 100644 >> --- a/mm/page_ext.c >> +++ b/mm/page_ext.c >> @@ -253,6 +253,18 @@ static struct page_ext *lookup_page_ext(const struct page *page) >> { >> unsigned long pfn = page_to_pfn(page); >> struct mem_section *section = __pfn_to_section(pfn); >> + >> + /* >> + * section can be NULL when the page_ext iterator's for-loop increment >> + * computes a PFN one step beyond the last registered section. This >> + * occurs because pfn_to_page() uses __nr_to_section() which succeeds >> + * for unregistered sections that share a root array with registered >> + * sections,while __pfn_to_section() returns NULL for them. >> + * >> + */ >> + if (!section) >> + return NULL; >> + >> struct page_ext *page_ext = READ_ONCE(section->page_ext); > > In addition to what Zi says, which will end up as: > > if (!section) > return NULL; > page_ext = READ_ONCE(section->page_ext); > > > drop that comment, not required. And likely best to just add a unlikely(), > because this doesn't usually happen. > ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH] mm: page_ext: validate section in for_each_page_ext iterator 2026-06-19 2:38 ` Luiz Capitulino @ 2026-06-19 8:00 ` David Hildenbrand (Arm) 2026-06-19 13:38 ` Matthew Wilcox 2026-06-19 20:36 ` Ketan Kishore 2026-06-19 20:31 ` Ketan Kishore 1 sibling, 2 replies; 10+ messages in thread From: David Hildenbrand (Arm) @ 2026-06-19 8:00 UTC (permalink / raw) To: Luiz Capitulino, Ketan, Andrew Morton, Vlastimil Babka, Suren Baghdasaryan, Michal Hocko, Brendan Jackman, Johannes Weiner, Zi Yan Cc: kernel, linux-mm, linux-kernel On 6/19/26 04:38, Luiz Capitulino wrote: > On 2026-06-18 05:04, David Hildenbrand (Arm) wrote: >> On 6/17/26 17:09, Ketan wrote: >>> The page_ext iteration API do not validate if the PFN still >>> belongs to a valid section while advancing the iterator. >>> >>> When dynamically adding memory in hotplug path, it can lead >>> to a NULL pointer dereference during page_ext_lookup at the >>> boundary of the last valid section. >>> >>> [ 14.555124][ T846] Call trace: >>> [ 14.555125][ T846] lookup_page_ext+0x6c/0x108 (P) >>> [ 14.555127][ T846] page_ext_lookup+0x30/0x3c >>> [ 14.555129][ T846] +0x11c/0x260 >>> [ 14.571201][ T846] __free_pages_ok+0x5e8/0x8e0 >>> [ 14.571204][ T846] __free_pages_core+0x78/0xf0 >>> [ 14.571206][ T846] generic_online_page+0x14/0x24 >>> [ 14.597782][ T846] online_pages+0x178/0x30c >>> [ 14.597784][ T846] memory_block_change_state+0x284/0x32c >>> [ 14.597787][ T846] memory_subsys_online+0x4c/0x64 >>> [ 14.597789][ T846] device_online+0x88/0xb0 >>> [ 14.597791][ T846] online_memory_block+0x30/0x40 >>> [ 14.597793][ T846] walk_memory_blocks+0xac/0xe8 >>> [ 14.597794][ T846] add_memory_resource+0x280/0x298 >>> [ 14.656161][ T846] add_memory+0x60/0x98 >> >> So, we do allocate the page_ext in memory_notify(MEM_GOING_ONLINE) -> ... >> page_ext_callback() -> online_page_ext(). >> >> Which makes sure that all page_ext is actually allocated (for the full section). >> >> So when we later end up in generic_online_page(), the page_ext should be there >> and the memory section should be valid. >> >> >> How can online_pages(), which onlines memory part of present memory sections, >> online (free) memory that suddenly does not belong to a present memory section? >> >> Something doesn't make sense here. > > Ketan, would you please share the full OOPs message and maybe tell us > how you reproduce this issue? Ah, I think I figured it out. for_each_page_ext() does a "__page_ext = page_ext_iter_next(&__iter)" and the end. That is the real problem and should be spelled out in the patch description. Likely we should just handle that in the iterator, looking up page-ext for something that doesn't exist is weird. From ce41607a4e4c1d872b6d3050da2e24d41a30d822 Mon Sep 17 00:00:00 2001 From: "David Hildenbrand (Arm)" <david@kernel.org> Date: Fri, 19 Jun 2026 09:58:46 +0200 Subject: [PATCH] tmp Signed-off-by: David Hildenbrand (Arm) <david@kernel.org> --- include/linux/page_ext.h | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/include/linux/page_ext.h b/include/linux/page_ext.h index 61e876e255e8..468c074582e1 100644 --- a/include/linux/page_ext.h +++ b/include/linux/page_ext.h @@ -112,6 +112,7 @@ static inline struct page_ext *page_ext_next(struct page_ext *curr) struct page_ext_iter { unsigned long index; + unsigned long pgcount; unsigned long start_pfn; struct page_ext *page_ext; }; @@ -120,15 +121,19 @@ struct page_ext_iter { * page_ext_iter_begin() - Prepare for iterating through page extensions. * @iter: page extension iterator. * @pfn: PFN of the page we're interested in. + * @pgcount: number of page_ext entries to iterate * * Must be called with RCU read lock taken. * * Return: NULL if no page_ext exists for this page. */ static inline struct page_ext *page_ext_iter_begin(struct page_ext_iter *iter, - unsigned long pfn) + unsigned long pfn, unsigned long pgcount) { + VM_WARN_ON_ONCE(!pgcount); + iter->index = 0; + iter->pgcount = pgcount; iter->start_pfn = pfn; iter->page_ext = page_ext_lookup(pfn); @@ -149,8 +154,9 @@ static inline struct page_ext *page_ext_iter_next(struct page_ext_iter *iter) if (WARN_ON_ONCE(!iter->page_ext)) return NULL; + if (++iter->index >= iter->pgcount) + return NULL; - iter->index++; pfn = iter->start_pfn + iter->index; if (page_ext_iter_next_fast_possible(pfn)) @@ -183,9 +189,9 @@ static inline struct page_ext *page_ext_iter_get(const struct page_ext_iter *ite * IMPORTANT: must be called with RCU read lock taken. */ #define for_each_page_ext(__page, __pgcount, __page_ext, __iter) \ - for (__page_ext = page_ext_iter_begin(&__iter, page_to_pfn(__page));\ - __page_ext && __iter.index < __pgcount; \ - __page_ext = page_ext_iter_next(&__iter)) + for (__page_ext = page_ext_iter_begin(&__iter, page_to_pfn(__page), __pgcount);\ + __page_ext; \ + __page_ext = page_ext_iter_next(&__iter)) #else /* !CONFIG_PAGE_EXTENSION */ struct page_ext; -- 2.43.0 -- Cheers, David ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH] mm: page_ext: validate section in for_each_page_ext iterator 2026-06-19 8:00 ` David Hildenbrand (Arm) @ 2026-06-19 13:38 ` Matthew Wilcox 2026-06-19 13:45 ` David Hildenbrand (Arm) 2026-06-19 20:44 ` Ketan Kishore 2026-06-19 20:36 ` Ketan Kishore 1 sibling, 2 replies; 10+ messages in thread From: Matthew Wilcox @ 2026-06-19 13:38 UTC (permalink / raw) To: David Hildenbrand (Arm) Cc: Luiz Capitulino, Ketan, Andrew Morton, Vlastimil Babka, Suren Baghdasaryan, Michal Hocko, Brendan Jackman, Johannes Weiner, Zi Yan, kernel, linux-mm, linux-kernel On Fri, Jun 19, 2026 at 10:00:59AM +0200, David Hildenbrand (Arm) wrote: > Likely we should just handle that in the iterator, looking up page-ext for something > that doesn't exist is weird. Yes, but I prefer to pass 'max' to the _next step explicitly, rather than put it in the iterator struct. Untested patch attached. The other way to do it is s/index/remaining/ and count it down rather than upwards. But that assumes none of the iterator users look at iter->index, and I don't have the patience to check that. I find it weird that somebody botheresd to add kernel-doc for page_ext_iter_begin/next. They're internal implementation and really shouldn't have public documentation. diff --git a/include/linux/page_ext.h b/include/linux/page_ext.h index 61e876e255e8..4f7d7a8709de 100644 --- a/include/linux/page_ext.h +++ b/include/linux/page_ext.h @@ -120,14 +120,18 @@ struct page_ext_iter { * page_ext_iter_begin() - Prepare for iterating through page extensions. * @iter: page extension iterator. * @pfn: PFN of the page we're interested in. + * @count: maximum number of page extensions to return. * * Must be called with RCU read lock taken. * * Return: NULL if no page_ext exists for this page. */ static inline struct page_ext *page_ext_iter_begin(struct page_ext_iter *iter, - unsigned long pfn) + unsigned long pfn, unsigned long count) { + if (count == 0) + return NULL; + iter->index = 0; iter->start_pfn = pfn; iter->page_ext = page_ext_lookup(pfn); @@ -138,19 +142,22 @@ static inline struct page_ext *page_ext_iter_begin(struct page_ext_iter *iter, /** * page_ext_iter_next() - Get next page extension * @iter: page extension iterator. + * @count: maximum number of page extensions to return. * * Must be called with RCU read lock taken. * * Return: NULL if no next page_ext exists. */ -static inline struct page_ext *page_ext_iter_next(struct page_ext_iter *iter) +static inline struct page_ext *page_ext_iter_next(struct page_ext_iter *iter, + unsigned long count) { unsigned long pfn; if (WARN_ON_ONCE(!iter->page_ext)) return NULL; - iter->index++; + if (iter->index++ >= count) + return NULL; pfn = iter->start_pfn + iter->index; if (page_ext_iter_next_fast_possible(pfn)) @@ -183,9 +190,9 @@ static inline struct page_ext *page_ext_iter_get(const struct page_ext_iter *ite * IMPORTANT: must be called with RCU read lock taken. */ #define for_each_page_ext(__page, __pgcount, __page_ext, __iter) \ - for (__page_ext = page_ext_iter_begin(&__iter, page_to_pfn(__page));\ - __page_ext && __iter.index < __pgcount; \ - __page_ext = page_ext_iter_next(&__iter)) + for (__page_ext = page_ext_iter_begin(&__iter, page_to_pfn(__page), __pgcount); \ + __page_ext; \ + __page_ext = page_ext_iter_next(&__iter, __pgcount)) #else /* !CONFIG_PAGE_EXTENSION */ struct page_ext; ^ permalink raw reply related [flat|nested] 10+ messages in thread
* Re: [PATCH] mm: page_ext: validate section in for_each_page_ext iterator 2026-06-19 13:38 ` Matthew Wilcox @ 2026-06-19 13:45 ` David Hildenbrand (Arm) 2026-06-19 20:44 ` Ketan Kishore 1 sibling, 0 replies; 10+ messages in thread From: David Hildenbrand (Arm) @ 2026-06-19 13:45 UTC (permalink / raw) To: Matthew Wilcox Cc: Luiz Capitulino, Ketan, Andrew Morton, Vlastimil Babka, Suren Baghdasaryan, Michal Hocko, Brendan Jackman, Johannes Weiner, Zi Yan, kernel, linux-mm, linux-kernel On 6/19/26 15:38, Matthew Wilcox wrote: > On Fri, Jun 19, 2026 at 10:00:59AM +0200, David Hildenbrand (Arm) wrote: >> Likely we should just handle that in the iterator, looking up page-ext for something >> that doesn't exist is weird. > > Yes, but I prefer to pass 'max' to the _next step explicitly, rather > than put it in the iterator struct. Untested patch attached. Yeah, no strong opinion, same fix idea. > > The other way to do it is s/index/remaining/ and count it down rather > than upwards. But that assumes none of the iterator users look at > iter->index, and I don't have the patience to check that. > > I find it weird that somebody botheresd to add kernel-doc for > page_ext_iter_begin/next. They're internal implementation and really > shouldn't have public documentation. Yeah, Luiz was just very thorough :) -- Cheers, David ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH] mm: page_ext: validate section in for_each_page_ext iterator 2026-06-19 13:38 ` Matthew Wilcox 2026-06-19 13:45 ` David Hildenbrand (Arm) @ 2026-06-19 20:44 ` Ketan Kishore 1 sibling, 0 replies; 10+ messages in thread From: Ketan Kishore @ 2026-06-19 20:44 UTC (permalink / raw) To: Matthew Wilcox, David Hildenbrand (Arm) Cc: Luiz Capitulino, Andrew Morton, Vlastimil Babka, Suren Baghdasaryan, Michal Hocko, Brendan Jackman, Johannes Weiner, Zi Yan, kernel, linux-mm, linux-kernel On 6/19/2026 7:08 PM, Matthew Wilcox wrote: > On Fri, Jun 19, 2026 at 10:00:59AM +0200, David Hildenbrand (Arm) wrote: >> Likely we should just handle that in the iterator, looking up page-ext for something >> that doesn't exist is weird. > > Yes, but I prefer to pass 'max' to the _next step explicitly, rather > than put it in the iterator struct. Untested patch attached. > > The other way to do it is s/index/remaining/ and count it down rather > than upwards. But that assumes none of the iterator users look at > iter->index, and I don't have the patience to check that. > > I find it weird that somebody botheresd to add kernel-doc for > page_ext_iter_begin/next. They're internal implementation and really > shouldn't have public documentation. > > diff --git a/include/linux/page_ext.h b/include/linux/page_ext.h > index 61e876e255e8..4f7d7a8709de 100644 > --- a/include/linux/page_ext.h > +++ b/include/linux/page_ext.h > @@ -120,14 +120,18 @@ struct page_ext_iter { > * page_ext_iter_begin() - Prepare for iterating through page extensions. > * @iter: page extension iterator. > * @pfn: PFN of the page we're interested in. > + * @count: maximum number of page extensions to return. > * > * Must be called with RCU read lock taken. > * > * Return: NULL if no page_ext exists for this page. > */ > static inline struct page_ext *page_ext_iter_begin(struct page_ext_iter *iter, > - unsigned long pfn) > + unsigned long pfn, unsigned long count) > { > + if (count == 0) > + return NULL; > + > iter->index = 0; > iter->start_pfn = pfn; > iter->page_ext = page_ext_lookup(pfn); > @@ -138,19 +142,22 @@ static inline struct page_ext *page_ext_iter_begin(struct page_ext_iter *iter, > /** > * page_ext_iter_next() - Get next page extension > * @iter: page extension iterator. > + * @count: maximum number of page extensions to return. > * > * Must be called with RCU read lock taken. > * > * Return: NULL if no next page_ext exists. > */ > -static inline struct page_ext *page_ext_iter_next(struct page_ext_iter *iter) > +static inline struct page_ext *page_ext_iter_next(struct page_ext_iter *iter, > + unsigned long count) > { > unsigned long pfn; > > if (WARN_ON_ONCE(!iter->page_ext)) > return NULL; > > - iter->index++; > + if (iter->index++ >= count) > + return NULL; > pfn = iter->start_pfn + iter->index; > > if (page_ext_iter_next_fast_possible(pfn)) > @@ -183,9 +190,9 @@ static inline struct page_ext *page_ext_iter_get(const struct page_ext_iter *ite > * IMPORTANT: must be called with RCU read lock taken. > */ > #define for_each_page_ext(__page, __pgcount, __page_ext, __iter) \ > - for (__page_ext = page_ext_iter_begin(&__iter, page_to_pfn(__page));\ > - __page_ext && __iter.index < __pgcount; \ > - __page_ext = page_ext_iter_next(&__iter)) > + for (__page_ext = page_ext_iter_begin(&__iter, page_to_pfn(__page), __pgcount); \ > + __page_ext; \ > + __page_ext = page_ext_iter_next(&__iter, __pgcount)) > > #else /* !CONFIG_PAGE_EXTENSION */ > struct page_ext; Thanks Matthew for the suggestions and the proposed alternate fix. I have tested both your and David's patch, the reported issue is not observed anymore with both the suggested patches. Since, in the reply from David on your suggested fix he agrees with the changes as they are fixing the issue in a similar way. I'll go ahead and post the v2 with your suggestions. Thanks & Regards, Ketan ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH] mm: page_ext: validate section in for_each_page_ext iterator 2026-06-19 8:00 ` David Hildenbrand (Arm) 2026-06-19 13:38 ` Matthew Wilcox @ 2026-06-19 20:36 ` Ketan Kishore 1 sibling, 0 replies; 10+ messages in thread From: Ketan Kishore @ 2026-06-19 20:36 UTC (permalink / raw) To: David Hildenbrand (Arm), Luiz Capitulino, Andrew Morton, Vlastimil Babka, Suren Baghdasaryan, Michal Hocko, Brendan Jackman, Johannes Weiner, Zi Yan Cc: kernel, linux-mm, linux-kernel On 6/19/2026 1:30 PM, David Hildenbrand (Arm) wrote: > On 6/19/26 04:38, Luiz Capitulino wrote: >> On 2026-06-18 05:04, David Hildenbrand (Arm) wrote: >>> On 6/17/26 17:09, Ketan wrote: >>>> The page_ext iteration API do not validate if the PFN still >>>> belongs to a valid section while advancing the iterator. >>>> >>>> When dynamically adding memory in hotplug path, it can lead >>>> to a NULL pointer dereference during page_ext_lookup at the >>>> boundary of the last valid section. >>>> >>>> [ 14.555124][ T846] Call trace: >>>> [ 14.555125][ T846] lookup_page_ext+0x6c/0x108 (P) >>>> [ 14.555127][ T846] page_ext_lookup+0x30/0x3c >>>> [ 14.555129][ T846] +0x11c/0x260 >>>> [ 14.571201][ T846] __free_pages_ok+0x5e8/0x8e0 >>>> [ 14.571204][ T846] __free_pages_core+0x78/0xf0 >>>> [ 14.571206][ T846] generic_online_page+0x14/0x24 >>>> [ 14.597782][ T846] online_pages+0x178/0x30c >>>> [ 14.597784][ T846] memory_block_change_state+0x284/0x32c >>>> [ 14.597787][ T846] memory_subsys_online+0x4c/0x64 >>>> [ 14.597789][ T846] device_online+0x88/0xb0 >>>> [ 14.597791][ T846] online_memory_block+0x30/0x40 >>>> [ 14.597793][ T846] walk_memory_blocks+0xac/0xe8 >>>> [ 14.597794][ T846] add_memory_resource+0x280/0x298 >>>> [ 14.656161][ T846] add_memory+0x60/0x98 >>> >>> So, we do allocate the page_ext in memory_notify(MEM_GOING_ONLINE) -> ... >>> page_ext_callback() -> online_page_ext(). >>> >>> Which makes sure that all page_ext is actually allocated (for the full section). >>> >>> So when we later end up in generic_online_page(), the page_ext should be there >>> and the memory section should be valid. >>> >>> >>> How can online_pages(), which onlines memory part of present memory sections, >>> online (free) memory that suddenly does not belong to a present memory section? >>> >>> Something doesn't make sense here. >> >> Ketan, would you please share the full OOPs message and maybe tell us >> how you reproduce this issue? > > Ah, I think I figured it out. > > for_each_page_ext() does a "__page_ext = page_ext_iter_next(&__iter)" and the end. > > That is the real problem and should be spelled out in the patch description. > > Likely we should just handle that in the iterator, looking up page-ext for something > that doesn't exist is weird. > > > From ce41607a4e4c1d872b6d3050da2e24d41a30d822 Mon Sep 17 00:00:00 2001 > From: "David Hildenbrand (Arm)" <david@kernel.org> > Date: Fri, 19 Jun 2026 09:58:46 +0200 > Subject: [PATCH] tmp > > Signed-off-by: David Hildenbrand (Arm) <david@kernel.org> > --- > include/linux/page_ext.h | 16 +++++++++++----- > 1 file changed, 11 insertions(+), 5 deletions(-) > > diff --git a/include/linux/page_ext.h b/include/linux/page_ext.h > index 61e876e255e8..468c074582e1 100644 > --- a/include/linux/page_ext.h > +++ b/include/linux/page_ext.h > @@ -112,6 +112,7 @@ static inline struct page_ext *page_ext_next(struct page_ext *curr) > > struct page_ext_iter { > unsigned long index; > + unsigned long pgcount; > unsigned long start_pfn; > struct page_ext *page_ext; > }; > @@ -120,15 +121,19 @@ struct page_ext_iter { > * page_ext_iter_begin() - Prepare for iterating through page extensions. > * @iter: page extension iterator. > * @pfn: PFN of the page we're interested in. > + * @pgcount: number of page_ext entries to iterate > * > * Must be called with RCU read lock taken. > * > * Return: NULL if no page_ext exists for this page. > */ > static inline struct page_ext *page_ext_iter_begin(struct page_ext_iter *iter, > - unsigned long pfn) > + unsigned long pfn, unsigned long pgcount) > { > + VM_WARN_ON_ONCE(!pgcount); > + > iter->index = 0; > + iter->pgcount = pgcount; > iter->start_pfn = pfn; > iter->page_ext = page_ext_lookup(pfn); > > @@ -149,8 +154,9 @@ static inline struct page_ext *page_ext_iter_next(struct page_ext_iter *iter) > > if (WARN_ON_ONCE(!iter->page_ext)) > return NULL; > + if (++iter->index >= iter->pgcount) > + return NULL; > > - iter->index++; > pfn = iter->start_pfn + iter->index; > > if (page_ext_iter_next_fast_possible(pfn)) > @@ -183,9 +189,9 @@ static inline struct page_ext *page_ext_iter_get(const struct page_ext_iter *ite > * IMPORTANT: must be called with RCU read lock taken. > */ > #define for_each_page_ext(__page, __pgcount, __page_ext, __iter) \ > - for (__page_ext = page_ext_iter_begin(&__iter, page_to_pfn(__page));\ > - __page_ext && __iter.index < __pgcount; \ > - __page_ext = page_ext_iter_next(&__iter)) > + for (__page_ext = page_ext_iter_begin(&__iter, page_to_pfn(__page), __pgcount);\ > + __page_ext; \ > + __page_ext = page_ext_iter_next(&__iter)) > > #else /* !CONFIG_PAGE_EXTENSION */ > struct page_ext; Sure David, your above understanding is correct. Thankyou for the suggested alternative fix, fixing the iterator itself seems to be a better approach than having the section check in lookup_page_ext. Thanks & Regards, Ketan ^ permalink raw reply [flat|nested] 10+ messages in thread
* Re: [PATCH] mm: page_ext: validate section in for_each_page_ext iterator 2026-06-19 2:38 ` Luiz Capitulino 2026-06-19 8:00 ` David Hildenbrand (Arm) @ 2026-06-19 20:31 ` Ketan Kishore 1 sibling, 0 replies; 10+ messages in thread From: Ketan Kishore @ 2026-06-19 20:31 UTC (permalink / raw) To: Luiz Capitulino, David Hildenbrand (Arm), Andrew Morton, Vlastimil Babka, Suren Baghdasaryan, Michal Hocko, Brendan Jackman, Johannes Weiner, Zi Yan Cc: kernel, linux-mm, linux-kernel On 6/19/2026 8:08 AM, Luiz Capitulino wrote: > On 2026-06-18 05:04, David Hildenbrand (Arm) wrote: >> On 6/17/26 17:09, Ketan wrote: >>> The page_ext iteration API do not validate if the PFN still >>> belongs to a valid section while advancing the iterator. >>> >>> When dynamically adding memory in hotplug path, it can lead >>> to a NULL pointer dereference during page_ext_lookup at the >>> boundary of the last valid section. >>> >>> [ 14.555124][ T846] Call trace: >>> [ 14.555125][ T846] lookup_page_ext+0x6c/0x108 (P) >>> [ 14.555127][ T846] page_ext_lookup+0x30/0x3c >>> [ 14.555129][ T846] +0x11c/0x260 >>> [ 14.571201][ T846] __free_pages_ok+0x5e8/0x8e0 >>> [ 14.571204][ T846] __free_pages_core+0x78/0xf0 >>> [ 14.571206][ T846] generic_online_page+0x14/0x24 >>> [ 14.597782][ T846] online_pages+0x178/0x30c >>> [ 14.597784][ T846] memory_block_change_state+0x284/0x32c >>> [ 14.597787][ T846] memory_subsys_online+0x4c/0x64 >>> [ 14.597789][ T846] device_online+0x88/0xb0 >>> [ 14.597791][ T846] online_memory_block+0x30/0x40 >>> [ 14.597793][ T846] walk_memory_blocks+0xac/0xe8 >>> [ 14.597794][ T846] add_memory_resource+0x280/0x298 >>> [ 14.656161][ T846] add_memory+0x60/0x98 >> >> So, we do allocate the page_ext in memory_notify(MEM_GOING_ONLINE) -> ... >> page_ext_callback() -> online_page_ext(). >> >> Which makes sure that all page_ext is actually allocated (for the full >> section). >> >> So when we later end up in generic_online_page(), the page_ext should >> be there >> and the memory section should be valid. >> >> >> How can online_pages(), which onlines memory part of present memory >> sections, >> online (free) memory that suddenly does not belong to a present memory >> section? >> >> Something doesn't make sense here. > > Ketan, would you please share the full OOPs message and maybe tell us > how you reproduce this issue? > Sure Luiz, below is the full OOPs with the call stack. In one of our usecases, we try to add all the movable zone memory available to kernel after boot. At the boundary of last section being added, the iterator for_each_page_ext (which in turn calls lookup_page_ext) inside which the below fetched section is NULL for invalid pfn. "struct mem_section *section = __pfn_to_section(pfn); struct page_ext *page_ext = READ_ONCE(section->page_ext);" -> While accessing section for invalid PFN we get the below NULL pointer dereference. [ 13.840511][ T846] Unable to handle kernel NULL pointer dereference at virtual address 0000000000000010 [ 13.850253][ T846] Mem abort info: [ 13.853859][ T846] ESR = 0x0000000096000005 [ 13.858441][ T846] EC = 0x25: DABT (current EL), IL = 32 bits [ 13.864642][ T846] SET = 0, FnV = 0 [ 13.868510][ T846] EA = 0, S1PTW = 0 [ 13.872472][ T846] FSC = 0x05: level 1 translation fault [ 13.878224][ T846] Data abort info: [ 13.881914][ T846] ISV = 0, ISS = 0x00000005, ISS2 = 0x00000000 [ 13.888283][ T846] CM = 0, WnR = 0, TnD = 0, TagAccess = 0 [ 13.894211][ T846] GCS = 0, Overlay = 0, DirtyBit = 0, Xs = 0 [ 13.900398][ T846] user pgtable: 4k pages, 39-bit VAs, pgdp=000000089ff23000 [ 13.907749][ T846] [0000000000000010] pgd=0000000000000000, p4d=0000000000000000, pud=0000000000000000 [ 13.917687][ T846] Internal error: Oops: 0000000096000005 [#1] SMP [ 13.924220][ T846] Dumping ftrace buffer: [ 13.928436][ T846] (ftrace buffer empty) [ 14.452210][ T846] CPU: 7 UID: 0 PID: 846 Comm: modprobe Tainted: G OE 6.18.21-android17-4-maybe-dirty-debug #1 PREEMPT df0e65d5b71bf0ce6ff80187ad4d31404755da65 [ 14.468528][ T846] Tainted: [O]=OOT_MODULE, [E]=UNSIGNED_MODULE [ 14.484959][ T846] pstate: 43400005 (nZcv daif +PAN -UAO +TCO +DIT -SSBS BTYPE=--) [ 14.492820][ T846] pc : lookup_page_ext+0x6c/0x108 [ 14.497840][ T846] lr : page_ext_lookup+0x30/0x3c [ 14.502766][ T846] sp : ffffffc08d21b250 [ 14.506887][ T846] x29: ffffffc08d21b250 x28: ffffffe2e901f080 x27: ffffffe2e9920000 [ 14.514927][ T846] x26: 0000000000000400 x25: ffffff882701a380 x24: ffffffe2e8f28000 [ 14.522972][ T846] x23: 0000000030000000 x22: 0000000000c00000 x21: eaf5b162e68bf47c [ 14.531008][ T846] x20: 0000000000000000 x19: 0000000000c00000 x18: ffffffe2e8e4c380 [ 14.539045][ T846] x17: 00000000c006287d x16: 00000000c006287d x15: 0000000073cc3b9a [ 14.539047][ T846] x14: 000000007c6d34b7 x13: 00000000d0fa10f2 x12: ffffff882701b128 [ 14.539048][ T846] x11: ffffffe2e93630a8 x10: 0000000000000011 x9 : 0000000000000003 [ 14.539050][ T846] x8 : 0000000000000000 x7 : 0000000000000000 x6 : ffffffe2e68b78e4 [ 14.539051][ T846] x5 : 0000000000000000 [ 14.555122][ T846] x4 : 0000000000000000 x3 : 0000000000000002 [ 14.555123][ T846] x2 : 0000000000000000 x1 : ffffffe2e7f30cab x0 : fffffffeee000000 [ 14.555124][ T846] Call trace: [ 14.555125][ T846] lookup_page_ext+0x6c/0x108 (P) [ 14.555127][ T846] page_ext_lookup+0x30/0x3c [ 14.555129][ T846] __reset_page_owner+0x11c/0x260 [ 14.571201][ T846] __free_pages_ok+0x5e8/0x8e0 [ 14.571204][ T846] __free_pages_core+0x78/0xf0 [ 14.571206][ T846] generic_online_page+0x14/0x24 [ 14.597782][ T846] online_pages+0x178/0x30c [ 14.597784][ T846] memory_block_change_state+0x284/0x32c [ 14.597787][ T846] memory_subsys_online+0x4c/0x64 [ 14.597789][ T846] device_online+0x88/0xb0 [ 14.597791][ T846] online_memory_block+0x30/0x40 [ 14.597793][ T846] walk_memory_blocks+0xac/0xe8 [ 14.597794][ T846] add_memory_resource+0x280/0x298 [ 14.656161][ T846] add_memory+0x60/0x98 As David already pointed out over this thread, "for_each_page_ext() does a "__page_ext = page_ext_iter_next(&__iter)" and the end." page_ext_iter_next tries to access the page_ext for an invalid pfn at the end. Thanks & Regards, Ketan >> >>> >>> Add a valid-section check before looking up the next page_ext >>> so the iterator stops cleanly at section boundaries. >>> >>> Fixes: 9039b9096ea2 ("mm: page_owner: use new iteration API") >> >> If the problem is real, we'd want to CC stable. >> >>> Signed-off-by: Ketan Kishore <ketan.kishore@oss.qualcomm.com> >>> --- >>> mm/page_ext.c | 12 ++++++++++++ >>> 1 file changed, 12 insertions(+) >>> >>> diff --git a/mm/page_ext.c b/mm/page_ext.c >>> index e2e92bd27ebd..74067ea740fe 100644 >>> --- a/mm/page_ext.c >>> +++ b/mm/page_ext.c >>> @@ -253,6 +253,18 @@ static struct page_ext *lookup_page_ext(const >>> struct page *page) >>> { >>> unsigned long pfn = page_to_pfn(page); >>> struct mem_section *section = __pfn_to_section(pfn); >>> + >>> + /* >>> + * section can be NULL when the page_ext iterator's for-loop >>> increment >>> + * computes a PFN one step beyond the last registered section. This >>> + * occurs because pfn_to_page() uses __nr_to_section() which >>> succeeds >>> + * for unregistered sections that share a root array with >>> registered >>> + * sections,while __pfn_to_section() returns NULL for them. >>> + * >>> + */ >>> + if (!section) >>> + return NULL; >>> + >>> struct page_ext *page_ext = READ_ONCE(section->page_ext); >> >> In addition to what Zi says, which will end up as: >> >> if (!section) >> return NULL; >> page_ext = READ_ONCE(section->page_ext); >> >> >> drop that comment, not required. And likely best to just add a >> unlikely(), >> because this doesn't usually happen. >> > ^ permalink raw reply [flat|nested] 10+ messages in thread
end of thread, other threads:[~2026-06-19 20:44 UTC | newest] Thread overview: 10+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-06-17 15:09 [PATCH] mm: page_ext: validate section in for_each_page_ext iterator Ketan 2026-06-17 15:52 ` Zi Yan 2026-06-18 9:04 ` David Hildenbrand (Arm) 2026-06-19 2:38 ` Luiz Capitulino 2026-06-19 8:00 ` David Hildenbrand (Arm) 2026-06-19 13:38 ` Matthew Wilcox 2026-06-19 13:45 ` David Hildenbrand (Arm) 2026-06-19 20:44 ` Ketan Kishore 2026-06-19 20:36 ` Ketan Kishore 2026-06-19 20:31 ` Ketan Kishore
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox