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: stable@vger.kernel.org Fixes: 51fe6141f0f6 ("iommufd: Data structure to provide IOVA to PFN mapping") Reported-by: syzbot+c2f65e2801743ca64e08@syzkaller.appspotmail.com Closes: https://lore.kernel.org/r/685af644.a00a0220.2e5631.0094.GAE@google.com Signed-off-by: Jason Gunthorpe jgg@nvidia.com
drivers/iommu/iommufd/io_pagetable.c | 41 +++++++++++++++++----------- 1 file changed, 25 insertions(+), 16 deletions(-)
Reviewed-by: Yi Liu yi.l.liu@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);
/*