mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v2] mm/filemap: stop fault-around when the walk steps back
@ 2026-10-31  4:33 Jose Fernandez (Anthropic)
  2026-11-02 17:27 ` Andrew Morton
                   ` (3 more replies)
  0 siblings, 4 replies; 12+ messages in thread
From: Jose Fernandez (Anthropic) @ 2026-10-31  4:33 UTC (permalink / raw)
  To: Matthew Wilcox (Oracle), Jan Kara, Andrew Morton, David Hildenbrand
  Cc: linux-fsdevel, linux-mm, linux-kernel, Krystian Kaniewski,
	stable, Jose Fernandez (Anthropic)

filemap_map_pages() can dereference a PTE pointer outside the page table
it locked. KASAN has reported this as a use-after-free read [1]. The
same path can also write out of bounds. When the stray entry reads as
none, the fault-around loop maps a page there with set_pte_range().

Its fault-around loop walks the page cache and moves vmf->pte by the
change in index. A lower index moves it back. xas_find() can step back
to a lower index when a multi-index entry is split during the walk.

[1] fixes xas_find(), but the loop still relies on the walk never
stepping back. Stop the fault-around when the walk steps back. ->fault()
maps the faulting page if the loop did not.

Link: https://lore.kernel.org/20260904121301.200049-1-krystianmkaniewski@gmail.com [1]
Fixes: 6b24ca4a1a8d ("mm: Use multi-index entries in the page cache")
Cc: stable@vger.kernel.org
Signed-off-by: Jose Fernandez (Anthropic) <jose.fernandez@linux.dev>
---
Changes in v2:
- Add Fixes: and Cc: stable tags, and say in the message that the loop
  can also write out of bounds (David Hildenbrand).
  https://patch.msgid.link/0a1ad7d2-ff0e-4248-8f03-2a64a9134aae@kernel.org
- Link to v1: https://patch.msgid.link/20261028-jf-mm-filemap-map-pages-lower-bound-v1-1-0bf126e876d4@linux.dev
---
 mm/filemap.c | 7 +++++++
 1 file changed, 7 insertions(+)

