[PATCH v2 1/3] vfio/type1: sanitize for overflow using check_*_overflow

Alex Mastro posted 3 patches 4 months ago
There is a newer version of this series
[PATCH v2 1/3] vfio/type1: sanitize for overflow using check_*_overflow
Posted by Alex Mastro 4 months ago
Adopt check_*_overflow functions to clearly express overflow check
intent.

Signed-off-by: Alex Mastro <amastro@fb.com>
---
 drivers/vfio/vfio_iommu_type1.c | 54 ++++++++++++++++++++++++++++++++---------
 1 file changed, 43 insertions(+), 11 deletions(-)

diff --git a/drivers/vfio/vfio_iommu_type1.c b/drivers/vfio/vfio_iommu_type1.c
index f8d68fe77b41..b510ef3f397b 100644
--- a/drivers/vfio/vfio_iommu_type1.c
+++ b/drivers/vfio/vfio_iommu_type1.c
@@ -37,6 +37,7 @@
 #include <linux/vfio.h>
 #include <linux/workqueue.h>
 #include <linux/notifier.h>
+#include <linux/overflow.h>
 #include "vfio.h"
 
 #define DRIVER_VERSION  "0.2"
@@ -825,14 +826,25 @@ static int vfio_iommu_type1_pin_pages(void *iommu_data,
 	unsigned long remote_vaddr;
 	struct vfio_dma *dma;
 	bool do_accounting;
+	dma_addr_t iova_end;
+	size_t iova_size;
 
-	if (!iommu || !pages)
+	if (!iommu || !pages || npage < 0)
 		return -EINVAL;
 
 	/* Supported for v2 version only */
 	if (!iommu->v2)
 		return -EACCES;
 
+	if (npage == 0)
+		return 0;
+
+	if (check_mul_overflow(npage, PAGE_SIZE, &iova_size))
+		return -EINVAL;
+
+	if (check_add_overflow(user_iova, iova_size - 1, &iova_end))
+		return -EINVAL;
+
 	mutex_lock(&iommu->lock);
 
 	if (WARN_ONCE(iommu->vaddr_invalid_count,
@@ -938,12 +950,23 @@ static void vfio_iommu_type1_unpin_pages(void *iommu_data,
 {
 	struct vfio_iommu *iommu = iommu_data;
 	bool do_accounting;
+	dma_addr_t iova_end;
+	size_t iova_size;
 	int i;
 
 	/* Supported for v2 version only */
 	if (WARN_ON(!iommu->v2))
 		return;
 
+	if (WARN_ON(npage < 0) || npage == 0)
+		return;
+
+	if (WARN_ON(check_mul_overflow(npage, PAGE_SIZE, &iova_size)))
+		return;
+
+	if (WARN_ON(check_add_overflow(user_iova, iova_size - 1, &iova_end)))
+		return;
+
 	mutex_lock(&iommu->lock);
 
 	do_accounting = list_empty(&iommu->domain_list);
@@ -1304,6 +1327,7 @@ static int vfio_dma_do_unmap(struct vfio_iommu *iommu,
 	int ret = -EINVAL, retries = 0;
 	unsigned long pgshift;
 	dma_addr_t iova = unmap->iova;
+	dma_addr_t iova_end;
 	u64 size = unmap->size;
 	bool unmap_all = unmap->flags & VFIO_DMA_UNMAP_FLAG_ALL;
 	bool invalidate_vaddr = unmap->flags & VFIO_DMA_UNMAP_FLAG_VADDR;
@@ -1328,7 +1352,8 @@ static int vfio_dma_do_unmap(struct vfio_iommu *iommu,
 			goto unlock;
 		size = U64_MAX;
 	} else if (!size || size & (pgsize - 1) ||
-		   iova + size - 1 < iova || size > SIZE_MAX) {
+		   check_add_overflow(iova, size - 1, &iova_end) ||
+		   size > SIZE_MAX) {
 		goto unlock;
 	}
 
@@ -1376,7 +1401,7 @@ static int vfio_dma_do_unmap(struct vfio_iommu *iommu,
 		if (dma && dma->iova != iova)
 			goto unlock;
 
-		dma = vfio_find_dma(iommu, iova + size - 1, 0);
+		dma = vfio_find_dma(iommu, iova_end, 0);
 		if (dma && dma->iova + dma->size != iova + size)
 			goto unlock;
 	}
@@ -1578,7 +1603,9 @@ static int vfio_dma_do_map(struct vfio_iommu *iommu,
 {
 	bool set_vaddr = map->flags & VFIO_DMA_MAP_FLAG_VADDR;
 	dma_addr_t iova = map->iova;
+	dma_addr_t iova_end;
 	unsigned long vaddr = map->vaddr;
+	unsigned long vaddr_end;
 	size_t size = map->size;
 	int ret = 0, prot = 0;
 	size_t pgsize;
@@ -1588,6 +1615,12 @@ static int vfio_dma_do_map(struct vfio_iommu *iommu,
 	if (map->size != size || map->vaddr != vaddr || map->iova != iova)
 		return -EINVAL;
 
+	if (check_add_overflow(iova, size - 1, &iova_end))
+		return -EINVAL;
+
+	if (check_add_overflow(vaddr, size - 1, &vaddr_end))
+		return -EINVAL;
+
 	/* READ/WRITE from device perspective */
 	if (map->flags & VFIO_DMA_MAP_FLAG_WRITE)
 		prot |= IOMMU_WRITE;
@@ -1608,12 +1641,6 @@ static int vfio_dma_do_map(struct vfio_iommu *iommu,
 		goto out_unlock;
 	}
 
-	/* Don't allow IOVA or virtual address wrap */
-	if (iova + size - 1 < iova || vaddr + size - 1 < vaddr) {
-		ret = -EINVAL;
-		goto out_unlock;
-	}
-
 	dma = vfio_find_dma(iommu, iova, size);
 	if (set_vaddr) {
 		if (!dma) {
@@ -1640,7 +1667,7 @@ static int vfio_dma_do_map(struct vfio_iommu *iommu,
 		goto out_unlock;
 	}
 
-	if (!vfio_iommu_iova_dma_valid(iommu, iova, iova + size - 1)) {
+	if (!vfio_iommu_iova_dma_valid(iommu, iova, iova_end)) {
 		ret = -EINVAL;
 		goto out_unlock;
 	}
@@ -2908,6 +2935,7 @@ static int vfio_iommu_type1_dirty_pages(struct vfio_iommu *iommu,
 		unsigned long pgshift;
 		size_t data_size = dirty.argsz - minsz;
 		size_t iommu_pgsize;
+		dma_addr_t range_end;
 
 		if (!data_size || data_size < sizeof(range))
 			return -EINVAL;
@@ -2916,8 +2944,12 @@ static int vfio_iommu_type1_dirty_pages(struct vfio_iommu *iommu,
 				   sizeof(range)))
 			return -EFAULT;
 
-		if (range.iova + range.size < range.iova)
+		if (range.size == 0)
+			return 0;
+
+		if (check_add_overflow(range.iova, range.size - 1, &range_end))
 			return -EINVAL;
+
 		if (!access_ok((void __user *)range.bitmap.data,
 			       range.bitmap.size))
 			return -EINVAL;

-- 
2.47.3
Re: [PATCH v2 1/3] vfio/type1: sanitize for overflow using check_*_overflow
Posted by Jason Gunthorpe 4 months ago
On Tue, Oct 07, 2025 at 09:08:46PM -0700, Alex Mastro wrote:
> Adopt check_*_overflow functions to clearly express overflow check
> intent.
> 
> Signed-off-by: Alex Mastro <amastro@fb.com>
> ---
>  drivers/vfio/vfio_iommu_type1.c | 54 ++++++++++++++++++++++++++++++++---------
>  1 file changed, 43 insertions(+), 11 deletions(-)
> 
> diff --git a/drivers/vfio/vfio_iommu_type1.c b/drivers/vfio/vfio_iommu_type1.c
> index f8d68fe77b41..b510ef3f397b 100644
> --- a/drivers/vfio/vfio_iommu_type1.c
> +++ b/drivers/vfio/vfio_iommu_type1.c
> @@ -37,6 +37,7 @@
>  #include <linux/vfio.h>
>  #include <linux/workqueue.h>
>  #include <linux/notifier.h>
> +#include <linux/overflow.h>
>  #include "vfio.h"
>  
>  #define DRIVER_VERSION  "0.2"
> @@ -825,14 +826,25 @@ static int vfio_iommu_type1_pin_pages(void *iommu_data,
>  	unsigned long remote_vaddr;
>  	struct vfio_dma *dma;
>  	bool do_accounting;
> +	dma_addr_t iova_end;
> +	size_t iova_size;
>  
> -	if (!iommu || !pages)
> +	if (!iommu || !pages || npage < 0)
>  		return -EINVAL;
>  
>  	/* Supported for v2 version only */
>  	if (!iommu->v2)
>  		return -EACCES;
>  
> +	if (npage == 0)
> +		return 0;
> +
> +	if (check_mul_overflow(npage, PAGE_SIZE, &iova_size))
> +		return -EINVAL;

-EOVERFLOW and everywhere else

> +
> +	if (check_add_overflow(user_iova, iova_size - 1, &iova_end))
> +		return -EINVAL;

Let's be consistent with iommufd/etc, 'end' is start+size 'last' is start+size-1

Otherwise it is super confusing :(

Jason
Re: [PATCH v2 1/3] vfio/type1: sanitize for overflow using check_*_overflow
Posted by Alex Mastro 4 months ago
On Wed, Oct 08, 2025 at 09:19:30AM -0300, Jason Gunthorpe wrote:
> On Tue, Oct 07, 2025 at 09:08:46PM -0700, Alex Mastro wrote:
> > +	if (check_mul_overflow(npage, PAGE_SIZE, &iova_size))
> > +		return -EINVAL;
> 
> -EOVERFLOW and everywhere else
> 
> > +
> > +	if (check_add_overflow(user_iova, iova_size - 1, &iova_end))
> > +		return -EINVAL;
> 
> Let's be consistent with iommufd/etc, 'end' is start+size 'last' is start+size-1
> 
> Otherwise it is super confusing :(


Both suggestions SGTM.
Re: [PATCH v2 1/3] vfio/type1: sanitize for overflow using check_*_overflow
Posted by Alex Mastro 4 months ago
On Wed, Oct 08, 2025 at 08:39:21AM -0700, Alex Mastro wrote:
> On Wed, Oct 08, 2025 at 09:19:30AM -0300, Jason Gunthorpe wrote:
> > On Tue, Oct 07, 2025 at 09:08:46PM -0700, Alex Mastro wrote:
> > > +	if (check_add_overflow(user_iova, iova_size - 1, &iova_end))
> > > +		return -EINVAL;
> > 
> > Let's be consistent with iommufd/etc, 'end' is start+size 'last' is start+size-1
> > 
> > Otherwise it is super confusing :(
> 
> 
> Both suggestions SGTM.

I'm not sure about the latter anymore. There's somewhat pervasive precedent for
using 'end' as the inclusive limit in vfio_iommu_type1.c. I am all for making
things less confusing. I don't think I can introduce 'end' 'last' convention
without preparing the existing code first.

Thoughts? Spend another commit renaming this to 'last'? Tolerate inconsistency
between vfio and iommufd?

116  struct vfio_iova {
117          struct list_head        list;
118          dma_addr_t              start;
119          dma_addr_t              end;
120  };
...
2037                  end = resv->start + resv->length - 1;
2038
2039                  list_for_each_entry_safe(n, next, iova, list) {
2040                          int ret = 0;
2041
2042                          /* No overlap */
2043                          if (start > n->end || end < n->start)
2044                                  continue;
...
2052                          if (start > n->start)
2053                                  ret = vfio_iommu_iova_insert(&n->list, n->start,
2054                                                               start - 1);
2055                          if (!ret && end < n->end)
2056                                  ret = vfio_iommu_iova_insert(&n->list, end + 1,
2057                                                               n->end);
Re: [PATCH v2 1/3] vfio/type1: sanitize for overflow using check_*_overflow
Posted by Jason Gunthorpe 4 months ago
On Wed, Oct 08, 2025 at 03:19:07PM -0700, Alex Mastro wrote:
> On Wed, Oct 08, 2025 at 08:39:21AM -0700, Alex Mastro wrote:
> > On Wed, Oct 08, 2025 at 09:19:30AM -0300, Jason Gunthorpe wrote:
> > > On Tue, Oct 07, 2025 at 09:08:46PM -0700, Alex Mastro wrote:
> > > > +	if (check_add_overflow(user_iova, iova_size - 1, &iova_end))
> > > > +		return -EINVAL;
> > > 
> > > Let's be consistent with iommufd/etc, 'end' is start+size 'last' is start+size-1
> > > 
> > > Otherwise it is super confusing :(
> > 
> > 
> > Both suggestions SGTM.
> 
> I'm not sure about the latter anymore. There's somewhat pervasive precedent for
> using 'end' as the inclusive limit in vfio_iommu_type1.c. I am all for making
> things less confusing. I don't think I can introduce 'end' 'last' convention
> without preparing the existing code first.
> 
> Thoughts? Spend another commit renaming this to 'last'? Tolerate inconsistency
> between vfio and iommufd?

IDK, if it is actually internally consistent and not using end
interchangably then it is probably Ok to keep doing it. If it is
already inconsistent then use last for new code and leave the old as
is?

Jason
Re: [PATCH v2 1/3] vfio/type1: sanitize for overflow using check_*_overflow
Posted by Alex Mastro 4 months ago
On Wed, Oct 08, 2025 at 10:15:35PM -0300, Jason Gunthorpe wrote:
> On Wed, Oct 08, 2025 at 03:19:07PM -0700, Alex Mastro wrote:
> > On Wed, Oct 08, 2025 at 08:39:21AM -0700, Alex Mastro wrote:
> > > On Wed, Oct 08, 2025 at 09:19:30AM -0300, Jason Gunthorpe wrote:
> > > > On Tue, Oct 07, 2025 at 09:08:46PM -0700, Alex Mastro wrote:
> > > > > +	if (check_add_overflow(user_iova, iova_size - 1, &iova_end))
> > > > > +		return -EINVAL;
> > > > 
> > > > Let's be consistent with iommufd/etc, 'end' is start+size 'last' is start+size-1
> > > > 
> > > > Otherwise it is super confusing :(
> > > 
> > > 
> > > Both suggestions SGTM.
> > 
> > I'm not sure about the latter anymore. There's somewhat pervasive precedent for
> > using 'end' as the inclusive limit in vfio_iommu_type1.c. I am all for making
> > things less confusing. I don't think I can introduce 'end' 'last' convention
> > without preparing the existing code first.
> > 
> > Thoughts? Spend another commit renaming this to 'last'? Tolerate inconsistency
> > between vfio and iommufd?
> 
> IDK, if it is actually internally consistent and not using end
> interchangably then it is probably Ok to keep doing it. If it is
> already inconsistent then use last for new code and leave the old as
> is?

The only references to 'last' are for elements in a list, tree, unrelated to
iova, and 'end' refers to (iova+size-1) for all cases that I saw.

For the sake of internal consistency, I'll keep 'end', and after this series,
if it's worth it, we can take a pass to unify the terminology between iommufd
and vfio.