From: Krystian Kaniewski <krystianmkaniewski@gmail.com>
To: Andrew Morton <akpm@linux-foundation.org>
Cc: Matthew Wilcox <willy@infradead.org>,
Mike Rapoport <rppt@kernel.org>,
linux-fsdevel@vger.kernel.org, linux-mm@kvack.org,
linux-kernel@vger.kernel.org,
syzbot+b72767277f29b6407083@syzkaller.appspotmail.com,
stable@vger.kernel.org
Subject: [PATCH v2] xarray: fix index jumping backwards in xas_find()
Date: Fri, 4 Sep 2026 14:12:59 +0200 [thread overview]
Message-ID: <20260904121301.200049-1-krystianmkaniewski@gmail.com> (raw)
In-Reply-To: <app_o87YogZExbwm@kernel.org>
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.
Fixes: b803b42823d0 ("xarray: Add XArray iterators")
Cc: <stable@vger.kernel.org>
Assisted-by: Gemini:gemini-3.7-flash syzbot
Reported-by: syzbot+b72767277f29b6407083@syzkaller.appspotmail.com
Closes: https://syzkaller.appspot.com/bug?extid=b72767277f29b6407083
Link: https://syzkaller.appspot.com/ai_job?id=a01c56bd-74d0-411c-afb4-ee6f0cb6cb61
Signed-off-by: Krystian Kaniewski <krystianmkaniewski@gmail.com>
---
Changes in v2:
- Derive the regression-test geometry from XA_CHUNK_SHIFT so it works with
both the userspace harness (SHIFT=3) and the kernel (SHIFT=6).
- Check the xas_split_alloc() result before calling xas_split().
- Describe the userspace-triggered use-after-free and add the stable Cc.
v1: https://lore.kernel.org/all/2992424b-2120-489e-9010-f45f46ed52c8@mail.kernel.org/
Tests:
- Applied with git am to linux-mm commit b0266eddcb10 (the base of the
reported CI run).
- Ubuntu 24.04 x86_64 mm-ci host tests: memblock, VMA, Maple tree, XArray,
Radix tree and IDA, Multiorder XArray, and IDR all passed.
- Userspace XArray tests with ASan/UBSan passed for SHIFT=3 and SHIFT=6.
- The built-in XArray test suite passed under generic KASAN.
lib/test_xarray.c | 70 +++++++++++++++++++++++++++++++++++++++++++++++
lib/xarray.c | 8 ++++--
2 files changed, 75 insertions(+), 3 deletions(-)
diff --git a/lib/test_xarray.c b/lib/test_xarray.c
index 5ca0aefee9aa..615f7730a5bd 100644
--- a/lib/test_xarray.c
+++ b/lib/test_xarray.c
@@ -1247,6 +1247,75 @@ static noinline void check_multi_find_3(struct xarray *xa)
}
}
+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 xarray *xa)
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 */
diff --git a/lib/xarray.c b/lib/xarray.c
index 9a8b4916540c..980324d686bd 100644
--- a/lib/xarray.c
+++ b/lib/xarray.c
@@ -1406,9 +1406,11 @@ void *xas_find(struct xa_state *xas, unsigned long max)
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);
base-commit: 8d3ae59288f1e7d58d76558a6ee96d533bc5019f
--
2.53.0
prev parent reply other threads:[~2026-09-04 12:13 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-09-03 13:48 [PATCH] xarray: fix index jumping backwards in xas_find() syzbot
2026-09-03 18:22 ` Andrew Morton
2026-09-04 8:21 ` Mike Rapoport
2026-09-04 8:41 ` Krystian Kaniewski
2026-09-04 12:16 ` Krystian Kaniewski
2026-09-04 12:12 ` Krystian Kaniewski [this message]
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260904121301.200049-1-krystianmkaniewski@gmail.com \
--to=krystianmkaniewski@gmail.com \
--cc=akpm@linux-foundation.org \
--cc=linux-fsdevel@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-mm@kvack.org \
--cc=rppt@kernel.org \
--cc=stable@vger.kernel.org \
--cc=syzbot+b72767277f29b6407083@syzkaller.appspotmail.com \
--cc=willy@infradead.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.