FAILED: patch "[PATCH] lib: test_hmm: evict device pages on file close to avoid" failed to apply to 6.1-stable tree
Subsystems (3)
[email protected]
maintainerThe patch below does not apply to the 6.1-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-6.1.y
git checkout FETCH_HEAD
git cherry-pick -x 744dd97752ef1076a8d8672bb0d8aa2c7abc1144
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026042709-regress-upper-c18d@gregkh' --subject-prefix 'PATCH 6.1.y' HEAD^..
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From 744dd97752ef1076a8d8672bb0d8aa2c7abc1144 Mon Sep 17 00:00:00 2001
From: Alistair Popple <[email protected]>
Date: Tue, 31 Mar 2026 17:34:43 +1100
Subject: [PATCH] lib: test_hmm: evict device pages on file close to avoid
use-after-free
Patch series "Minor hmm_test fixes and cleanups".
Two bugfixes a cleanup for the HMM kernel selftests. These were mostly reported by Zenghui Yu with special thanks to Lorenzo for analysing and pointing out the problems.
This patch (of 3):
When dmirror_fops_release() is called it frees the dmirror struct but doesn't migrate device private pages back to system memory first. This leaves those pages with a dangling zone_device_data pointer to the freed dmirror.
If a subsequent fault occurs on those pages (eg. during coredump) the dmirror_devmem_fault() callback dereferences the stale pointer causing a kernel panic. This was reported [1] when running mm/ksft_hmm.sh on arm64, where a test failure triggered SIGABRT and the resulting coredump walked the VMAs faulting in the stale device private pages.
Fix this by calling dmirror_device_evict_chunk() for each devmem chunk in dmirror_fops_release() to migrate all device private pages back to system memory before freeing the dmirror struct. The function is moved earlier in the file to avoid a forward declaration.
lib/test_hmm.c +62 -50
@@ -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) {
Sasha Levin
maintainerPATCH 1/1 lib: test_hmm: evict device pages on file close to avoid use-after-free
From: Alistair Popple <[email protected]>
[ Upstream commit 744dd97752ef1076a8d8672bb0d8aa2c7abc1144 ]
Patch series "Minor hmm_test fixes and cleanups".
Two bugfixes a cleanup for the HMM kernel selftests. These were mostly reported by Zenghui Yu with special thanks to Lorenzo for analysing and pointing out the problems.
This patch (of 3):
When dmirror_fops_release() is called it frees the dmirror struct but doesn't migrate device private pages back to system memory first. This leaves those pages with a dangling zone_device_data pointer to the freed dmirror.
If a subsequent fault occurs on those pages (eg. during coredump) the dmirror_devmem_fault() callback dereferences the stale pointer causing a kernel panic. This was reported [1] when running mm/ksft_hmm.sh on arm64, where a test failure triggered SIGABRT and the resulting coredump walked the VMAs faulting in the stale device private pages.
Fix this by calling dmirror_device_evict_chunk() for each devmem chunk in dmirror_fops_release() to migrate all device private pages back to system memory before freeing the dmirror struct. The function is moved earlier in the file to avoid a forward declaration.
--- lib/test_hmm.c | 86 ++++++++++++++++++++++++++++---------------------- 1 file changed, 49 insertions(+), 37 deletions(-)
lib/test_hmm.c +49 -37
@@ -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 ;
@@ -1223,43 +1272,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) {
Signature · 1 line
2.53.0