[PATCH] mm/migrate_device: avoid out-of-bounds writes for compound folios

Hui Su <[email protected]>
Newsgroups gmane.linux.kernel,gmane.linux.kernel.mm
Message-ID <[email protected]>
migrate_device_range() and migrate_device_pfns() clear the entries
following a compound folio so that the PFN arrays retain their
page-granular representation.

If a compound folio extends beyond the end of the caller-provided range,
the loops clear all following folio entries without limiting them to the
number of slots remaining in the npages-sized array, causing an
out-of-bounds write.

Limit the number of entries updated and consumed to the remaining array
slots while keeping the actual number of pages in the folio unchanged.

Observed with a KASAN x86 QEMU kernel using the HMM
migrate_anon_huge_zero selftest. Closing /dev/hmm_dmirror0 after
migrating an anonymous huge page to device memory exercises:

  dmirror_fops_release()
    -> dmirror_device_evict_chunk()
      -> migrate_device_range()

Fixes: a30b48bf1b24 ("mm/migrate_device: implement THP migration of zone device pages")
Signed-off-by: Hui Su <[email protected]>
---
 mm/migrate_device.c | 16 +++++++++-------
 1 file changed, 9 insertions(+), 7 deletions(-)

diff --git a/mm/migrate_device.c b/mm/migrate_device.c
index 908d2d4ec43a..67bfc1af0b29 100644
--- a/mm/migrate_device.c
+++ b/mm/migrate_device.c
@@ -1396,16 +1396,17 @@ int migrate_device_range(unsigned long *src_pfns, unsigned long start,
 	for (pfn = start, i = 0; i < npages; pfn++, i++) {
 		struct page *page = pfn_to_page(pfn);
 		struct folio *folio = page_folio(page);
-		unsigned int nr = 1;
+		unsigned long nr, slots;
 
 		src_pfns[i] = migrate_device_pfn_lock(pfn);
 		nr = folio_nr_pages(folio);
+		slots = min(nr, npages - i);
 		if (nr > 1) {
 			src_pfns[i] |= MIGRATE_PFN_COMPOUND;
-			for (j = 1; j < nr; j++)
+			for (j = 1; j < slots; j++)
 				src_pfns[i+j] = 0;
-			i += j - 1;
-			pfn += j - 1;
+			i += slots - 1;
+			pfn += slots - 1;
 		}
 	}
 
@@ -1430,15 +1431,16 @@ int migrate_device_pfns(unsigned long *src_pfns, unsigned long npages)
 	for (i = 0; i < npages; i++) {
 		struct page *page = pfn_to_page(src_pfns[i]);
 		struct folio *folio = page_folio(page);
-		unsigned int nr = 1;
+		unsigned long nr, slots;
 
 		src_pfns[i] = migrate_device_pfn_lock(src_pfns[i]);
 		nr = folio_nr_pages(folio);
+		slots = min(nr, npages - i);
 		if (nr > 1) {
 			src_pfns[i] |= MIGRATE_PFN_COMPOUND;
-			for (j = 1; j < nr; j++)
+			for (j = 1; j < slots; j++)
 				src_pfns[i+j] = 0;
-			i += j - 1;
+			i += slots - 1;
 		}
 	}
 
-- 
2.54.0
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.