VYPR
Unrated severityNVD Advisory· Published Jun 8, 2026

CVE-2026-46280

CVE-2026-46280

Description

Linux kernel vulnerability allows use-after-free in HMM selftests, potentially leading to kernel panic during coredump.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Linux kernel vulnerability allows use-after-free in HMM selftests, potentially leading to kernel panic during coredump.

Vulnerability

The Linux kernel's HMM (Hardware Memory Management) selftests contain a use-after-free vulnerability. When the dmirror_fops_release() function is called, it frees the dmirror struct without migrating device private pages back to system memory first. This leaves these pages with a dangling pointer to the freed dmirror struct. Affected versions are not explicitly stated, but the fix is present in the "Minor hmm_test fixes and cleanups" patch series.

Exploitation

An attacker could trigger this vulnerability if a subsequent page fault occurs on these device private pages, for example, during a coredump. This fault would cause the dmirror_devmem_fault() callback to dereference the stale pointer, leading to a kernel panic. The vulnerability was observed when running mm/ksft_hmm.sh on arm64, where a test failure triggered a SIGABRT and the resulting coredump caused the fault.

Impact

Successful exploitation of this vulnerability results in a kernel panic, causing a denial of service. The panic occurs when the system attempts to handle a coredump and dereferences a stale pointer to freed memory, leading to instability and system crash.

Mitigation

The vulnerability is fixed by calling dmirror_device_evict_chunk() for each devmem chunk in dmirror_fops_release() before freeing the dmirror struct. This ensures all device private pages are migrated back to system memory. The specific fixed version and release date are not provided in the available references, but the patch is part of the "Minor hmm_test fixes and cleanups" series [1].

AI Insight generated on Jun 8, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected products

1

Patches

10
bf477abd448c

