From 16549e3ca5dcbf619e44e28823044c6f260cb392 Mon Sep 17 00:00:00 2001 From: bspengler-oss <94915855+bspengler-oss@users.noreply.github.com> Date: Mon, 17 Nov 2025 20:40:41 -0500 Subject: [PATCH 1/5] 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 Signed-off-by: bspengler-oss <94915855+bspengler-oss@users.noreply.github.com> --- module/os/linux/zfs/abd_os.c | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/module/os/linux/zfs/abd_os.c b/module/os/linux/zfs/abd_os.c index 18f2426fbbfc..d0c624868f33 100644 --- a/module/os/linux/zfs/abd_os.c +++ b/module/os/linux/zfs/abd_os.c @@ -911,11 +911,19 @@ abd_iter_map(struct abd_iter *aiter) aiter->iter_mapsize = aiter->iter_abd->abd_size - offset; paddr = ABD_LINEAR_BUF(aiter->iter_abd); } else { + struct page *page; + offset = aiter->iter_offset; 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)); + page = sg_page(aiter->iter_sg); + if (PageHighMem(page)) { + page = nth_page(page, offset / PAGE_SIZE); + offset &= ~PAGE_MASK; + aiter->iter_mapsize = MIN(aiter->iter_mapsize, PAGE_SIZE - offset); + } + paddr = zfs_kmap_local(page); } aiter->iter_mapaddr = (char *)paddr + offset; @@ -933,8 +941,15 @@ abd_iter_unmap(struct abd_iter *aiter) return; if (!abd_is_linear(aiter->iter_abd)) { + struct page *page; + size_t offset = aiter->iter_offset; + + page = sg_page(aiter->iter_sg); + if (PageHighMem(page)) + offset &= ~PAGE_MASK; + /* 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); From 7b17cc5f9418dfeb9e52faab9aa05d014c570d4c Mon Sep 17 00:00:00 2001 From: bspengler-oss <94915855+bspengler-oss@users.noreply.github.com> Date: Mon, 17 Nov 2025 20:42:44 -0500 Subject: [PATCH 2/5] Preserve LIFO ordering of kmap ops in abd_raidz_gen_iterate() ZFS typically preserves proper LIFO ordering regarding map/unmap operations that wrap the Linux kernel's kmap interfaces that require such ordering, but one instance in abd_raidz_gen_iterate() did not. Similar issues have been fixed in the Linux kernel in the past, see for instance CVE-2025-39899 for userfaultfd. Signed-off-by: bspengler-oss <94915855+bspengler-oss@users.noreply.github.com> --- module/zfs/abd.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/module/zfs/abd.c b/module/zfs/abd.c index bf9b13c30509..b473b305e3a7 100644 --- a/module/zfs/abd.c +++ b/module/zfs/abd.c @@ -1111,13 +1111,6 @@ abd_raidz_gen_iterate(abd_t **cabds, abd_t *dabd, size_t off, func_raidz_gen(caddrs, daddr, len, dlen); - for (i = parity-1; i >= 0; i--) { - abd_iter_unmap(&caiters[i]); - c_cabds[i] = - abd_advance_abd_iter(cabds[i], c_cabds[i], - &caiters[i], len); - } - if (dsize > 0) { abd_iter_unmap(&daiter); c_dabd = @@ -1126,6 +1119,13 @@ abd_raidz_gen_iterate(abd_t **cabds, abd_t *dabd, size_t off, dsize -= dlen; } + for (i = parity-1; i >= 0; i--) { + abd_iter_unmap(&caiters[i]); + c_cabds[i] = + abd_advance_abd_iter(cabds[i], c_cabds[i], + &caiters[i], len); + } + csize -= len; } abd_exit_critical(flags); From 166c635621f84cc37811caf9abe02253a8c67741 Mon Sep 17 00:00:00 2001 From: bspengler-oss <94915855+bspengler-oss@users.noreply.github.com> Date: Mon, 17 Nov 2025 20:56:07 -0500 Subject: [PATCH 3/5] Fix HIGHMEM/kmap API violation in zfs_uiomove_bvec_impl() Fix another instance where ZFS assumes multiple pages can be mapped at once via zfs_kmap_local(), resulting in crashes and potential memory corruption on HIGHMEM-enabled (typically 32-bit) systems. Signed-off-by: bspengler-oss <94915855+bspengler-oss@users.noreply.github.com> --- module/os/linux/zfs/zfs_uio.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/module/os/linux/zfs/zfs_uio.c b/module/os/linux/zfs/zfs_uio.c index d282f6d95ddf..2586676109ba 100644 --- a/module/os/linux/zfs/zfs_uio.c +++ b/module/os/linux/zfs/zfs_uio.c @@ -100,15 +100,16 @@ zfs_uiomove_bvec_impl(void *p, size_t n, zfs_uio_rw_t rw, zfs_uio_t *uio) while (n && uio->uio_resid) { void *paddr; - cnt = MIN(bv->bv_len - skip, n); + size_t offset = bv->bv_offset + skip; + cnt = MIN(PAGE_SIZE - (offset & ~PAGE_MASK), MIN(bv->bv_len - skip, n)); - paddr = zfs_kmap_local(bv->bv_page); + paddr = zfs_kmap_local(bv->bv_page + (offset >> PAGE_SHIFT)); if (rw == UIO_READ) { /* Copy from buffer 'p' to the bvec data */ - memcpy(paddr + bv->bv_offset + skip, p, cnt); + memcpy(paddr + (offset & ~PAGE_MASK), p, cnt); } else { /* Copy from bvec data to buffer 'p' */ - memcpy(p, paddr + bv->bv_offset + skip, cnt); + memcpy(p, paddr + (offset & ~PAGE_MASK), cnt); } zfs_kunmap_local(paddr); From e7dd1b202c42df12f7da3629032c0a50ddfdbbdd Mon Sep 17 00:00:00 2001 From: RageLtMan Date: Sun, 7 Dec 2025 16:08:19 -0500 Subject: [PATCH 4/5] Fix cstyle per behlendorf Signed-off-by: RageLtMan --- module/os/linux/zfs/abd_os.c | 5 +++-- module/os/linux/zfs/zfs_uio.c | 3 ++- module/zfs/abd.c | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/module/os/linux/zfs/abd_os.c b/module/os/linux/zfs/abd_os.c index d0c624868f33..6c364a83362e 100644 --- a/module/os/linux/zfs/abd_os.c +++ b/module/os/linux/zfs/abd_os.c @@ -912,7 +912,7 @@ abd_iter_map(struct abd_iter *aiter) paddr = ABD_LINEAR_BUF(aiter->iter_abd); } else { struct page *page; - + offset = aiter->iter_offset; aiter->iter_mapsize = MIN(aiter->iter_sg->length - offset, aiter->iter_abd->abd_size - aiter->iter_pos); @@ -921,7 +921,8 @@ abd_iter_map(struct abd_iter *aiter) if (PageHighMem(page)) { page = nth_page(page, offset / PAGE_SIZE); offset &= ~PAGE_MASK; - aiter->iter_mapsize = MIN(aiter->iter_mapsize, PAGE_SIZE - offset); + aiter->iter_mapsize = MIN(aiter->iter_mapsize, + PAGE_SIZE - offset); } paddr = zfs_kmap_local(page); } diff --git a/module/os/linux/zfs/zfs_uio.c b/module/os/linux/zfs/zfs_uio.c index 2586676109ba..8f9b161995f4 100644 --- a/module/os/linux/zfs/zfs_uio.c +++ b/module/os/linux/zfs/zfs_uio.c @@ -101,7 +101,8 @@ zfs_uiomove_bvec_impl(void *p, size_t n, zfs_uio_rw_t rw, zfs_uio_t *uio) while (n && uio->uio_resid) { void *paddr; size_t offset = bv->bv_offset + skip; - cnt = MIN(PAGE_SIZE - (offset & ~PAGE_MASK), MIN(bv->bv_len - skip, n)); + cnt = MIN(PAGE_SIZE - (offset & ~PAGE_MASK), + MIN(bv->bv_len - skip, n)); paddr = zfs_kmap_local(bv->bv_page + (offset >> PAGE_SHIFT)); if (rw == UIO_READ) { diff --git a/module/zfs/abd.c b/module/zfs/abd.c index b473b305e3a7..120adc494c80 100644 --- a/module/zfs/abd.c +++ b/module/zfs/abd.c @@ -1125,7 +1125,7 @@ abd_raidz_gen_iterate(abd_t **cabds, abd_t *dabd, size_t off, abd_advance_abd_iter(cabds[i], c_cabds[i], &caiters[i], len); } - + csize -= len; } abd_exit_critical(flags); From 91e899b8ef9d47297cb42c76af1dfa377b21b7d5 Mon Sep 17 00:00:00 2001 From: RageLtMan Date: Sun, 7 Dec 2025 16:11:11 -0500 Subject: [PATCH 5/5] ~PAGE_MASK != PAGE_SIZE - 1 on FreeBSD Signed-off-by: RageLtMan --- module/os/linux/zfs/abd_os.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/module/os/linux/zfs/abd_os.c b/module/os/linux/zfs/abd_os.c index 6c364a83362e..9d55d614cb4d 100644 --- a/module/os/linux/zfs/abd_os.c +++ b/module/os/linux/zfs/abd_os.c @@ -920,7 +920,7 @@ abd_iter_map(struct abd_iter *aiter) page = sg_page(aiter->iter_sg); if (PageHighMem(page)) { page = nth_page(page, offset / PAGE_SIZE); - offset &= ~PAGE_MASK; + offset &= PAGE_SIZE - 1; aiter->iter_mapsize = MIN(aiter->iter_mapsize, PAGE_SIZE - offset); } @@ -947,7 +947,7 @@ abd_iter_unmap(struct abd_iter *aiter) page = sg_page(aiter->iter_sg); if (PageHighMem(page)) - offset &= ~PAGE_MASK; + offset &= PAGE_SIZE - 1; /* LINTED E_FUNC_SET_NOT_USED */ zfs_kunmap_local(aiter->iter_mapaddr - offset);