Re: [PATCH RFC] XArray: fix index jumping backwards in xas_find()
Krystian Kaniewski <[email protected]>
| Newsgroups | dev.linux.lists.syzbot |
|---|---|
| Message-ID | <[email protected]> |
Resynchronize a non-leaf `xas_find()` cursor to the node slot containing
`xa_index` before the existing `xas_next_offset()` call. The current
`get_offset(xa_index - 1, node) + 1` assignment already moves past the
resynchronization slot, then the common increment moves once more. For an
order-7 entry covering indexes 0 through 127, an adjacent entry at 128,
and a
walk beginning at 100, the current code changes the offset from 0 to 2
and then
to 3. It skips the stable entry at offset 2. This violates the documented
forward-iteration contract without any concurrent modification.
Keep the shift-aware mismatch detection and the XArray-layer repair. Use the
slot containing `xa_index` as the non-leaf resynchronization point, then
let the
existing helper perform the single forward step. Preserve the
established leaf
boundary calculation. Add deterministic coverage for the order-7 adjacent
entry case and for splitting a multi-index entry after a lookup begins
inside
it. Implement both cases in a new `check_multi_find_4()` helper in
`lib/test_xarray.c` and invoke it from `check_find()`.
Correct the commit message to describe the retained KASAN use-after-free in
`filemap_map_pages()` through `ptep_get()`, not a slab-out-of-bounds report.
Preserve the verified backward-index mechanism, the `Fixes` tag, syzbot
links,
and the existing Gemini provenance tag. Do not change locking, public
APIs, or
caller error behavior.
On 8/17/2026 3:04 AM, syzbot wrote:
> 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 out-of-bounds
> memory access.
>
> For example, a KASAN slab-out-of-bounds read can be triggered in ptep_get()
> via filemap_map_pages():
>
> BUG: KASAN: slab-out-of-bounds in ptep_get include/linux/pgtable.h:495
> [inline]
> BUG: KASAN: slab-out-of-bounds in filemap_map_folio_range mm/filemap.c:3820
> [inline]
> BUG: KASAN: slab-out-of-bounds in filemap_map_pages+0x1100/0x2090
> mm/filemap.c:3955
> Read of size 8 at addr ffff88811b3b4a00 by task syz-executor411/5876
>
> To fix this, remove the !xas->xa_node->shift restriction and use the
> get_offset() helper to correctly calculate the offset for any node shift.
> This ensures that xas->xa_offset is correctly synchronized with
> xas->xa_index before advancing, regardless of the node's depth in the tree.
>
> Fixes: b803b42823d0 ("xarray: Add XArray iterators")
> Assisted-by: Gemini:gemini-3.6-flash Gemini:gemini-3.1-pro-preview syzbot
> Reported-by: [email protected]
> Closes: https://syzkaller.appspot.com/bug?extid=b72767277f29b6407083
> Link: https://syzkaller.appspot.com/ai_job?id=608570d2-1765-472d-a79b-ea5b0b3a226a
> To: "Andrew Morton" <[email protected]>
> To: <[email protected]>
> To: <[email protected]>
> To: "Matthew Wilcox" <[email protected]>
> Cc: <[email protected]>
>
> ---
> diff --git a/lib/xarray.c b/lib/xarray.c
> index 9a8b49165..5695618bf 100644
> --- a/lib/xarray.c
> +++ b/lib/xarray.c
> @@ -1406,9 +1406,8 @@ 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)) {
> + xas->xa_offset = get_offset(xas->xa_index - 1, xas->xa_node) + 1;
> }
>
> xas_next_offset(xas);
>
>
> base-commit: 8d3ae59288f1e7d58d76558a6ee96d533bc5019f