Fix interaction of abd_iter_map()/abd_iter_unmap() with HIGHMEM

HIGHMEM kmap interfaces operate on only a single page at a time
yet ZFS hadn't accounted for this, resulting in crashes and
potential memory corruption on HIGHMEM (typically 32-bit) systems.
This was caught by PaX's KERNSEAL feature as it makes use of
HIGHMEM functionality on x64.

On typical 64-bit systems, this issue wouldn't have been observed,
as the map interfaces simply fall back to returning an address in
lowmem where the contiguous pages can be accessed directly.

Joint work with the PaX Team, tested by Mark van Dijk

Reviewed-by: RageLtMan <rageltman@sempervictus>
Reviewed-by: Rob Norris <robn@despairlabs.com>
Reviewed-by: Brian Behlendorf <behlendorf1@llnl.gov>
Signed-off-by: bspengler-oss <94915855+bspengler-oss@users.noreply.github.com>
Closes #15668
Closes #18030
This commit is contained in:
bspengler-oss 2025-11-17 20:40:41 -05:00 committed by Brian Behlendorf
parent a2f768f61f
commit 5e271995d1

View File

@ -915,7 +915,14 @@ abd_iter_map(struct abd_iter *aiter)
aiter->iter_mapsize = MIN(aiter->iter_sg->length - offset,
aiter->iter_abd->abd_size - aiter->iter_pos);
paddr = zfs_kmap_local(sg_page(aiter->iter_sg));
struct page *page = sg_page(aiter->iter_sg);
if (PageHighMem(page)) {
page = nth_page(page, offset / PAGE_SIZE);
offset &= PAGE_SIZE - 1;
aiter->iter_mapsize = MIN(aiter->iter_mapsize,
PAGE_SIZE - offset);
}
paddr = zfs_kmap_local(page);
}
aiter->iter_mapaddr = (char *)paddr + offset;
@ -933,8 +940,14 @@ abd_iter_unmap(struct abd_iter *aiter)
return;
if (!abd_is_linear(aiter->iter_abd)) {
size_t offset = aiter->iter_offset;
struct page *page = sg_page(aiter->iter_sg);
if (PageHighMem(page))
offset &= PAGE_SIZE - 1;
/* LINTED E_FUNC_SET_NOT_USED */
zfs_kunmap_local(aiter->iter_mapaddr - aiter->iter_offset);
zfs_kunmap_local(aiter->iter_mapaddr - offset);
}
ASSERT3P(aiter->iter_mapaddr, !=, NULL);