On 2025/7/18 03:15, Jason Gunthorpe wrote:
When allocating IOVA the candidate range gets aligned to the target
alignment. If the range is close to ULONG_MAX then the ALIGN() can
wrap resulting in a corrupted iova.

Open code the ALIGN() using get_add_overflow() to prevent this.
This simplifies the checks as we don't need to check for length earlier
either.

Consolidate the two copies of this code under a single helper.

This bug would allow userspace to create a mapping that overlaps with some
other mapping or a reserved range.

Cc: sta...@vger.kernel.org
Fixes: 51fe6141f0f6 ("iommufd: Data structure to provide IOVA to PFN mapping")
Reported-by: syzbot+c2f65e2801743ca64...@syzkaller.appspotmail.com
Closes: https://lore.kernel.org/r/685af644.a00a0220.2e5631.0094....@google.com
Signed-off-by: Jason Gunthorpe <j...@nvidia.com>
---
  drivers/iommu/iommufd/io_pagetable.c | 41 +++++++++++++++++-----------
  1 file changed, 25 insertions(+), 16 deletions(-)

Reviewed-by: Yi Liu <yi.l....@intel.com>

diff --git a/drivers/iommu/iommufd/io_pagetable.c 
b/drivers/iommu/iommufd/io_pagetable.c
index abf4aadca96c0b..c0360c450880b8 100644
--- a/drivers/iommu/iommufd/io_pagetable.c
+++ b/drivers/iommu/iommufd/io_pagetable.c
@@ -70,20 +70,34 @@ struct iopt_area *iopt_area_contig_next(struct 
iopt_area_contig_iter *iter)
        return iter->area;
  }
+static bool __alloc_iova_check_range(unsigned long *start, unsigned long last,
+                                    unsigned long length,
+                                    unsigned long iova_alignment,
+                                    unsigned long page_offset)
+{
+       unsigned long aligned_start;
+
+       /* ALIGN_UP() */
+       if (check_add_overflow(*start, iova_alignment - 1, &aligned_start))
+               return false;
+       aligned_start &= ~(iova_alignment - 1);
+       aligned_start |= page_offset;
+
+       if (aligned_start >= last || last - aligned_start < length - 1)
+               return false;
+       *start = aligned_start;
+       return true;
+}
+
  static bool __alloc_iova_check_hole(struct interval_tree_double_span_iter 
*span,
                                    unsigned long length,
                                    unsigned long iova_alignment,
                                    unsigned long page_offset)
  {
-       if (span->is_used || span->last_hole - span->start_hole < length - 1)
+       if (span->is_used)
                return false;
-
-       span->start_hole = ALIGN(span->start_hole, iova_alignment) |
-                          page_offset;
-       if (span->start_hole > span->last_hole ||
-           span->last_hole - span->start_hole < length - 1)
-               return false;
-       return true;
+       return __alloc_iova_check_range(&span->start_hole, span->last_hole,
+                                       length, iova_alignment, page_offset);
  }
static bool __alloc_iova_check_used(struct interval_tree_span_iter *span,
@@ -91,15 +105,10 @@ static bool __alloc_iova_check_used(struct 
interval_tree_span_iter *span,
                                    unsigned long iova_alignment,
                                    unsigned long page_offset)
  {
-       if (span->is_hole || span->last_used - span->start_used < length - 1)
+       if (span->is_hole)
                return false;
-
-       span->start_used = ALIGN(span->start_used, iova_alignment) |
-                          page_offset;
-       if (span->start_used > span->last_used ||
-           span->last_used - span->start_used < length - 1)
-               return false;
-       return true;
+       return __alloc_iova_check_range(&span->start_used, span->last_used,
+                                       length, iova_alignment, page_offset);
  }
/*

--
Regards,
Yi Liu

Reply via email to