xfarray: don't crash when sorting if array element crosses a folio

LOLLM points out that if an array element crosses a folio boundary,
xfile_get_folio returns a NULL folio pointer.  If this happens,
si->folio is also set to NULL, and calling folio_pos/folio_address will
just crash the kernel.  Teach this function to handle this condition by
falling back to reading the array element into scratchpad memory.

Cc: stable@vger.kernel.org # v6.6
Fixes: cf36f4f64c ("xfs: cache pages used for xfarray quicksort convergence")
Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Assisted-by: LOLLM # finding obvious bugs
Reviewed-by: Christoph Hellwig <hch@lst.de>
Signed-off-by: Carlos Maiolino <cem@kernel.org>
This commit is contained in:
Darrick J. Wong 2026-09-22 22:59:42 -07:00 committed by Carlos Maiolino
parent 23067b4f98
commit 329750292e

View File

@ -794,6 +794,46 @@ xfarray_sort_scan_done(
si->folio = NULL;
}
static int
xfarray_sort_load_folio(
struct xfarray_sortinfo *si,
xfarray_idx_t idx,
loff_t idx_pos)
{
struct folio *folio;
loff_t next_pos;
folio = xfile_get_folio(si->array->xfile, idx_pos, si->array->obj_size,
XFILE_ALLOC);
if (IS_ERR(folio))
return PTR_ERR(folio);
si->folio = folio;
/* No folio? Get the caller to read into the scratchpad. */
if (!si->folio)
return 0;
si->first_folio_idx = xfarray_idx(si->array,
folio_pos(si->folio) + si->array->obj_size - 1);
next_pos = folio_next_pos(si->folio);
si->last_folio_idx = xfarray_idx(si->array, next_pos - 1);
if (xfarray_pos(si->array, si->last_folio_idx + 1) > next_pos)
si->last_folio_idx--;
/*
* If this folio still doesn't cover the desired element, it must cross
* a folio boundary. Get the caller to read into the scratchpad.
*/
if (idx < si->first_folio_idx || idx > si->last_folio_idx) {
xfarray_sort_scan_done(si);
return 0;
}
trace_xfarray_sort_scan(si, idx);
return 0;
}
/*
* Cache the folio backing the start of the given array element. If the array
* element is contained entirely within the folio, return a pointer to the
@ -819,33 +859,18 @@ xfarray_sort_scan(
(idx < si->first_folio_idx || idx > si->last_folio_idx))
xfarray_sort_scan_done(si);
/* Grab the first folio that backs this array element. */
/* Grab the folio that backs this array element. */
if (!si->folio) {
struct folio *folio;
loff_t next_pos;
folio = xfile_get_folio(si->array->xfile, idx_pos,
si->array->obj_size, XFILE_ALLOC);
if (IS_ERR(folio))
return PTR_ERR(folio);
si->folio = folio;
si->first_folio_idx = xfarray_idx(si->array,
folio_pos(si->folio) + si->array->obj_size - 1);
next_pos = folio_next_pos(si->folio);
si->last_folio_idx = xfarray_idx(si->array, next_pos - 1);
if (xfarray_pos(si->array, si->last_folio_idx + 1) > next_pos)
si->last_folio_idx--;
trace_xfarray_sort_scan(si, idx);
error = xfarray_sort_load_folio(si, idx, idx_pos);
if (error)
return error;
}
/*
* If this folio still doesn't cover the desired element, it must cross
* a folio boundary. Read into the scratchpad and we're done.
* If we don't have a folio mapping the entire array element, read into
* the scratchpad and we're done.
*/
if (idx < si->first_folio_idx || idx > si->last_folio_idx) {
if (!si->folio) {
void *temp = xfarray_scratch(si->array);
error = xfile_load(si->array->xfile, temp, si->array->obj_size,