lib: test_hmm: evict device pages on file close to avoid use-after-free

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitAlistair PoppleApr 28, 2026Fixed in 6.6.140via kernel-cna
1 file changed · +49 38
  • lib/test_hmm.c+49 38 modified
    diff --git a/lib/test_hmm.c b/lib/test_hmm.c
    index b823ba7cb6a15..cb50065e37a34 100644
    --- a/lib/test_hmm.c
    +++ b/lib/test_hmm.c
    @@ -183,11 +183,60 @@ static int dmirror_fops_open(struct inode *inode, struct file *filp)
     	return 0;
     }
     
    +static void dmirror_device_evict_chunk(struct dmirror_chunk *chunk)
    +{
    +	unsigned long start_pfn = chunk->pagemap.range.start >> PAGE_SHIFT;
    +	unsigned long end_pfn = chunk->pagemap.range.end >> PAGE_SHIFT;
    +	unsigned long npages = end_pfn - start_pfn + 1;
    +	unsigned long i;
    +	unsigned long *src_pfns;
    +	unsigned long *dst_pfns;
    +
    +	src_pfns = kvcalloc(npages, sizeof(*src_pfns), GFP_KERNEL | __GFP_NOFAIL);
    +	dst_pfns = kvcalloc(npages, sizeof(*dst_pfns), GFP_KERNEL | __GFP_NOFAIL);
    +
    +	migrate_device_range(src_pfns, start_pfn, npages);
    +	for (i = 0; i < npages; i++) {
    +		struct page *dpage, *spage;
    +
    +		spage = migrate_pfn_to_page(src_pfns[i]);
    +		if (!spage || !(src_pfns[i] & MIGRATE_PFN_MIGRATE))
    +			continue;
    +
    +		if (WARN_ON(!is_device_private_page(spage) &&
    +			    !is_device_coherent_page(spage)))
    +			continue;
    +		spage = BACKING_PAGE(spage);
    +		dpage = alloc_page(GFP_HIGHUSER_MOVABLE | __GFP_NOFAIL);
    +		lock_page(dpage);
    +		copy_highpage(dpage, spage);
    +		dst_pfns[i] = migrate_pfn(page_to_pfn(dpage));
    +		if (src_pfns[i] & MIGRATE_PFN_WRITE)
    +			dst_pfns[i] |= MIGRATE_PFN_WRITE;
    +	}
    +	migrate_device_pages(src_pfns, dst_pfns, npages);
    +	migrate_device_finalize(src_pfns, dst_pfns, npages);
    +	kvfree(src_pfns);
    +	kvfree(dst_pfns);
    +}
    +
     static int dmirror_fops_release(struct inode *inode, struct file *filp)
     {
     	struct dmirror *dmirror = filp->private_data;
    +	struct dmirror_device *mdevice = dmirror->mdevice;
    +	int i;
     
     	mmu_interval_notifier_remove(&dmirror->notifier);
    +
    +	if (mdevice->devmem_chunks) {
    +		for (i = 0; i < mdevice->devmem_count; i++) {
    +			struct dmirror_chunk *devmem =
    +				mdevice->devmem_chunks[i];
    +
    +			dmirror_device_evict_chunk(devmem);
    +		}
    +	}
    +
     	xa_destroy(&dmirror->pt);
     	kfree(dmirror);
     	return 0;
    @@ -1217,43 +1266,6 @@ static int dmirror_snapshot(struct dmirror *dmirror,
     	return ret;
     }
     
    -static void dmirror_device_evict_chunk(struct dmirror_chunk *chunk)
    -{
    -	unsigned long start_pfn = chunk->pagemap.range.start >> PAGE_SHIFT;
    -	unsigned long end_pfn = chunk->pagemap.range.end >> PAGE_SHIFT;
    -	unsigned long npages = end_pfn - start_pfn + 1;
    -	unsigned long i;
    -	unsigned long *src_pfns;
    -	unsigned long *dst_pfns;
    -
    -	src_pfns = kvcalloc(npages, sizeof(*src_pfns), GFP_KERNEL | __GFP_NOFAIL);
    -	dst_pfns = kvcalloc(npages, sizeof(*dst_pfns), GFP_KERNEL | __GFP_NOFAIL);
    -
    -	migrate_device_range(src_pfns, start_pfn, npages);
    -	for (i = 0; i < npages; i++) {
    -		struct page *dpage, *spage;
    -
    -		spage = migrate_pfn_to_page(src_pfns[i]);
    -		if (!spage || !(src_pfns[i] & MIGRATE_PFN_MIGRATE))
    -			continue;
    -
    -		if (WARN_ON(!is_device_private_page(spage) &&
    -			    !is_device_coherent_page(spage)))
    -			continue;
    -		spage = BACKING_PAGE(spage);
    -		dpage = alloc_page(GFP_HIGHUSER_MOVABLE | __GFP_NOFAIL);
    -		lock_page(dpage);
    -		copy_highpage(dpage, spage);
    -		dst_pfns[i] = migrate_pfn(page_to_pfn(dpage));
    -		if (src_pfns[i] & MIGRATE_PFN_WRITE)
    -			dst_pfns[i] |= MIGRATE_PFN_WRITE;
    -	}
    -	migrate_device_pages(src_pfns, dst_pfns, npages);
    -	migrate_device_finalize(src_pfns, dst_pfns, npages);
    -	kvfree(src_pfns);
    -	kvfree(dst_pfns);
    -}
    -
     /* Removes free pages from the free list so they can't be re-allocated */
     static void dmirror_remove_free_pages(struct dmirror_chunk *devmem)
     {
    -- 
    cgit 1.3-korg
    
    
    
5846715b6382

lib: test_hmm: evict device pages on file close to avoid use-after-free

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitAlistair PoppleApr 28, 2026Fixed in 6.12.86via kernel-cna
1 file changed · +49 38
  • lib/test_hmm.c+49 38 modified
    diff --git a/lib/test_hmm.c b/lib/test_hmm.c
    index 056f2e411d7b4..850d23469ef78 100644
    --- a/lib/test_hmm.c
    +++ b/lib/test_hmm.c
    @@ -183,11 +183,60 @@ static int dmirror_fops_open(struct inode *inode, struct file *filp)
     	return 0;
     }
     
    +static void dmirror_device_evict_chunk(struct dmirror_chunk *chunk)
    +{
    +	unsigned long start_pfn = chunk->pagemap.range.start >> PAGE_SHIFT;
    +	unsigned long end_pfn = chunk->pagemap.range.end >> PAGE_SHIFT;
    +	unsigned long npages = end_pfn - start_pfn + 1;
    +	unsigned long i;
    +	unsigned long *src_pfns;
    +	unsigned long *dst_pfns;
    +
    +	src_pfns = kvcalloc(npages, sizeof(*src_pfns), GFP_KERNEL | __GFP_NOFAIL);
    +	dst_pfns = kvcalloc(npages, sizeof(*dst_pfns), GFP_KERNEL | __GFP_NOFAIL);
    +
    +	migrate_device_range(src_pfns, start_pfn, npages);
    +	for (i = 0; i < npages; i++) {
    +		struct page *dpage, *spage;
    +
    +		spage = migrate_pfn_to_page(src_pfns[i]);
    +		if (!spage || !(src_pfns[i] & MIGRATE_PFN_MIGRATE))
    +			continue;
    +
    +		if (WARN_ON(!is_device_private_page(spage) &&
    +			    !is_device_coherent_page(spage)))
    +			continue;
    +		spage = BACKING_PAGE(spage);
    +		dpage = alloc_page(GFP_HIGHUSER_MOVABLE | __GFP_NOFAIL);
    +		lock_page(dpage);
    +		copy_highpage(dpage, spage);
    +		dst_pfns[i] = migrate_pfn(page_to_pfn(dpage));
    +		if (src_pfns[i] & MIGRATE_PFN_WRITE)
    +			dst_pfns[i] |= MIGRATE_PFN_WRITE;
    +	}
    +	migrate_device_pages(src_pfns, dst_pfns, npages);
    +	migrate_device_finalize(src_pfns, dst_pfns, npages);
    +	kvfree(src_pfns);
    +	kvfree(dst_pfns);
    +}
    +
     static int dmirror_fops_release(struct inode *inode, struct file *filp)
     {
     	struct dmirror *dmirror = filp->private_data;
    +	struct dmirror_device *mdevice = dmirror->mdevice;
    +	int i;
     
     	mmu_interval_notifier_remove(&dmirror->notifier);
    +
    +	if (mdevice->devmem_chunks) {
    +		for (i = 0; i < mdevice->devmem_count; i++) {
    +			struct dmirror_chunk *devmem =
    +				mdevice->devmem_chunks[i];
    +
    +			dmirror_device_evict_chunk(devmem);
    +		}
    +	}
    +
     	xa_destroy(&dmirror->pt);
     	kfree(dmirror);
     	return 0;
    @@ -1214,43 +1263,6 @@ static int dmirror_snapshot(struct dmirror *dmirror,
     	return ret;
     }
     
    -static void dmirror_device_evict_chunk(struct dmirror_chunk *chunk)
    -{
    -	unsigned long start_pfn = chunk->pagemap.range.start >> PAGE_SHIFT;
    -	unsigned long end_pfn = chunk->pagemap.range.end >> PAGE_SHIFT;
    -	unsigned long npages = end_pfn - start_pfn + 1;
    -	unsigned long i;
    -	unsigned long *src_pfns;
    -	unsigned long *dst_pfns;
    -
    -	src_pfns = kvcalloc(npages, sizeof(*src_pfns), GFP_KERNEL | __GFP_NOFAIL);
    -	dst_pfns = kvcalloc(npages, sizeof(*dst_pfns), GFP_KERNEL | __GFP_NOFAIL);
    -
    -	migrate_device_range(src_pfns, start_pfn, npages);
    -	for (i = 0; i < npages; i++) {
    -		struct page *dpage, *spage;
    -
    -		spage = migrate_pfn_to_page(src_pfns[i]);
    -		if (!spage || !(src_pfns[i] & MIGRATE_PFN_MIGRATE))
    -			continue;
    -
    -		if (WARN_ON(!is_device_private_page(spage) &&
    -			    !is_device_coherent_page(spage)))
    -			continue;
    -		spage = BACKING_PAGE(spage);
    -		dpage = alloc_page(GFP_HIGHUSER_MOVABLE | __GFP_NOFAIL);
    -		lock_page(dpage);
    -		copy_highpage(dpage, spage);
    -		dst_pfns[i] = migrate_pfn(page_to_pfn(dpage));
    -		if (src_pfns[i] & MIGRATE_PFN_WRITE)
    -			dst_pfns[i] |= MIGRATE_PFN_WRITE;
    -	}
    -	migrate_device_pages(src_pfns, dst_pfns, npages);
    -	migrate_device_finalize(src_pfns, dst_pfns, npages);
    -	kvfree(src_pfns);
    -	kvfree(dst_pfns);
    -}
    -
     /* Removes free pages from the free list so they can't be re-allocated */
     static void dmirror_remove_free_pages(struct dmirror_chunk *devmem)
     {
    -- 
    cgit 1.3-korg
    
    
    
38f113f81d3f

lib: test_hmm: evict device pages on file close to avoid use-after-free

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitAlistair PoppleApr 28, 2026Fixed in 6.18.27via kernel-cna
1 file changed · +49 38
  • lib/test_hmm.c+49 38 modified
    diff --git a/lib/test_hmm.c b/lib/test_hmm.c
    index 83e3d8208a540..00d34a6c6276b 100644
    --- a/lib/test_hmm.c
    +++ b/lib/test_hmm.c
    @@ -183,11 +183,60 @@ static int dmirror_fops_open(struct inode *inode, struct file *filp)
     	return 0;
     }
     
    +static void dmirror_device_evict_chunk(struct dmirror_chunk *chunk)
    +{
    +	unsigned long start_pfn = chunk->pagemap.range.start >> PAGE_SHIFT;
    +	unsigned long end_pfn = chunk->pagemap.range.end >> PAGE_SHIFT;
    +	unsigned long npages = end_pfn - start_pfn + 1;
    +	unsigned long i;
    +	unsigned long *src_pfns;
    +	unsigned long *dst_pfns;
    +
    +	src_pfns = kvcalloc(npages, sizeof(*src_pfns), GFP_KERNEL | __GFP_NOFAIL);
    +	dst_pfns = kvcalloc(npages, sizeof(*dst_pfns), GFP_KERNEL | __GFP_NOFAIL);
    +
    +	migrate_device_range(src_pfns, start_pfn, npages);
    +	for (i = 0; i < npages; i++) {
    +		struct page *dpage, *spage;
    +
    +		spage = migrate_pfn_to_page(src_pfns[i]);
    +		if (!spage || !(src_pfns[i] & MIGRATE_PFN_MIGRATE))
    +			continue;
    +
    +		if (WARN_ON(!is_device_private_page(spage) &&
    +			    !is_device_coherent_page(spage)))
    +			continue;
    +		spage = BACKING_PAGE(spage);
    +		dpage = alloc_page(GFP_HIGHUSER_MOVABLE | __GFP_NOFAIL);
    +		lock_page(dpage);
    +		copy_highpage(dpage, spage);
    +		dst_pfns[i] = migrate_pfn(page_to_pfn(dpage));
    +		if (src_pfns[i] & MIGRATE_PFN_WRITE)
    +			dst_pfns[i] |= MIGRATE_PFN_WRITE;
    +	}
    +	migrate_device_pages(src_pfns, dst_pfns, npages);
    +	migrate_device_finalize(src_pfns, dst_pfns, npages);
    +	kvfree(src_pfns);
    +	kvfree(dst_pfns);
    +}
    +
     static int dmirror_fops_release(struct inode *inode, struct file *filp)
     {
     	struct dmirror *dmirror = filp->private_data;
    +	struct dmirror_device *mdevice = dmirror->mdevice;
    +	int i;
     
     	mmu_interval_notifier_remove(&dmirror->notifier);
    +
    +	if (mdevice->devmem_chunks) {
    +		for (i = 0; i < mdevice->devmem_count; i++) {
    +			struct dmirror_chunk *devmem =
    +				mdevice->devmem_chunks[i];
    +
    +			dmirror_device_evict_chunk(devmem);
    +		}
    +	}
    +
     	xa_destroy(&dmirror->pt);
     	kfree(dmirror);
     	return 0;
    @@ -1192,43 +1241,6 @@ static int dmirror_snapshot(struct dmirror *dmirror,
     	return ret;
     }
     
    -static void dmirror_device_evict_chunk(struct dmirror_chunk *chunk)
    -{
    -	unsigned long start_pfn = chunk->pagemap.range.start >> PAGE_SHIFT;
    -	unsigned long end_pfn = chunk->pagemap.range.end >> PAGE_SHIFT;
    -	unsigned long npages = end_pfn - start_pfn + 1;
    -	unsigned long i;
    -	unsigned long *src_pfns;
    -	unsigned long *dst_pfns;
    -
    -	src_pfns = kvcalloc(npages, sizeof(*src_pfns), GFP_KERNEL | __GFP_NOFAIL);
    -	dst_pfns = kvcalloc(npages, sizeof(*dst_pfns), GFP_KERNEL | __GFP_NOFAIL);
    -
    -	migrate_device_range(src_pfns, start_pfn, npages);
    -	for (i = 0; i < npages; i++) {
    -		struct page *dpage, *spage;
    -
    -		spage = migrate_pfn_to_page(src_pfns[i]);
    -		if (!spage || !(src_pfns[i] & MIGRATE_PFN_MIGRATE))
    -			continue;
    -
    -		if (WARN_ON(!is_device_private_page(spage) &&
    -			    !is_device_coherent_page(spage)))
    -			continue;
    -		spage = BACKING_PAGE(spage);
    -		dpage = alloc_page(GFP_HIGHUSER_MOVABLE | __GFP_NOFAIL);
    -		lock_page(dpage);
    -		copy_highpage(dpage, spage);
    -		dst_pfns[i] = migrate_pfn(page_to_pfn(dpage));
    -		if (src_pfns[i] & MIGRATE_PFN_WRITE)
    -			dst_pfns[i] |= MIGRATE_PFN_WRITE;
    -	}
    -	migrate_device_pages(src_pfns, dst_pfns, npages);
    -	migrate_device_finalize(src_pfns, dst_pfns, npages);
    -	kvfree(src_pfns);
    -	kvfree(dst_pfns);
    -}
    -
     /* Removes free pages from the free list so they can't be re-allocated */
     static void dmirror_remove_free_pages(struct dmirror_chunk *devmem)
     {
    -- 
    cgit 1.3-korg
    
    
    
744dd97752ef

lib: test_hmm: evict device pages on file close to avoid use-after-free

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitAlistair PoppleMar 31, 2026Fixed in 7.1-rc1via kernel-cna
1 file changed · +62 51
  • lib/test_hmm.c+62 51 modified
    diff --git a/lib/test_hmm.c b/lib/test_hmm.c
    index 0964d53365e61..79fe7d233df1e 100644
    --- a/lib/test_hmm.c
    +++ b/lib/test_hmm.c
    @@ -185,11 +185,73 @@ static int dmirror_fops_open(struct inode *inode, struct file *filp)
     	return 0;
     }
     
    +static void dmirror_device_evict_chunk(struct dmirror_chunk *chunk)
    +{
    +	unsigned long start_pfn = chunk->pagemap.range.start >> PAGE_SHIFT;
    +	unsigned long end_pfn = chunk->pagemap.range.end >> PAGE_SHIFT;
    +	unsigned long npages = end_pfn - start_pfn + 1;
    +	unsigned long i;
    +	unsigned long *src_pfns;
    +	unsigned long *dst_pfns;
    +	unsigned int order = 0;
    +
    +	src_pfns = kvcalloc(npages, sizeof(*src_pfns), GFP_KERNEL | __GFP_NOFAIL);
    +	dst_pfns = kvcalloc(npages, sizeof(*dst_pfns), GFP_KERNEL | __GFP_NOFAIL);
    +
    +	migrate_device_range(src_pfns, start_pfn, npages);
    +	for (i = 0; i < npages; i++) {
    +		struct page *dpage, *spage;
    +
    +		spage = migrate_pfn_to_page(src_pfns[i]);
    +		if (!spage || !(src_pfns[i] & MIGRATE_PFN_MIGRATE))
    +			continue;
    +
    +		if (WARN_ON(!is_device_private_page(spage) &&
    +			    !is_device_coherent_page(spage)))
    +			continue;
    +
    +		order = folio_order(page_folio(spage));
    +		spage = BACKING_PAGE(spage);
    +		if (src_pfns[i] & MIGRATE_PFN_COMPOUND) {
    +			dpage = folio_page(folio_alloc(GFP_HIGHUSER_MOVABLE,
    +					      order), 0);
    +		} else {
    +			dpage = alloc_page(GFP_HIGHUSER_MOVABLE | __GFP_NOFAIL);
    +			order = 0;
    +		}
    +
    +		/* TODO Support splitting here */
    +		lock_page(dpage);
    +		dst_pfns[i] = migrate_pfn(page_to_pfn(dpage));
    +		if (src_pfns[i] & MIGRATE_PFN_WRITE)
    +			dst_pfns[i] |= MIGRATE_PFN_WRITE;
    +		if (order)
    +			dst_pfns[i] |= MIGRATE_PFN_COMPOUND;
    +		folio_copy(page_folio(dpage), page_folio(spage));
    +	}
    +	migrate_device_pages(src_pfns, dst_pfns, npages);
    +	migrate_device_finalize(src_pfns, dst_pfns, npages);
    +	kvfree(src_pfns);
    +	kvfree(dst_pfns);
    +}
    +
     static int dmirror_fops_release(struct inode *inode, struct file *filp)
     {
     	struct dmirror *dmirror = filp->private_data;
    +	struct dmirror_device *mdevice = dmirror->mdevice;
    +	int i;
     
     	mmu_interval_notifier_remove(&dmirror->notifier);
    +
    +	if (mdevice->devmem_chunks) {
    +		for (i = 0; i < mdevice->devmem_count; i++) {
    +			struct dmirror_chunk *devmem =
    +				mdevice->devmem_chunks[i];
    +
    +			dmirror_device_evict_chunk(devmem);
    +		}
    +	}
    +
     	xa_destroy(&dmirror->pt);
     	kfree(dmirror);
     	return 0;
    @@ -1377,56 +1439,6 @@ static int dmirror_snapshot(struct dmirror *dmirror,
     	return ret;
     }
     
    -static void dmirror_device_evict_chunk(struct dmirror_chunk *chunk)
    -{
    -	unsigned long start_pfn = chunk->pagemap.range.start >> PAGE_SHIFT;
    -	unsigned long end_pfn = chunk->pagemap.range.end >> PAGE_SHIFT;
    -	unsigned long npages = end_pfn - start_pfn + 1;
    -	unsigned long i;
    -	unsigned long *src_pfns;
    -	unsigned long *dst_pfns;
    -	unsigned int order = 0;
    -
    -	src_pfns = kvcalloc(npages, sizeof(*src_pfns), GFP_KERNEL | __GFP_NOFAIL);
    -	dst_pfns = kvcalloc(npages, sizeof(*dst_pfns), GFP_KERNEL | __GFP_NOFAIL);
    -
    -	migrate_device_range(src_pfns, start_pfn, npages);
    -	for (i = 0; i < npages; i++) {
    -		struct page *dpage, *spage;
    -
    -		spage = migrate_pfn_to_page(src_pfns[i]);
    -		if (!spage || !(src_pfns[i] & MIGRATE_PFN_MIGRATE))
    -			continue;
    -
    -		if (WARN_ON(!is_device_private_page(spage) &&
    -			    !is_device_coherent_page(spage)))
    -			continue;
    -
    -		order = folio_order(page_folio(spage));
    -		spage = BACKING_PAGE(spage);
    -		if (src_pfns[i] & MIGRATE_PFN_COMPOUND) {
    -			dpage = folio_page(folio_alloc(GFP_HIGHUSER_MOVABLE,
    -					      order), 0);
    -		} else {
    -			dpage = alloc_page(GFP_HIGHUSER_MOVABLE | __GFP_NOFAIL);
    -			order = 0;
    -		}
    -
    -		/* TODO Support splitting here */
    -		lock_page(dpage);
    -		dst_pfns[i] = migrate_pfn(page_to_pfn(dpage));
    -		if (src_pfns[i] & MIGRATE_PFN_WRITE)
    -			dst_pfns[i] |= MIGRATE_PFN_WRITE;
    -		if (order)
    -			dst_pfns[i] |= MIGRATE_PFN_COMPOUND;
    -		folio_copy(page_folio(dpage), page_folio(spage));
    -	}
    -	migrate_device_pages(src_pfns, dst_pfns, npages);
    -	migrate_device_finalize(src_pfns, dst_pfns, npages);
    -	kvfree(src_pfns);
    -	kvfree(dst_pfns);
    -}
    -
     /* Removes free pages from the free list so they can't be re-allocated */
     static void dmirror_remove_free_pages(struct dmirror_chunk *devmem)
     {
    -- 
    cgit 1.3-korg
    
    
    
9de1eb0aac28

lib: test_hmm: evict device pages on file close to avoid use-after-free

https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.gitAlistair PoppleMar 31, 2026Fixed in 7.0.4via kernel-cna
1 file changed · +62 51
  • lib/test_hmm.c+62 51 modified
    diff --git a/lib/test_hmm.c b/lib/test_hmm.c
    index 0964d53365e61..79fe7d233df1e 100644
    --- a/lib/test_hmm.c
    +++ b/lib/test_hmm.c
    @@ -185,11 +185,73 @@ static int dmirror_fops_open(struct inode *inode, struct file *filp)
     	return 0;
     }
     
    +static void dmirror_device_evict_chunk(struct dmirror_chunk *chunk)
    +{
    +	unsigned long start_pfn = chunk->pagemap.range.start >> PAGE_SHIFT;
    +	unsigned long end_pfn = chunk->pagemap.range.end >> PAGE_SHIFT;
    +	unsigned long npages = end_pfn - start_pfn + 1;
    +	unsigned long i;
    +	unsigned long *src_pfns;
    +	unsigned long *dst_pfns;
    +	unsigned int order = 0;
    +
    +	src_pfns = kvcalloc(npages, sizeof(*src_pfns), GFP_KERNEL | __GFP_NOFAIL);
    +	dst_pfns = kvcalloc(npages, sizeof(*dst_pfns), GFP_KERNEL | __GFP_NOFAIL);
    +
    +	migrate_device_range(src_pfns, start_pfn, npages);
    +	for (i = 0; i < npages; i++) {
    +		struct page *dpage, *spage;
    +
    +		spage = migrate_pfn_to_page(src_pfns[i]);
    +		if (!spage || !(src_pfns[i] & MIGRATE_PFN_MIGRATE))
    +			continue;
    +
    +		if (WARN_ON(!is_device_private_page(spage) &&
    +			    !is_device_coherent_page(spage)))
    +			continue;
    +
    +		order = folio_order(page_folio(spage));
    +		spage = BACKING_PAGE(spage);
    +		if (src_pfns[i] & MIGRATE_PFN_COMPOUND) {
    +			dpage = folio_page(folio_alloc(GFP_HIGHUSER_MOVABLE,
    +					      order), 0);
    +		} else {
    +			dpage = alloc_page(GFP_HIGHUSER_MOVABLE | __GFP_NOFAIL);
    +			order = 0;
    +		}
    +
    +		/* TODO Support splitting here */
    +		lock_page(dpage);
    +		dst_pfns[i] = migrate_pfn(page_to_pfn(dpage));
    +		if (src_pfns[i] & MIGRATE_PFN_WRITE)
    +			dst_pfns[i] |= MIGRATE_PFN_WRITE;
    +		if (order)
    +			dst_pfns[i] |= MIGRATE_PFN_COMPOUND;
    +		folio_copy(page_folio(dpage), page_folio(spage));
    +	}
    +	migrate_device_pages(src_pfns, dst_pfns, npages);
    +	migrate_device_finalize(src_pfns, dst_pfns, npages);
    +	kvfree(src_pfns);
    +	kvfree(dst_pfns);
    +}
    +
     static int dmirror_fops_release(struct inode *inode, struct file *filp)
     {
     	struct dmirror *dmirror = filp->private_data;
    +	struct dmirror_device *mdevice = dmirror->mdevice;
    +	int i;
     
     	mmu_interval_notifier_remove(&dmirror->notifier);
    +
    +	if (mdevice->devmem_chunks) {
    +		for (i = 0; i < mdevice->devmem_count; i++) {
    +			struct dmirror_chunk *devmem =
    +				mdevice->devmem_chunks[i];
    +
    +			dmirror_device_evict_chunk(devmem);
    +		}
    +	}
    +
     	xa_destroy(&dmirror->pt);
     	kfree(dmirror);
     	return 0;
    @@ -1377,56 +1439,6 @@ static int dmirror_snapshot(struct dmirror *dmirror,
     	return ret;
     }
     
    -static void dmirror_device_evict_chunk(struct dmirror_chunk *chunk)
    -{
    -	unsigned long start_pfn = chunk->pagemap.range.start >> PAGE_SHIFT;
    -	unsigned long end_pfn = chunk->pagemap.range.end >> PAGE_SHIFT;
    -	unsigned long npages = end_pfn - start_pfn + 1;
    -	unsigned long i;
    -	unsigned long *src_pfns;
    -	unsigned long *dst_pfns;
    -	unsigned int order = 0;
    -
    -	src_pfns = kvcalloc(npages, sizeof(*src_pfns), GFP_KERNEL | __GFP_NOFAIL);
    -	dst_pfns = kvcalloc(npages, sizeof(*dst_pfns), GFP_KERNEL | __GFP_NOFAIL);
    -
    -	migrate_device_range(src_pfns, start_pfn, npages);
    -	for (i = 0; i < npages; i++) {
    -		struct page *dpage, *spage;
    -
    -		spage = migrate_pfn_to_page(src_pfns[i]);
    -		if (!spage || !(src_pfns[i] & MIGRATE_PFN_MIGRATE))
    -			continue;
    -
    -		if (WARN_ON(!is_device_private_page(spage) &&
    -			    !is_device_coherent_page(spage)))
    -			continue;
    -
    -		order = folio_order(page_folio(spage));
    -		spage = BACKING_PAGE(spage);
    -		if (src_pfns[i] & MIGRATE_PFN_COMPOUND) {
    -			dpage = folio_page(folio_alloc(GFP_HIGHUSER_MOVABLE,
    -					      order), 0);
    -		} else {
    -			dpage = alloc_page(GFP_HIGHUSER_MOVABLE | __GFP_NOFAIL);
    -			order = 0;
    -		}
    -
    -		/* TODO Support splitting here */
    -		lock_page(dpage);
    -		dst_pfns[i] = migrate_pfn(page_to_pfn(dpage));
    -		if (src_pfns[i] & MIGRATE_PFN_WRITE)
    -			dst_pfns[i] |= MIGRATE_PFN_WRITE;
    -		if (order)
    -			dst_pfns[i] |= MIGRATE_PFN_COMPOUND;
    -		folio_copy(page_folio(dpage), page_folio(spage));
    -	}
    -	migrate_device_pages(src_pfns, dst_pfns, npages);
    -	migrate_device_finalize(src_pfns, dst_pfns, npages);
    -	kvfree(src_pfns);
    -	kvfree(dst_pfns);
    -}
    -
     /* Removes free pages from the free list so they can't be re-allocated */
     static void dmirror_remove_free_pages(struct dmirror_chunk *devmem)
     {
    -- 
    cgit 1.3-korg
    
    
    
bf477abd448c

lib: test_hmm: evict device pages on file close to avoid use-after-free

1 file changed · +49 38
  • lib/test_hmm.c+49 38 modified
    diff --git a/lib/test_hmm.c b/lib/test_hmm.c
    index b823ba7cb6a15..cb50065e37a34 100644
    --- a/lib/test_hmm.c
    +++ b/lib/test_hmm.c
    @@ -183,11 +183,60 @@ static int dmirror_fops_open(struct inode *inode, struct file *filp)
     	return 0;
     }
     
    +static void dmirror_device_evict_chunk(struct dmirror_chunk *chunk)
    +{
    +	unsigned long start_pfn = chunk->pagemap.range.start >> PAGE_SHIFT;
    +	unsigned long end_pfn = chunk->pagemap.range.end >> PAGE_SHIFT;
    +	unsigned long npages = end_pfn - start_pfn + 1;
    +	unsigned long i;
    +	unsigned long *src_pfns;
    +	unsigned long *dst_pfns;
    +
    +	src_pfns = kvcalloc(npages, sizeof(*src_pfns), GFP_KERNEL | __GFP_NOFAIL);
    +	dst_pfns = kvcalloc(npages, sizeof(*dst_pfns), GFP_KERNEL | __GFP_NOFAIL);
    +
    +	migrate_device_range(src_pfns, start_pfn, npages);
    +	for (i = 0; i < npages; i++) {
    +		struct page *dpage, *spage;
    +
    +		spage = migrate_pfn_to_page(src_pfns[i]);
    +		if (!spage || !(src_pfns[i] & MIGRATE_PFN_MIGRATE))
    +			continue;
    +
    +		if (WARN_ON(!is_device_private_page(spage) &&
    +			    !is_device_coherent_page(spage)))
    +			continue;
    +		spage = BACKING_PAGE(spage);
    +		dpage = alloc_page(GFP_HIGHUSER_MOVABLE | __GFP_NOFAIL);
    +		lock_page(dpage);
    +		copy_highpage(dpage, spage);
    +		dst_pfns[i] = migrate_pfn(page_to_pfn(dpage));
    +		if (src_pfns[i] & MIGRATE_PFN_WRITE)
    +			dst_pfns[i] |= MIGRATE_PFN_WRITE;
    +	}
    +	migrate_device_pages(src_pfns, dst_pfns, npages);
    +	migrate_device_finalize(src_pfns, dst_pfns, npages);
    +	kvfree(src_pfns);
    +	kvfree(dst_pfns);
    +}
    +
     static int dmirror_fops_release(struct inode *inode, struct file *filp)
     {
     	struct dmirror *dmirror = filp->private_data;
    +	struct dmirror_device *mdevice = dmirror->mdevice;
    +	int i;
     
     	mmu_interval_notifier_remove(&dmirror->notifier);
    +
    +	if (mdevice->devmem_chunks) {
    +		for (i = 0; i < mdevice->devmem_count; i++) {
    +			struct dmirror_chunk *devmem =
    +				mdevice->devmem_chunks[i];
    +
    +			dmirror_device_evict_chunk(devmem);
    +		}
    +	}
    +
     	xa_destroy(&dmirror->pt);
     	kfree(dmirror);
     	return 0;
    @@ -1217,43 +1266,6 @@ static int dmirror_snapshot(struct dmirror *dmirror,
     	return ret;
     }
     
    -static void dmirror_device_evict_chunk(struct dmirror_chunk *chunk)
    -{
    -	unsigned long start_pfn = chunk->pagemap.range.start >> PAGE_SHIFT;
    -	unsigned long end_pfn = chunk->pagemap.range.end >> PAGE_SHIFT;
    -	unsigned long npages = end_pfn - start_pfn + 1;
    -	unsigned long i;
    -	unsigned long *src_pfns;
    -	unsigned long *dst_pfns;
    -
    -	src_pfns = kvcalloc(npages, sizeof(*src_pfns), GFP_KERNEL | __GFP_NOFAIL);
    -	dst_pfns = kvcalloc(npages, sizeof(*dst_pfns), GFP_KERNEL | __GFP_NOFAIL);
    -
    -	migrate_device_range(src_pfns, start_pfn, npages);
    -	for (i = 0; i < npages; i++) {
    -		struct page *dpage, *spage;
    -
    -		spage = migrate_pfn_to_page(src_pfns[i]);
    -		if (!spage || !(src_pfns[i] & MIGRATE_PFN_MIGRATE))
    -			continue;
    -
    -		if (WARN_ON(!is_device_private_page(spage) &&
    -			    !is_device_coherent_page(spage)))
    -			continue;
    -		spage = BACKING_PAGE(spage);
    -		dpage = alloc_page(GFP_HIGHUSER_MOVABLE | __GFP_NOFAIL);
    -		lock_page(dpage);
    -		copy_highpage(dpage, spage);
    -		dst_pfns[i] = migrate_pfn(page_to_pfn(dpage));
    -		if (src_pfns[i] & MIGRATE_PFN_WRITE)
    -			dst_pfns[i] |= MIGRATE_PFN_WRITE;
    -	}
    -	migrate_device_pages(src_pfns, dst_pfns, npages);
    -	migrate_device_finalize(src_pfns, dst_pfns, npages);
    -	kvfree(src_pfns);
    -	kvfree(dst_pfns);
    -}
    -
     /* Removes free pages from the free list so they can't be re-allocated */
     static void dmirror_remove_free_pages(struct dmirror_chunk *devmem)
     {
    -- 
    cgit 1.3-korg
    
    
    
5846715b6382

lib: test_hmm: evict device pages on file close to avoid use-after-free

1 file changed · +49 38
  • lib/test_hmm.c+49 38 modified
    diff --git a/lib/test_hmm.c b/lib/test_hmm.c
    index 056f2e411d7b4..850d23469ef78 100644
    --- a/lib/test_hmm.c
    +++ b/lib/test_hmm.c
    @@ -183,11 +183,60 @@ static int dmirror_fops_open(struct inode *inode, struct file *filp)
     	return 0;
     }
     
    +static void dmirror_device_evict_chunk(struct dmirror_chunk *chunk)
    +{
    +	unsigned long start_pfn = chunk->pagemap.range.start >> PAGE_SHIFT;
    +	unsigned long end_pfn = chunk->pagemap.range.end >> PAGE_SHIFT;
    +	unsigned long npages = end_pfn - start_pfn + 1;
    +	unsigned long i;
    +	unsigned long *src_pfns;
    +	unsigned long *dst_pfns;
    +
    +	src_pfns = kvcalloc(npages, sizeof(*src_pfns), GFP_KERNEL | __GFP_NOFAIL);
    +	dst_pfns = kvcalloc(npages, sizeof(*dst_pfns), GFP_KERNEL | __GFP_NOFAIL);
    +
    +	migrate_device_range(src_pfns, start_pfn, npages);
    +	for (i = 0; i < npages; i++) {
    +		struct page *dpage, *spage;
    +
    +		spage = migrate_pfn_to_page(src_pfns[i]);
    +		if (!spage || !(src_pfns[i] & MIGRATE_PFN_MIGRATE))
    +			continue;
    +
    +		if (WARN_ON(!is_device_private_page(spage) &&
    +			    !is_device_coherent_page(spage)))
    +			continue;
    +		spage = BACKING_PAGE(spage);
    +		dpage = alloc_page(GFP_HIGHUSER_MOVABLE | __GFP_NOFAIL);
    +		lock_page(dpage);
    +		copy_highpage(dpage, spage);
    +		dst_pfns[i] = migrate_pfn(page_to_pfn(dpage));
    +		if (src_pfns[i] & MIGRATE_PFN_WRITE)
    +			dst_pfns[i] |= MIGRATE_PFN_WRITE;
    +	}
    +	migrate_device_pages(src_pfns, dst_pfns, npages);
    +	migrate_device_finalize(src_pfns, dst_pfns, npages);
    +	kvfree(src_pfns);
    +	kvfree(dst_pfns);
    +}
    +
     static int dmirror_fops_release(struct inode *inode, struct file *filp)
     {
     	struct dmirror *dmirror = filp->private_data;
    +	struct dmirror_device *mdevice = dmirror->mdevice;
    +	int i;
     
     	mmu_interval_notifier_remove(&dmirror->notifier);
    +
    +	if (mdevice->devmem_chunks) {
    +		for (i = 0; i < mdevice->devmem_count; i++) {
    +			struct dmirror_chunk *devmem =
    +				mdevice->devmem_chunks[i];
    +
    +			dmirror_device_evict_chunk(devmem);
    +		}
    +	}
    +
     	xa_destroy(&dmirror->pt);
     	kfree(dmirror);
     	return 0;
    @@ -1214,43 +1263,6 @@ static int dmirror_snapshot(struct dmirror *dmirror,
     	return ret;
     }
     
    -static void dmirror_device_evict_chunk(struct dmirror_chunk *chunk)
    -{
    -	unsigned long start_pfn = chunk->pagemap.range.start >> PAGE_SHIFT;
    -	unsigned long end_pfn = chunk->pagemap.range.end >> PAGE_SHIFT;
    -	unsigned long npages = end_pfn - start_pfn + 1;
    -	unsigned long i;
    -	unsigned long *src_pfns;
    -	unsigned long *dst_pfns;
    -
    -	src_pfns = kvcalloc(npages, sizeof(*src_pfns), GFP_KERNEL | __GFP_NOFAIL);
    -	dst_pfns = kvcalloc(npages, sizeof(*dst_pfns), GFP_KERNEL | __GFP_NOFAIL);
    -
    -	migrate_device_range(src_pfns, start_pfn, npages);
    -	for (i = 0; i < npages; i++) {
    -		struct page *dpage, *spage;
    -
    -		spage = migrate_pfn_to_page(src_pfns[i]);
    -		if (!spage || !(src_pfns[i] & MIGRATE_PFN_MIGRATE))
    -			continue;
    -
    -		if (WARN_ON(!is_device_private_page(spage) &&
    -			    !is_device_coherent_page(spage)))
    -			continue;
    -		spage = BACKING_PAGE(spage);
    -		dpage = alloc_page(GFP_HIGHUSER_MOVABLE | __GFP_NOFAIL);
    -		lock_page(dpage);
    -		copy_highpage(dpage, spage);
    -		dst_pfns[i] = migrate_pfn(page_to_pfn(dpage));
    -		if (src_pfns[i] & MIGRATE_PFN_WRITE)
    -			dst_pfns[i] |= MIGRATE_PFN_WRITE;
    -	}
    -	migrate_device_pages(src_pfns, dst_pfns, npages);
    -	migrate_device_finalize(src_pfns, dst_pfns, npages);
    -	kvfree(src_pfns);
    -	kvfree(dst_pfns);
    -}
    -
     /* Removes free pages from the free list so they can't be re-allocated */
     static void dmirror_remove_free_pages(struct dmirror_chunk *devmem)
     {
    -- 
    cgit 1.3-korg
    
    
    
38f113f81d3f

lib: test_hmm: evict device pages on file close to avoid use-after-free

1 file changed · +49 38
  • lib/test_hmm.c+49 38 modified
    diff --git a/lib/test_hmm.c b/lib/test_hmm.c
    index 83e3d8208a540..00d34a6c6276b 100644
    --- a/lib/test_hmm.c
    +++ b/lib/test_hmm.c
    @@ -183,11 +183,60 @@ static int dmirror_fops_open(struct inode *inode, struct file *filp)
     	return 0;
     }
     
    +static void dmirror_device_evict_chunk(struct dmirror_chunk *chunk)
    +{
    +	unsigned long start_pfn = chunk->pagemap.range.start >> PAGE_SHIFT;
    +	unsigned long end_pfn = chunk->pagemap.range.end >> PAGE_SHIFT;
    +	unsigned long npages = end_pfn - start_pfn + 1;
    +	unsigned long i;
    +	unsigned long *src_pfns;
    +	unsigned long *dst_pfns;
    +
    +	src_pfns = kvcalloc(npages, sizeof(*src_pfns), GFP_KERNEL | __GFP_NOFAIL);
    +	dst_pfns = kvcalloc(npages, sizeof(*dst_pfns), GFP_KERNEL | __GFP_NOFAIL);
    +
    +	migrate_device_range(src_pfns, start_pfn, npages);
    +	for (i = 0; i < npages; i++) {
    +		struct page *dpage, *spage;
    +
    +		spage = migrate_pfn_to_page(src_pfns[i]);
    +		if (!spage || !(src_pfns[i] & MIGRATE_PFN_MIGRATE))
    +			continue;
    +
    +		if (WARN_ON(!is_device_private_page(spage) &&
    +			    !is_device_coherent_page(spage)))
    +			continue;
    +		spage = BACKING_PAGE(spage);
    +		dpage = alloc_page(GFP_HIGHUSER_MOVABLE | __GFP_NOFAIL);
    +		lock_page(dpage);
    +		copy_highpage(dpage, spage);
    +		dst_pfns[i] = migrate_pfn(page_to_pfn(dpage));
    +		if (src_pfns[i] & MIGRATE_PFN_WRITE)
    +			dst_pfns[i] |= MIGRATE_PFN_WRITE;
    +	}
    +	migrate_device_pages(src_pfns, dst_pfns, npages);
    +	migrate_device_finalize(src_pfns, dst_pfns, npages);
    +	kvfree(src_pfns);
    +	kvfree(dst_pfns);
    +}
    +
     static int dmirror_fops_release(struct inode *inode, struct file *filp)
     {
     	struct dmirror *dmirror = filp->private_data;
    +	struct dmirror_device *mdevice = dmirror->mdevice;
    +	int i;
     
     	mmu_interval_notifier_remove(&dmirror->notifier);
    +
    +	if (mdevice->devmem_chunks) {
    +		for (i = 0; i < mdevice->devmem_count; i++) {
    +			struct dmirror_chunk *devmem =
    +				mdevice->devmem_chunks[i];
    +
    +			dmirror_device_evict_chunk(devmem);
    +		}
    +	}
    +
     	xa_destroy(&dmirror->pt);
     	kfree(dmirror);
     	return 0;
    @@ -1192,43 +1241,6 @@ static int dmirror_snapshot(struct dmirror *dmirror,
     	return ret;
     }
     
    -static void dmirror_device_evict_chunk(struct dmirror_chunk *chunk)
    -{
    -	unsigned long start_pfn = chunk->pagemap.range.start >> PAGE_SHIFT;
    -	unsigned long end_pfn = chunk->pagemap.range.end >> PAGE_SHIFT;
    -	unsigned long npages = end_pfn - start_pfn + 1;
    -	unsigned long i;
    -	unsigned long *src_pfns;
    -	unsigned long *dst_pfns;
    -
    -	src_pfns = kvcalloc(npages, sizeof(*src_pfns), GFP_KERNEL | __GFP_NOFAIL);
    -	dst_pfns = kvcalloc(npages, sizeof(*dst_pfns), GFP_KERNEL | __GFP_NOFAIL);
    -
    -	migrate_device_range(src_pfns, start_pfn, npages);
    -	for (i = 0; i < npages; i++) {
    -		struct page *dpage, *spage;
    -
    -		spage = migrate_pfn_to_page(src_pfns[i]);
    -		if (!spage || !(src_pfns[i] & MIGRATE_PFN_MIGRATE))
    -			continue;
    -
    -		if (WARN_ON(!is_device_private_page(spage) &&
    -			    !is_device_coherent_page(spage)))
    -			continue;
    -		spage = BACKING_PAGE(spage);
    -		dpage = alloc_page(GFP_HIGHUSER_MOVABLE | __GFP_NOFAIL);
    -		lock_page(dpage);
    -		copy_highpage(dpage, spage);
    -		dst_pfns[i] = migrate_pfn(page_to_pfn(dpage));
    -		if (src_pfns[i] & MIGRATE_PFN_WRITE)
    -			dst_pfns[i] |= MIGRATE_PFN_WRITE;
    -	}
    -	migrate_device_pages(src_pfns, dst_pfns, npages);
    -	migrate_device_finalize(src_pfns, dst_pfns, npages);
    -	kvfree(src_pfns);
    -	kvfree(dst_pfns);
    -}
    -
     /* Removes free pages from the free list so they can't be re-allocated */
     static void dmirror_remove_free_pages(struct dmirror_chunk *devmem)
     {
    -- 
    cgit 1.3-korg
    
    
    
744dd97752ef

lib: test_hmm: evict device pages on file close to avoid use-after-free

1 file changed · +62 51
  • lib/test_hmm.c+62 51 modified
    diff --git a/lib/test_hmm.c b/lib/test_hmm.c
    index 0964d53365e61..79fe7d233df1e 100644
    --- a/lib/test_hmm.c
    +++ b/lib/test_hmm.c
    @@ -185,11 +185,73 @@ static int dmirror_fops_open(struct inode *inode, struct file *filp)
     	return 0;
     }
     
    +static void dmirror_device_evict_chunk(struct dmirror_chunk *chunk)
    +{
    +	unsigned long start_pfn = chunk->pagemap.range.start >> PAGE_SHIFT;
    +	unsigned long end_pfn = chunk->pagemap.range.end >> PAGE_SHIFT;
    +	unsigned long npages = end_pfn - start_pfn + 1;
    +	unsigned long i;
    +	unsigned long *src_pfns;
    +	unsigned long *dst_pfns;
    +	unsigned int order = 0;
    +
    +	src_pfns = kvcalloc(npages, sizeof(*src_pfns), GFP_KERNEL | __GFP_NOFAIL);
    +	dst_pfns = kvcalloc(npages, sizeof(*dst_pfns), GFP_KERNEL | __GFP_NOFAIL);
    +
    +	migrate_device_range(src_pfns, start_pfn, npages);
    +	for (i = 0; i < npages; i++) {
    +		struct page *dpage, *spage;
    +
    +		spage = migrate_pfn_to_page(src_pfns[i]);
    +		if (!spage || !(src_pfns[i] & MIGRATE_PFN_MIGRATE))
    +			continue;
    +
    +		if (WARN_ON(!is_device_private_page(spage) &&
    +			    !is_device_coherent_page(spage)))
    +			continue;
    +
    +		order = folio_order(page_folio(spage));
    +		spage = BACKING_PAGE(spage);
    +		if (src_pfns[i] & MIGRATE_PFN_COMPOUND) {
    +			dpage = folio_page(folio_alloc(GFP_HIGHUSER_MOVABLE,
    +					      order), 0);
    +		} else {
    +			dpage = alloc_page(GFP_HIGHUSER_MOVABLE | __GFP_NOFAIL);
    +			order = 0;
    +		}
    +
    +		/* TODO Support splitting here */
    +		lock_page(dpage);
    +		dst_pfns[i] = migrate_pfn(page_to_pfn(dpage));
    +		if (src_pfns[i] & MIGRATE_PFN_WRITE)
    +			dst_pfns[i] |= MIGRATE_PFN_WRITE;
    +		if (order)
    +			dst_pfns[i] |= MIGRATE_PFN_COMPOUND;
    +		folio_copy(page_folio(dpage), page_folio(spage));
    +	}
    +	migrate_device_pages(src_pfns, dst_pfns, npages);
    +	migrate_device_finalize(src_pfns, dst_pfns, npages);
    +	kvfree(src_pfns);
    +	kvfree(dst_pfns);
    +}
    +
     static int dmirror_fops_release(struct inode *inode, struct file *filp)
     {
     	struct dmirror *dmirror = filp->private_data;
    +	struct dmirror_device *mdevice = dmirror->mdevice;
    +	int i;
     
     	mmu_interval_notifier_remove(&dmirror->notifier);
    +
    +	if (mdevice->devmem_chunks) {
    +		for (i = 0; i < mdevice->devmem_count; i++) {
    +			struct dmirror_chunk *devmem =
    +				mdevice->devmem_chunks[i];
    +
    +			dmirror_device_evict_chunk(devmem);
    +		}
    +	}
    +
     	xa_destroy(&dmirror->pt);
     	kfree(dmirror);
     	return 0;
    @@ -1377,56 +1439,6 @@ static int dmirror_snapshot(struct dmirror *dmirror,
     	return ret;
     }
     
    -static void dmirror_device_evict_chunk(struct dmirror_chunk *chunk)
    -{
    -	unsigned long start_pfn = chunk->pagemap.range.start >> PAGE_SHIFT;
    -	unsigned long end_pfn = chunk->pagemap.range.end >> PAGE_SHIFT;
    -	unsigned long npages = end_pfn - start_pfn + 1;
    -	unsigned long i;
    -	unsigned long *src_pfns;
    -	unsigned long *dst_pfns;
    -	unsigned int order = 0;
    -
    -	src_pfns = kvcalloc(npages, sizeof(*src_pfns), GFP_KERNEL | __GFP_NOFAIL);
    -	dst_pfns = kvcalloc(npages, sizeof(*dst_pfns), GFP_KERNEL | __GFP_NOFAIL);
    -
    -	migrate_device_range(src_pfns, start_pfn, npages);
    -	for (i = 0; i < npages; i++) {
    -		struct page *dpage, *spage;
    -
    -		spage = migrate_pfn_to_page(src_pfns[i]);
    -		if (!spage || !(src_pfns[i] & MIGRATE_PFN_MIGRATE))
    -			continue;
    -
    -		if (WARN_ON(!is_device_private_page(spage) &&
    -			    !is_device_coherent_page(spage)))
    -			continue;
    -
    -		order = folio_order(page_folio(spage));
    -		spage = BACKING_PAGE(spage);
    -		if (src_pfns[i] & MIGRATE_PFN_COMPOUND) {
    -			dpage = folio_page(folio_alloc(GFP_HIGHUSER_MOVABLE,
    -					      order), 0);
    -		} else {
    -			dpage = alloc_page(GFP_HIGHUSER_MOVABLE | __GFP_NOFAIL);
    -			order = 0;
    -		}
    -
    -		/* TODO Support splitting here */
    -		lock_page(dpage);
    -		dst_pfns[i] = migrate_pfn(page_to_pfn(dpage));
    -		if (src_pfns[i] & MIGRATE_PFN_WRITE)
    -			dst_pfns[i] |= MIGRATE_PFN_WRITE;
    -		if (order)
    -			dst_pfns[i] |= MIGRATE_PFN_COMPOUND;
    -		folio_copy(page_folio(dpage), page_folio(spage));
    -	}
    -	migrate_device_pages(src_pfns, dst_pfns, npages);
    -	migrate_device_finalize(src_pfns, dst_pfns, npages);
    -	kvfree(src_pfns);
    -	kvfree(dst_pfns);
    -}
    -
     /* Removes free pages from the free list so they can't be re-allocated */
     static void dmirror_remove_free_pages(struct dmirror_chunk *devmem)
     {
    -- 
    cgit 1.3-korg
    
    
    
9de1eb0aac28

lib: test_hmm: evict device pages on file close to avoid use-after-free

1 file changed · +62 51
  • lib/test_hmm.c+62 51 modified
    diff --git a/lib/test_hmm.c b/lib/test_hmm.c
    index 0964d53365e61..79fe7d233df1e 100644
    --- a/lib/test_hmm.c
    +++ b/lib/test_hmm.c
    @@ -185,11 +185,73 @@ static int dmirror_fops_open(struct inode *inode, struct file *filp)
     	return 0;
     }
     
    +static void dmirror_device_evict_chunk(struct dmirror_chunk *chunk)
    +{
    +	unsigned long start_pfn = chunk->pagemap.range.start >> PAGE_SHIFT;
    +	unsigned long end_pfn = chunk->pagemap.range.end >> PAGE_SHIFT;
    +	unsigned long npages = end_pfn - start_pfn + 1;
    +	unsigned long i;
    +	unsigned long *src_pfns;
    +	unsigned long *dst_pfns;
    +	unsigned int order = 0;
    +
    +	src_pfns = kvcalloc(npages, sizeof(*src_pfns), GFP_KERNEL | __GFP_NOFAIL);
    +	dst_pfns = kvcalloc(npages, sizeof(*dst_pfns), GFP_KERNEL | __GFP_NOFAIL);
    +
    +	migrate_device_range(src_pfns, start_pfn, npages);
    +	for (i = 0; i < npages; i++) {
    +		struct page *dpage, *spage;
    +
    +		spage = migrate_pfn_to_page(src_pfns[i]);
    +		if (!spage || !(src_pfns[i] & MIGRATE_PFN_MIGRATE))
    +			continue;
    +
    +		if (WARN_ON(!is_device_private_page(spage) &&
    +			    !is_device_coherent_page(spage)))
    +			continue;
    +
    +		order = folio_order(page_folio(spage));
    +		spage = BACKING_PAGE(spage);
    +		if (src_pfns[i] & MIGRATE_PFN_COMPOUND) {
    +			dpage = folio_page(folio_alloc(GFP_HIGHUSER_MOVABLE,
    +					      order), 0);
    +		} else {
    +			dpage = alloc_page(GFP_HIGHUSER_MOVABLE | __GFP_NOFAIL);
    +			order = 0;
    +		}
    +
    +		/* TODO Support splitting here */
    +		lock_page(dpage);
    +		dst_pfns[i] = migrate_pfn(page_to_pfn(dpage));
    +		if (src_pfns[i] & MIGRATE_PFN_WRITE)
    +			dst_pfns[i] |= MIGRATE_PFN_WRITE;
    +		if (order)
    +			dst_pfns[i] |= MIGRATE_PFN_COMPOUND;
    +		folio_copy(page_folio(dpage), page_folio(spage));
    +	}
    +	migrate_device_pages(src_pfns, dst_pfns, npages);
    +	migrate_device_finalize(src_pfns, dst_pfns, npages);
    +	kvfree(src_pfns);
    +	kvfree(dst_pfns);
    +}
    +
     static int dmirror_fops_release(struct inode *inode, struct file *filp)
     {
     	struct dmirror *dmirror = filp->private_data;
    +	struct dmirror_device *mdevice = dmirror->mdevice;
    +	int i;
     
     	mmu_interval_notifier_remove(&dmirror->notifier);
    +
    +	if (mdevice->devmem_chunks) {
    +		for (i = 0; i < mdevice->devmem_count; i++) {
    +			struct dmirror_chunk *devmem =
    +				mdevice->devmem_chunks[i];
    +
    +			dmirror_device_evict_chunk(devmem);
    +		}
    +	}
    +
     	xa_destroy(&dmirror->pt);
     	kfree(dmirror);
     	return 0;
    @@ -1377,56 +1439,6 @@ static int dmirror_snapshot(struct dmirror *dmirror,
     	return ret;
     }
     
    -static void dmirror_device_evict_chunk(struct dmirror_chunk *chunk)
    -{
    -	unsigned long start_pfn = chunk->pagemap.range.start >> PAGE_SHIFT;
    -	unsigned long end_pfn = chunk->pagemap.range.end >> PAGE_SHIFT;
    -	unsigned long npages = end_pfn - start_pfn + 1;
    -	unsigned long i;
    -	unsigned long *src_pfns;
    -	unsigned long *dst_pfns;
    -	unsigned int order = 0;
    -
    -	src_pfns = kvcalloc(npages, sizeof(*src_pfns), GFP_KERNEL | __GFP_NOFAIL);
    -	dst_pfns = kvcalloc(npages, sizeof(*dst_pfns), GFP_KERNEL | __GFP_NOFAIL);
    -
    -	migrate_device_range(src_pfns, start_pfn, npages);
    -	for (i = 0; i < npages; i++) {
    -		struct page *dpage, *spage;
    -
    -		spage = migrate_pfn_to_page(src_pfns[i]);
    -		if (!spage || !(src_pfns[i] & MIGRATE_PFN_MIGRATE))
    -			continue;
    -
    -		if (WARN_ON(!is_device_private_page(spage) &&
    -			    !is_device_coherent_page(spage)))
    -			continue;
    -
    -		order = folio_order(page_folio(spage));
    -		spage = BACKING_PAGE(spage);
    -		if (src_pfns[i] & MIGRATE_PFN_COMPOUND) {
    -			dpage = folio_page(folio_alloc(GFP_HIGHUSER_MOVABLE,
    -					      order), 0);
    -		} else {
    -			dpage = alloc_page(GFP_HIGHUSER_MOVABLE | __GFP_NOFAIL);
    -			order = 0;
    -		}
    -
    -		/* TODO Support splitting here */
    -		lock_page(dpage);
    -		dst_pfns[i] = migrate_pfn(page_to_pfn(dpage));
    -		if (src_pfns[i] & MIGRATE_PFN_WRITE)
    -			dst_pfns[i] |= MIGRATE_PFN_WRITE;
    -		if (order)
    -			dst_pfns[i] |= MIGRATE_PFN_COMPOUND;
    -		folio_copy(page_folio(dpage), page_folio(spage));
    -	}
    -	migrate_device_pages(src_pfns, dst_pfns, npages);
    -	migrate_device_finalize(src_pfns, dst_pfns, npages);
    -	kvfree(src_pfns);
    -	kvfree(dst_pfns);
    -}
    -
     /* Removes free pages from the free list so they can't be re-allocated */
     static void dmirror_remove_free_pages(struct dmirror_chunk *devmem)
     {
    -- 
    cgit 1.3-korg
    
    
    

Vulnerability mechanics

Root cause

"The `dmirror_fops_release` function frees the `dmirror` struct before migrating device private pages back to system memory, leading to a use-after-free vulnerability."

Attack vector

An attacker can trigger this vulnerability by closing a file descriptor associated with a `dmirror` device. This action calls the `dmirror_fops_release` function. If a subsequent memory access occurs on the now-freed device pages, such as during a coredump triggered by a test failure, the `dmirror_devmem_fault` callback will dereference a stale pointer, causing a kernel panic.

Affected code

The vulnerability exists in the `dmirror_fops_release` function within the `lib/test_hmm.c` file. Specifically, the issue arises because the `dmirror` struct is freed before device private pages are migrated back to system memory. The `dmirror_device_evict_chunk` function is introduced and called within `dmirror_fops_release` to address this.

What the fix does

The patch modifies the `dmirror_fops_release` function to first iterate through all `devmem_chunks` associated with the `dmirror_device`. For each chunk, it calls `dmirror_device_evict_chunk` to migrate device private pages back to system memory. Only after all pages are migrated does it proceed to free the `dmirror` struct, thus preventing the use-after-free condition and subsequent kernel panic.

Generated on Jun 8, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

5

News mentions

0

No linked articles in our index yet.