[PATCH] iommufd/iova_bitmap: fail GET_DIRTY_BITMAP when a bitmap page cannot be pinned

From: samarth . colleges

Date: Tue Oct 13 2026 - 14:25:25 EST


From: Samarth Narang <samarth.colleges@xxxxxxxxx>

The iova_bitmap_set() function reports dirty IOVAs into a user-provided bitmap, it does so by
pinning the bitmap pages lazily one chunk at a time as the reported IOVA advances.
When advancing to the next chunk fails, the function simply returns without recording the dirty bits.

if (iova_bitmap_advance_to(bitmap, iova))
return;

So on an IOMMU_HWPT_GET_DIRTY_BITMAP ioctl, if any bitmap chunk past the
first cannot be pinned, the dirty pages in that chunk are cleared in the page table,
not set in the bitmap, and the ioctl will still return success.
This means that the destination VM will observe stale memory.

This patch records the first such error in the iova_bitmap and propagates it so the
ioctl fails instead of silently succeeding.

Fixes: 4a00f9434891 ("iommupt: Add read_and_clear_dirty op")
Signed-off-by: Samarth Narang <samarth.colleges@xxxxxxxxx>
---
diff --git a/drivers/iommu/iommufd/io_pagetable.c b/drivers/iommu/iommufd/io_pagetable.c
index 4e447ce74..e6540b0a2 100644
--- a/drivers/iommu/iommufd/io_pagetable.c
+++ b/drivers/iommu/iommufd/io_pagetable.c
@@ -589,7 +589,7 @@ iommu_read_and_clear_dirty(struct iommu_domain *domain,
arg.iopt = iopt;
arg.domain = domain;
arg.dirty = &dirty;
- iova_bitmap_for_each(iter, &arg, __iommu_read_and_clear_dirty);
+ ret = iova_bitmap_for_each(iter, &arg, __iommu_read_and_clear_dirty);

if (!(flags & IOMMU_DIRTY_NO_CLEAR))
iommu_iotlb_sync(domain, &gather);
diff --git a/drivers/iommu/iommufd/iova_bitmap.c b/drivers/iommu/iommufd/iova_bitmap.c
index dac3e657d..483f0cc84 100644
--- a/drivers/iommu/iommufd/iova_bitmap.c
+++ b/drivers/iommu/iommufd/iova_bitmap.c
@@ -116,6 +116,9 @@ struct iova_bitmap {

/* length of the IOVA range for the whole bitmap */
size_t length;
+
+ /* first error from pinning a bitmap chunk; -EFAULT etc. */
+ int err;
};

/*
@@ -384,7 +387,9 @@ static int iova_bitmap_advance_to(struct iova_bitmap *bitmap,
int iova_bitmap_for_each(struct iova_bitmap *bitmap, void *opaque,
iova_bitmap_fn_t fn)
{
- return fn(bitmap, bitmap->iova, bitmap->length, opaque);
+ int ret = fn(bitmap, bitmap->iova, bitmap->length, opaque);
+
+ return ret ? ret : bitmap->err;
}
EXPORT_SYMBOL_NS_GPL(iova_bitmap_for_each, "IOMMUFD");

@@ -411,8 +416,13 @@ void iova_bitmap_set(struct iova_bitmap *bitmap,
* may fail if it's out of bounds, or pinning the pages
* returns an error.
*/
- if (iova_bitmap_advance_to(bitmap, iova))
+ int ret = iova_bitmap_advance_to(bitmap, iova);
+
+ if (ret) {
+ if (!bitmap->err)
+ bitmap->err = ret;
return;
+ }
}

last_page_idx = mapped->npages - 1;