diff --git a/mm/filemap.c b/mm/filemap.c
index b74bc1e5015c..1df776907140 100644
--- a/mm/filemap.c
+++ b/mm/filemap.c
@@ -3968,6 +3968,13 @@ vm_fault_t filemap_map_pages(struct vm_fault *vmf,
 		unsigned long end;
 		vm_fault_t map_ret;
 
+		/* A lower index could move vmf->pte out of its page table. */
+		if (unlikely(xas.xa_index < last_pgoff)) {
+			folio_unlock(folio);
+			folio_put(folio);
+			break;
+		}
+
 		addr += (xas.xa_index - last_pgoff) << PAGE_SHIFT;
 		vmf->pte += xas.xa_index - last_pgoff;
 		last_pgoff = xas.xa_index;

---
base-commit: 65ba8f34ec886ec72b2c07ddc8cf3d815c32869d
change-id: 20261028-jf-mm-filemap-map-pages-lower-bound-a623ed8a81c9

Best regards,
--  
Jose Fernandez (Anthropic) <jose.fernandez@linux.dev>


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

* Re: [PATCH v2] mm/filemap: stop fault-around when the walk steps back
  2026-10-31  4:33 [PATCH v2] mm/filemap: stop fault-around when the walk steps back Jose Fernandez (Anthropic)
@ 2026-11-02 17:27 ` Andrew Morton
  2026-11-05 22:13   ` Jose Fernandez
  2026-11-02 21:47 ` Jan Kara
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 12+ messages in thread
From: Andrew Morton @ 2026-11-02 17:27 UTC (permalink / raw)
  To: Jose Fernandez (Anthropic)
  Cc: Matthew Wilcox (Oracle),
	Jan Kara, David Hildenbrand, linux-fsdevel, linux-mm,
	linux-kernel, Krystian Kaniewski, stable

On Sat, 31 Oct 2026 04:33:59 +0000 "Jose Fernandez (Anthropic)" <jose.fernandez@linux.dev> wrote:

> filemap_map_pages() can dereference a PTE pointer outside the page table
> it locked. KASAN has reported this as a use-after-free read [1]. The
> same path can also write out of bounds. When the stray entry reads as
> none, the fault-around loop maps a page there with set_pte_range().
> 
> Its fault-around loop walks the page cache and moves vmf->pte by the
> change in index. A lower index moves it back. xas_find() can step back
> to a lower index when a multi-index entry is split during the walk.
> 
> [1] fixes xas_find(), but the loop still relies on the walk never
> stepping back. Stop the fault-around when the walk steps back. ->fault()
> maps the faulting page if the loop did not.

Thanks.

Can I add your tested-by to [1]?

> Link: https://lore.kernel.org/20260904121301.200049-1-krystianmkaniewski@gmail.com [1]

"xarray: fix index jumping backwards in xas_find()".


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

* Re: [PATCH v2] mm/filemap: stop fault-around when the walk steps back
  2026-10-31  4:33 [PATCH v2] mm/filemap: stop fault-around when the walk steps back Jose Fernandez (Anthropic)
  2026-11-02 17:27 ` Andrew Morton
@ 2026-11-02 21:47 ` Jan Kara
  2026-11-03 12:12 ` Pedro Falcato
  2026-11-05 10:07 ` David Hildenbrand (Arm)
  3 siblings, 0 replies; 12+ messages in thread
From: Jan Kara @ 2026-11-02 21:47 UTC (permalink / raw)
  To: Jose Fernandez (Anthropic)
  Cc: Matthew Wilcox (Oracle),
	Jan Kara, Andrew Morton, David Hildenbrand, linux-fsdevel,
	linux-mm, linux-kernel, Krystian Kaniewski, stable

On Sat 31-10-26 04:33:59, Jose Fernandez (Anthropic) wrote:
> filemap_map_pages() can dereference a PTE pointer outside the page table
> it locked. KASAN has reported this as a use-after-free read [1]. The
> same path can also write out of bounds. When the stray entry reads as
> none, the fault-around loop maps a page there with set_pte_range().
> 
> Its fault-around loop walks the page cache and moves vmf->pte by the
> change in index. A lower index moves it back. xas_find() can step back
> to a lower index when a multi-index entry is split during the walk.
> 
> [1] fixes xas_find(), but the loop still relies on the walk never
> stepping back. Stop the fault-around when the walk steps back. ->fault()
> maps the faulting page if the loop did not.
> 
> Link: https://lore.kernel.org/20260904121301.200049-1-krystianmkaniewski@gmail.com [1]
> Fixes: 6b24ca4a1a8d ("mm: Use multi-index entries in the page cache")
> Cc: stable@vger.kernel.org
> Signed-off-by: Jose Fernandez (Anthropic) <jose.fernandez@linux.dev>

Looks good. Feel free to add:

Reviewed-by: Jan Kara <jack@suse.cz>

								Honza

> ---
> Changes in v2:
> - Add Fixes: and Cc: stable tags, and say in the message that the loop
>   can also write out of bounds (David Hildenbrand).
>   https://patch.msgid.link/0a1ad7d2-ff0e-4248-8f03-2a64a9134aae@kernel.org
> - Link to v1: https://patch.msgid.link/20261028-jf-mm-filemap-map-pages-lower-bound-v1-1-0bf126e876d4@linux.dev
> ---
>  mm/filemap.c | 7 +++++++
>  1 file changed, 7 insertions(+)
> 
> diff --git a/mm/filemap.c b/mm/filemap.c
> index b74bc1e5015c..1df776907140 100644
> --- a/mm/filemap.c
> +++ b/mm/filemap.c
> @@ -3968,6 +3968,13 @@ vm_fault_t filemap_map_pages(struct vm_fault *vmf,
>  		unsigned long end;
>  		vm_fault_t map_ret;
>  
> +		/* A lower index could move vmf->pte out of its page table. */
> +		if (unlikely(xas.xa_index < last_pgoff)) {
> +			folio_unlock(folio);
> +			folio_put(folio);
> +			break;
> +		}
> +
>  		addr += (xas.xa_index - last_pgoff) << PAGE_SHIFT;
>  		vmf->pte += xas.xa_index - last_pgoff;
>  		last_pgoff = xas.xa_index;
> 
> ---
> base-commit: 65ba8f34ec886ec72b2c07ddc8cf3d815c32869d
> change-id: 20261028-jf-mm-filemap-map-pages-lower-bound-a623ed8a81c9
> 
> Best regards,
> --  
> Jose Fernandez (Anthropic) <jose.fernandez@linux.dev>
> 
-- 
Jan Kara <jack@suse.com>
SUSE Labs, CR

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

* Re: [PATCH v2] mm/filemap: stop fault-around when the walk steps back
  2026-10-31  4:33 [PATCH v2] mm/filemap: stop fault-around when the walk steps back Jose Fernandez (Anthropic)
  2026-11-02 17:27 ` Andrew Morton
  2026-11-02 21:47 ` Jan Kara
@ 2026-11-03 12:12 ` Pedro Falcato
  2026-11-04 23:12   ` Jose Fernandez (Anthropic)
  2026-11-05 10:07 ` David Hildenbrand (Arm)
  3 siblings, 1 reply; 12+ messages in thread
From: Pedro Falcato @ 2026-11-03 12:12 UTC (permalink / raw)
  To: Jose Fernandez (Anthropic)
  Cc: Matthew Wilcox (Oracle),
	Jan Kara, Andrew Morton, David Hildenbrand, linux-fsdevel,
	linux-mm, linux-kernel, Krystian Kaniewski, stable

On Sat, Oct 31, 2026 at 04:33:59AM +0000, Jose Fernandez (Anthropic) wrote:
> filemap_map_pages() can dereference a PTE pointer outside the page table
> it locked. KASAN has reported this as a use-after-free read [1]. The
> same path can also write out of bounds. When the stray entry reads as
> none, the fault-around loop maps a page there with set_pte_range().
> 
> Its fault-around loop walks the page cache and moves vmf->pte by the
> change in index. A lower index moves it back. xas_find() can step back
> to a lower index when a multi-index entry is split during the walk.
> 
> [1] fixes xas_find(), but the loop still relies on the walk never
> stepping back. Stop the fault-around when the walk steps back. ->fault()
> maps the faulting page if the loop did not.

I'm not sure I understand. xas_find() was fixed. Is there any other case where
this loop can step backwards?

If not, I don't think this should be Cc: stable, and should probably be a WARN_ON().
As it's a hardening patch and not an actual bugfix.


-- 
Pedro

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

* Re: [PATCH v2] mm/filemap: stop fault-around when the walk steps back
  2026-11-03 12:12 ` Pedro Falcato
@ 2026-11-04 23:12   ` Jose Fernandez (Anthropic)
  2026-11-05 12:19     ` Pedro Falcato
  0 siblings, 1 reply; 12+ messages in thread
From: Jose Fernandez (Anthropic) @ 2026-11-04 23:12 UTC (permalink / raw)
  To: Pedro Falcato
  Cc: Matthew Wilcox (Oracle),
	Jan Kara, Andrew Morton, David Hildenbrand, linux-fsdevel,
	linux-mm, linux-kernel, Krystian Kaniewski, stable

On Tue, Nov 03, 2026 at 12:12:58PM +0000, Pedro Falcato wrote:
> I'm not sure I understand. xas_find() was fixed. Is there any other case where
> this loop can step backwards?
>
> If not, I don't think this should be Cc: stable, and should probably be a WARN_ON().
> As it's a hardening patch and not an actual bugfix.

Yes, there is at least one more case. It triggers when a read fault
starts a lookup inside a large folio while a hole punch removes that
folio and a write puts part of it back. A stress test I wrote for the
race still made the fault-around walk step back with Krystian's
xas_find() fix applied. It happened in every run with an artificial
delay in the lookup and in 2 out of 40 runs without one. Some of those
step backs left a page mapped before the start of the mapping, and the
kernel reported it as a bad rss-counter when the process exited. With
this patch applied none did.

A WARN_ON() doesn't fit because ordinary operations racing with a fault
can reach this. So I recommend keeping the Fixes and stable tags as
David suggested on v1.

- Jose

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

* Re: [PATCH v2] mm/filemap: stop fault-around when the walk steps back
  2026-10-31  4:33 [PATCH v2] mm/filemap: stop fault-around when the walk steps back Jose Fernandez (Anthropic)
                   ` (2 preceding siblings ...)
  2026-11-03 12:12 ` Pedro Falcato
@ 2026-11-05 10:07 ` David Hildenbrand (Arm)
  3 siblings, 0 replies; 12+ messages in thread
From: David Hildenbrand (Arm) @ 2026-11-05 10:07 UTC (permalink / raw)
  To: Jose Fernandez (Anthropic), Matthew Wilcox (Oracle),
	Jan Kara, Andrew Morton
  Cc: linux-fsdevel, linux-mm, linux-kernel, Krystian Kaniewski, stable

On 10/31/26 05:33, Jose Fernandez (Anthropic) wrote:
> filemap_map_pages() can dereference a PTE pointer outside the page table
> it locked. KASAN has reported this as a use-after-free read [1]. The
> same path can also write out of bounds. When the stray entry reads as
> none, the fault-around loop maps a page there with set_pte_range().
> 
> Its fault-around loop walks the page cache and moves vmf->pte by the
> change in index. A lower index moves it back. xas_find() can step back
> to a lower index when a multi-index entry is split during the walk.
> 
> [1] fixes xas_find(), but the loop still relies on the walk never
> stepping back. Stop the fault-around when the walk steps back. ->fault()
> maps the faulting page if the loop did not.
> 
> Link: https://lore.kernel.org/20260904121301.200049-1-krystianmkaniewski@gmail.com [1]
> Fixes: 6b24ca4a1a8d ("mm: Use multi-index entries in the page cache")
> Cc: stable@vger.kernel.org
> Signed-off-by: Jose Fernandez (Anthropic) <jose.fernandez@linux.dev>
> ---
> Changes in v2:
> - Add Fixes: and Cc: stable tags, and say in the message that the loop
>   can also write out of bounds (David Hildenbrand).
>   https://patch.msgid.link/0a1ad7d2-ff0e-4248-8f03-2a64a9134aae@kernel.org
> - Link to v1: https://patch.msgid.link/20261028-jf-mm-filemap-map-pages-lower-bound-v1-1-0bf126e876d4@linux.dev
> ---
>  mm/filemap.c | 7 +++++++
>  1 file changed, 7 insertions(+)
> 
> diff --git a/mm/filemap.c b/mm/filemap.c
> index b74bc1e5015c..1df776907140 100644
> --- a/mm/filemap.c
> +++ b/mm/filemap.c
> @@ -3968,6 +3968,13 @@ vm_fault_t filemap_map_pages(struct vm_fault *vmf,
>  		unsigned long end;
>  		vm_fault_t map_ret;
>  
> +		/* A lower index could move vmf->pte out of its page table. */
> +		if (unlikely(xas.xa_index < last_pgoff)) {
> +			folio_unlock(folio);
> +			folio_put(folio);
> +			break;
> +		}
> +
>  		addr += (xas.xa_index - last_pgoff) << PAGE_SHIFT;
>  		vmf->pte += xas.xa_index - last_pgoff;
>  		last_pgoff = xas.xa_index;
> 
> ---
> base-commit: 65ba8f34ec886ec72b2c07ddc8cf3d815c32869d
> change-id: 20261028-jf-mm-filemap-map-pages-lower-bound-a623ed8a81c9
> 
> Best regards,
> --  
> Jose Fernandez (Anthropic) <jose.fernandez@linux.dev>
> 

I'm trusting on Andrew to pick this up :)

-- 
Cheers,

David

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

* Re: [PATCH v2] mm/filemap: stop fault-around when the walk steps back
  2026-11-04 23:12   ` Jose Fernandez (Anthropic)
@ 2026-11-05 12:19     ` Pedro Falcato
  2026-11-05 22:43       ` Jose Fernandez (Anthropic)
  0 siblings, 1 reply; 12+ messages in thread
From: Pedro Falcato @ 2026-11-05 12:19 UTC (permalink / raw)
  To: Jose Fernandez (Anthropic)
  Cc: Matthew Wilcox (Oracle),
	Jan Kara, Andrew Morton, David Hildenbrand, linux-fsdevel,
	linux-mm, linux-kernel, Krystian Kaniewski, stable

On Wed, Nov 04, 2026 at 11:12:41PM +0000, Jose Fernandez (Anthropic) wrote:
> On Tue, Nov 03, 2026 at 12:12:58PM +0000, Pedro Falcato wrote:
> > I'm not sure I understand. xas_find() was fixed. Is there any other case where
> > this loop can step backwards?
> >
> > If not, I don't think this should be Cc: stable, and should probably be a WARN_ON().
> > As it's a hardening patch and not an actual bugfix.
> 
> Yes, there is at least one more case. It triggers when a read fault
> starts a lookup inside a large folio while a hole punch removes that
> folio and a write puts part of it back. A stress test I wrote for the

Thank you. So, if I'm not mistaken, this is the race:
First, you map/see this:
	[folio pgoff 4, length 4]
Then, at some point in time, someone punches the hole:
	[no folios here]
Then, at some point in time, someone faults another folio in the range:
	[folio pgoff 0, length 8]
so you step backwards. Is this correct? Or am I misunderstanding this?

If so, how can this happen while we hold the PTE lock? That itself should
block truncation.

Can you share the reproducer?

-- 
Pedro

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

* Re: [PATCH v2] mm/filemap: stop fault-around when the walk steps back
  2026-11-02 17:27 ` Andrew Morton
@ 2026-11-05 22:13   ` Jose Fernandez
  2026-11-05 22:24     ` Jose Fernandez (Anthropic)
  0 siblings, 1 reply; 12+ messages in thread
From: Jose Fernandez @ 2026-11-05 22:13 UTC (permalink / raw)
  To: Andrew Morton
  Cc: Matthew Wilcox (Oracle),
	Jan Kara, David Hildenbrand, linux-fsdevel, linux-mm,
	linux-kernel, Krystian Kaniewski, stable

On Mon, 2 Nov 2026 09:27:50 -0800, Andrew Morton wrote:
> Can I add your tested-by to [1]?
> 
> "xarray: fix index jumping backwards in xas_find()".

Yes, please do:

Tested-by: Jose Fernandez (Anthropic) <jose.fernandez@linux.dev>

I ran it under the stress test from my reply to Pedro in this thread.

- Jose

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

* Re: [PATCH v2] mm/filemap: stop fault-around when the walk steps back
  2026-11-05 22:13   ` Jose Fernandez
@ 2026-11-05 22:24     ` Jose Fernandez (Anthropic)
  0 siblings, 0 replies; 12+ messages in thread
From: Jose Fernandez (Anthropic) @ 2026-11-05 22:24 UTC (permalink / raw)
  To: Andrew Morton
  Cc: Matthew Wilcox (Oracle),
	Jan Kara, David Hildenbrand, linux-fsdevel, linux-mm,
	linux-kernel, Krystian Kaniewski, stable

On Thu, 5 Nov 2026 22:13:34 +0000, Jose Fernandez wrote:
> I ran it under the stress test from my reply to Pedro in this thread.

I also ran the check_multi_find_4 test that comes with the patch. It
fails without the fix and passes with it. The stress test only ran with
the fix applied.

- Jose

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

* Re: [PATCH v2] mm/filemap: stop fault-around when the walk steps back
  2026-11-05 12:19     ` Pedro Falcato
@ 2026-11-05 22:43       ` Jose Fernandez (Anthropic)
  2026-11-06 11:36         ` Krystian Kaniewski
  0 siblings, 1 reply; 12+ messages in thread
From: Jose Fernandez (Anthropic) @ 2026-11-05 22:43 UTC (permalink / raw)
  To: Pedro Falcato
  Cc: Matthew Wilcox (Oracle),
	Jan Kara, Andrew Morton, David Hildenbrand, linux-fsdevel,
	linux-mm, linux-kernel, Krystian Kaniewski, stable

On Thu, 5 Nov 2026 12:19:13 +0000, Pedro Falcato wrote:
> Thank you. So, if I'm not mistaken, this is the race:
> First, you map/see this:
> 	[folio pgoff 4, length 4]
> Then, at some point in time, someone punches the hole:
> 	[no folios here]
> Then, at some point in time, someone faults another folio in the range:
> 	[folio pgoff 0, length 8]
> so you step backwards. Is this correct? Or am I misunderstanding this?

Not quite. In my test a large folio goes away and a small one comes back
below where the walk started. If that happens during the walk's first
lookup, the walk can step back. Krystian's fix doesn't cover that
lookup.

In your order it doesn't step back. The new folio covers the place where
the walk is, so the walk either gets it at the right index or skips its
sibling slots.

> If so, how can this happen while we hold the PTE lock? That itself should
> block truncation.

We don't hold it yet. filemap_map_pages() does the first lookup under
RCU and takes the PTE lock after it.

> Can you share the reproducer?

With the delays it can corrupt memory on a kernel without this patch, so
I'd rather not post it. I can share it off-list with the maintainers if
they want it. The 40 runs without a delay were on a debug kernel with
the delays set to 0.

Andrew, I can send a v3 with this case in the commit message if you
want it.

- Jose

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

* Re: [PATCH v2] mm/filemap: stop fault-around when the walk steps back
  2026-11-05 22:43       ` Jose Fernandez (Anthropic)
@ 2026-11-06 11:36         ` Krystian Kaniewski
  2026-11-06 15:57           ` Andrew Morton
  0 siblings, 1 reply; 12+ messages in thread
From: Krystian Kaniewski @ 2026-11-06 11:36 UTC (permalink / raw)
  To: Jose Fernandez (Anthropic), Pedro Falcato
  Cc: Matthew Wilcox (Oracle),
	Jan Kara, Andrew Morton, David Hildenbrand, linux-fsdevel,
	linux-mm, linux-kernel, stable

On 11/5/26 23:43, Jose Fernandez (Anthropic) wrote:
> On Thu, 5 Nov 2026 12:19:13 +0000, Pedro Falcato wrote:
>> Thank you. So, if I'm not mistaken, this is the race:
>> First, you map/see this:
>> 	[folio pgoff 4, length 4]
>> Then, at some point in time, someone punches the hole:
>> 	[no folios here]
>> Then, at some point in time, someone faults another folio in the range:
>> 	[folio pgoff 0, length 8]
>> so you step backwards. Is this correct? Or am I misunderstanding this?
> 
> Not quite. In my test a large folio goes away and a small one comes back
> below where the walk started. If that happens during the walk's first
> lookup, the walk can step back. Krystian's fix doesn't cover that
> lookup.
> 
Right. My patch only covers the case where the walk has already
returned an entry. The first lookup, and every lookup after xas_retry(),
starts in the XAS_RESTART branch. My patch does not change that branch.

There, xas_load() can read the sibling slot of the start index before
a concurrent erase, and the canonical slot after it. In that case it
returns NULL, but xa_offset still points to the canonical slot.
xas_find() then moves on to the slot after the canonical slot. That
slot can be below the start index.

I tested this in the userspace XArray harness. I wrote that state into
a node and called xas_find() from XAS_RESTART. It returned an index
below the start, with and without my patch.

Andrew, Jose's Tested-by for the xarray patch is on the filemap patch
in mm-hotfixes-unstable.
-- 
Krystian Kaniewski

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

* Re: [PATCH v2] mm/filemap: stop fault-around when the walk steps back
  2026-11-06 11:36         ` Krystian Kaniewski
@ 2026-11-06 15:57           ` Andrew Morton
  0 siblings, 0 replies; 12+ messages in thread
From: Andrew Morton @ 2026-11-06 15:57 UTC (permalink / raw)
  To: Krystian Kaniewski
  Cc: Jose Fernandez (Anthropic),
	Pedro Falcato, Matthew Wilcox (Oracle),
	Jan Kara, David Hildenbrand, linux-fsdevel, linux-mm,
	linux-kernel, stable

On Fri, 6 Nov 2026 12:36:05 +0100 Krystian Kaniewski <krystianmkaniewski@gmail.com> wrote:

> Andrew, Jose's Tested-by for the xarray patch is on the filemap patch
> in mm-hotfixes-unstable.

So it is, thanks.  I've moved that into "xarray: fix index jumping
backwards in xas_find()":


From: Krystian Kaniewski <krystianmkaniewski@gmail.com>
Subject: xarray: fix index jumping backwards in xas_find()
Date: Fri, 4 Sep 2026 14:12:59 +0200

A bug in the XArray iterator xas_find() causes the iterator's index
(xas->xa_index) to jump backwards when iterating over a multi-index entry
(like a THP) that resides in a non-leaf node and is concurrently split.

When iterating over a multi-index entry in a non-leaf node, xas_load()
sets xas->xa_offset to the base offset of the entry, but leaves
xas->xa_index at the requested index.  When the caller subsequently wants
to advance to the next entry, xas_find() is called.  xas_find() attempts
to synchronize xas->xa_offset with xas->xa_index before advancing. 
However, the fixup logic was incorrectly restricted to leaf nodes
(!xas->xa_node->shift).  Because the THP resides in a non-leaf node, the
fixup is skipped.

As a result, xas_find() simply increments xas->xa_offset and recalculates
xas->xa_index based on this new offset.  This causes xas->xa_index to jump
backwards.  If the THP was concurrently split, the entry at the new offset
is a node pointer, so xas_find() descends into it and returns the folio at
the backwards index.  The caller (filemap_map_pages()) then calculates the
PTE pointer based on this backwards index, resulting in an invalid memory
access such as an out-of-bounds read or use-after-free on a page-table
page freed via tlb_remove_table_rcu().

A userspace access that faults in a file-backed mapping can trigger this
path.  When the index moves backwards, filemap_map_pages() can calculate a
PTE outside the page locked for fault-around and dereference a freed
page-table page, resulting in a KASAN-detected use-after-free read.

To fix this, check if xas->xa_offset matches get_offset(xas->xa_index,
xas->xa_node).  If it does not and the node is a non-leaf node, set
xas->xa_offset to get_offset(xas->xa_index, xas->xa_node) before
advancing.  Also add test cases in test_xarray to verify xas_find()
behavior when iterating over and splitting multi-index entries.

Link: https://lore.kernel.org/20260904121301.200049-1-krystianmkaniewski@gmail.com
Fixes: b803b42823d0 ("xarray: Add XArray iterators")
Reported-by: syzbot+b72767277f29b6407083@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=b72767277f29b6407083
Closes: https://syzkaller.appspot.com/ai_job?id=a01c56bd-74d0-411c-afb4-ee6f0cb6cb61
Signed-off-by: Krystian Kaniewski <krystianmkaniewski@gmail.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Tested-by: Jose Fernandez (Anthropic) <jose.fernandez@linux.dev>
  Link: https://lore.kernel.org/20261104-jf-filemap-v2-pedro-reply-r2-74c87c6fbcf@linux.dev
Cc: Matthew Wilcox (Oracle) <willy@infradead.org>
Cc: Mike Rapoport <rppt@kernel.org>
Cc: <stable@vger.kernel.org>
---

 lib/test_xarray.c |   70 ++++++++++++++++++++++++++++++++++++++++++++
 lib/xarray.c      |    8 +++--
 2 files changed, 75 insertions(+), 3 deletions(-)

--- a/lib/test_xarray.c~xarray-fix-index-jumping-backwards-in-xas_find
+++ a/lib/test_xarray.c
@@ -1247,6 +1247,75 @@ static noinline void check_multi_find_3(
 	}
 }
 
+static noinline void check_multi_find_4(struct xarray *xa)
+{
+#ifdef CONFIG_XARRAY_MULTI
+	unsigned int order = XA_CHUNK_SHIFT + 1;
+	unsigned long next = 1UL << order;
+	unsigned long start = next - 1;
+	XA_STATE(xas, xa, start);
+	XA_STATE_ORDER(split, xa, 0, 0);
+	void *entry;
+	unsigned long i;
+
+	/* Multi-index entry in two slots of a non-leaf node. */
+	xa_store_order(xa, 0, order, xa_mk_index(0), GFP_KERNEL);
+	XA_BUG_ON(xa, xa_store_index(xa, next, GFP_KERNEL) != NULL);
+
+	rcu_read_lock();
+	entry = xas_find(&xas, ULONG_MAX);
+	XA_BUG_ON(xa, entry != xa_mk_index(0));
+	XA_BUG_ON(xa, xas.xa_index != start);
+
+	entry = xas_find(&xas, ULONG_MAX);
+	XA_BUG_ON(xa, entry != xa_mk_index(next));
+	XA_BUG_ON(xa, xas.xa_index != next);
+
+	entry = xas_find(&xas, ULONG_MAX);
+	XA_BUG_ON(xa, entry != NULL);
+	rcu_read_unlock();
+
+	xa_erase_index(xa, next);
+	xa_erase_index(xa, 0);
+	XA_BUG_ON(xa, !xa_empty(xa));
+
+	/* Split the multi-index entry after a lookup begins inside it. */
+	xa_store_order(xa, 0, order, xa_mk_index(0), GFP_KERNEL);
+	XA_BUG_ON(xa, xa_store_index(xa, next, GFP_KERNEL) != NULL);
+
+	xas_set(&xas, start);
+	rcu_read_lock();
+	entry = xas_find(&xas, ULONG_MAX);
+	XA_BUG_ON(xa, entry != xa_mk_index(0));
+	XA_BUG_ON(xa, xas.xa_index != start);
+	rcu_read_unlock();
+
+	xas_split_alloc(&split, xa_mk_index(0), order, GFP_KERNEL);
+	if (xas_error(&split)) {
+		XA_BUG_ON(xa, true);
+		goto out;
+	}
+	xas_lock(&split);
+	xas_split(&split, xa_mk_index(0), order);
+	for (i = 0; i < next; i++)
+		__xa_store(xa, i, xa_mk_index(i), 0);
+	xas_unlock(&split);
+
+	rcu_read_lock();
+	entry = xas_find(&xas, ULONG_MAX);
+	XA_BUG_ON(xa, entry != xa_mk_index(next));
+	XA_BUG_ON(xa, xas.xa_index != next);
+
+	entry = xas_find(&xas, ULONG_MAX);
+	XA_BUG_ON(xa, entry != NULL);
+	rcu_read_unlock();
+
+out:
+	xa_destroy(xa);
+	XA_BUG_ON(xa, !xa_empty(xa));
+#endif
+}
+
 static noinline void check_find_1(struct xarray *xa)
 {
 	unsigned long i, j, k;
@@ -1370,6 +1439,7 @@ static noinline void check_find(struct x
 		check_multi_find_1(xa, i);
 	check_multi_find_2(xa);
 	check_multi_find_3(xa);
+	check_multi_find_4(xa);
 }
 
 /* See find_swap_entry() in mm/shmem.c */
--- a/lib/xarray.c~xarray-fix-index-jumping-backwards-in-xas_find
+++ a/lib/xarray.c
@@ -1410,9 +1410,11 @@ void *xas_find(struct xa_state *xas, uns
 		entry = xas_load(xas);
 		if (entry || xas_not_node(xas->xa_node))
 			return entry;
-	} else if (!xas->xa_node->shift &&
-		    xas->xa_offset != (xas->xa_index & XA_CHUNK_MASK)) {
-		xas->xa_offset = ((xas->xa_index - 1) & XA_CHUNK_MASK) + 1;
+	} else if (xas->xa_offset != get_offset(xas->xa_index, xas->xa_node)) {
+		if (!xas->xa_node->shift)
+			xas->xa_offset = ((xas->xa_index - 1) & XA_CHUNK_MASK) + 1;
+		else
+			xas->xa_offset = get_offset(xas->xa_index, xas->xa_node);
 	}
 
 	xas_next_offset(xas);
_


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

end of thread, other threads:[~2026-11-06 15:57 UTC | newest]

Thread overview: 12+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-31  4:33 [PATCH v2] mm/filemap: stop fault-around when the walk steps back Jose Fernandez (Anthropic)
2026-11-02 17:27 ` Andrew Morton
2026-11-05 22:13   ` Jose Fernandez
2026-11-05 22:24     ` Jose Fernandez (Anthropic)
2026-11-02 21:47 ` Jan Kara
2026-11-03 12:12 ` Pedro Falcato
2026-11-04 23:12   ` Jose Fernandez (Anthropic)
2026-11-05 12:19     ` Pedro Falcato
2026-11-05 22:43       ` Jose Fernandez (Anthropic)
2026-11-06 11:36         ` Krystian Kaniewski
2026-11-06 15:57           ` Andrew Morton
2026-11-05 10:07 ` David Hildenbrand (Arm)

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

all inboxes | Powered by JetHome®