Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

iommu/vt-d: Fix __domain_mapping()'s usage of switch_to_super_page()

switch_to_super_page() assumes the memory range it's working on is aligned
to the target large page level. Unfortunately, __domain_mapping() doesn't
take this into account when using it, and will pass unaligned ranges
ultimately freeing a PTE range larger than expected.

Take for example a mapping with the following iov_pfn range [0x3fe400,
0x4c0600), which should be backed by the following mappings:

iov_pfn [0x3fe400, 0x3fffff] covered by 2MiB pages
iov_pfn [0x400000, 0x4bffff] covered by 1GiB pages
iov_pfn [0x4c0000, 0x4c05ff] covered by 2MiB pages

Under this circumstance, __domain_mapping() will pass [0x400000, 0x4c05ff]
to switch_to_super_page() at a 1 GiB granularity, which will in turn
free PTEs all the way to iov_pfn 0x4fffff.

Mitigate this by rounding down the iov_pfn range passed to
switch_to_super_page() in __domain_mapping()
to the target large page level.

Additionally add range alignment checks to switch_to_super_page.

Fixes: 9906b9352a35 ("iommu/vt-d: Avoid duplicate removing in __domain_mapping()")
Signed-off-by: Eugene Koira <eugkoira@amazon.com>
Cc: stable@vger.kernel.org
Reviewed-by: Nicolas Saenz Julienne <nsaenz@amazon.com>
Reviewed-by: David Woodhouse <dwmw@amazon.co.uk>
Link: https://lore.kernel.org/r/20250826143816.38686-1-eugkoira@amazon.com
Signed-off-by: Lu Baolu <baolu.lu@linux.intel.com>
Signed-off-by: Joerg Roedel <joerg.roedel@amd.com>

authored by

Eugene Koira and committed by
Joerg Roedel
dce043c0 b3506e9b

+6 -1
+6 -1
drivers/iommu/intel/iommu.c
··· 1575 1575 unsigned long lvl_pages = lvl_to_nr_pages(level); 1576 1576 struct dma_pte *pte = NULL; 1577 1577 1578 + if (WARN_ON(!IS_ALIGNED(start_pfn, lvl_pages) || 1579 + !IS_ALIGNED(end_pfn + 1, lvl_pages))) 1580 + return; 1581 + 1578 1582 while (start_pfn <= end_pfn) { 1579 1583 if (!pte) 1580 1584 pte = pfn_to_dma_pte(domain, start_pfn, &level, ··· 1654 1650 unsigned long pages_to_remove; 1655 1651 1656 1652 pteval |= DMA_PTE_LARGE_PAGE; 1657 - pages_to_remove = min_t(unsigned long, nr_pages, 1653 + pages_to_remove = min_t(unsigned long, 1654 + round_down(nr_pages, lvl_pages), 1658 1655 nr_pte_to_next_page(pte) * lvl_pages); 1659 1656 end_pfn = iov_pfn + pages_to_remove - 1; 1660 1657 switch_to_super_page(domain, iov_pfn, end_pfn, largepage_lvl);