Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3e2852ed authored by Matthew Wilcox (Oracle)'s avatar Matthew Wilcox (Oracle) Committed by Greg Kroah-Hartman
Browse files

XArray: Fix xas_create_range() when multi-order entry present



commit 3e3c658055c002900982513e289398a1aad4a488 upstream.

If there is already an entry present that is of order >= XA_CHUNK_SHIFT
when we call xas_create_range(), xas_create_range() will misinterpret
that entry as a node and dereference xa_node->parent, generally leading
to a crash that looks something like this:

general protection fault, probably for non-canonical address 0xdffffc0000000001:
0000 [#1] PREEMPT SMP KASAN
KASAN: null-ptr-deref in range [0x0000000000000008-0x000000000000000f]
CPU: 0 PID: 32 Comm: khugepaged Not tainted 5.17.0-rc8-syzkaller-00003-g56e337f2cf13 #0
RIP: 0010:xa_parent_locked include/linux/xarray.h:1207 [inline]
RIP: 0010:xas_create_range+0x2d9/0x6e0 lib/xarray.c:725

It's deterministically reproducable once you know what the problem is,
but producing it in a live kernel requires khugepaged to hit a race.
While the problem has been present since xas_create_range() was
introduced, I'm not aware of a way to hit it before the page cache was
converted to use multi-index entries.

Fixes: 6b24ca4a1a8d ("mm: Use multi-index entries in the page cache")
Reported-by: default avatar <syzbot+0d2b0bf32ca5cfd09f2e@syzkaller.appspotmail.com>
Signed-off-by: default avatarMatthew Wilcox (Oracle) <willy@infradead.org>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent a840286f
Loading
Loading
Loading
Loading
+22 −0
Original line number Diff line number Diff line
@@ -1438,6 +1438,25 @@ static noinline void check_create_range_4(struct xarray *xa,
	XA_BUG_ON(xa, !xa_empty(xa));
}

static noinline void check_create_range_5(struct xarray *xa,
		unsigned long index, unsigned int order)
{
	XA_STATE_ORDER(xas, xa, index, order);
	unsigned int i;

	xa_store_order(xa, index, order, xa_mk_index(index), GFP_KERNEL);

	for (i = 0; i < order + 10; i++) {
		do {
			xas_lock(&xas);
			xas_create_range(&xas);
			xas_unlock(&xas);
		} while (xas_nomem(&xas, GFP_KERNEL));
	}

	xa_destroy(xa);
}

static noinline void check_create_range(struct xarray *xa)
{
	unsigned int order;
@@ -1465,6 +1484,9 @@ static noinline void check_create_range(struct xarray *xa)
		check_create_range_4(xa, (3U << order) + 1, order);
		check_create_range_4(xa, (3U << order) - 1, order);
		check_create_range_4(xa, (1U << 24) + 1, order);

		check_create_range_5(xa, 0, order);
		check_create_range_5(xa, (1U << order), order);
	}

	check_create_range_3();
+2 −0
Original line number Diff line number Diff line
@@ -722,6 +722,8 @@ void xas_create_range(struct xa_state *xas)

		for (;;) {
			struct xa_node *node = xas->xa_node;
			if (node->shift >= shift)
				break;
			xas->xa_node = xa_parent_locked(xas->xa, node);
			xas->xa_offset = node->offset - 1;
			if (node->offset != 0)