Skip to content

Commit bf477ab

Browse files
apopple-nvidiagregkh
authored andcommitted
lib: test_hmm: evict device pages on file close to avoid use-after-free
[ Upstream commit 744dd97 ] 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. Link: https://lore.kernel.org/20260331063445.3551404-1-apopple@nvidia.com Link: https://lore.kernel.org/20260331063445.3551404-2-apopple@nvidia.com Fixes: b2ef9f5 ("mm/hmm/test: add selftest driver for HMM") Signed-off-by: Alistair Popple <apopple@nvidia.com> Reported-by: Zenghui Yu <zenghui.yu@linux.dev> Closes: https://lore.kernel.org/linux-mm/8bd0396a-8997-4d2e-a13f-5aac033083d7@linux.dev/ Reviewed-by: Balbir Singh <balbirs@nvidia.com> Tested-by: Zenghui Yu <zenghui.yu@linux.dev> Cc: David Hildenbrand <david@kernel.org> Cc: Jason Gunthorpe <jgg@ziepe.ca> Cc: Leon Romanovsky <leon@kernel.org> Cc: Liam Howlett <liam.howlett@oracle.com> Cc: Lorenzo Stoakes (Oracle) <ljs@kernel.org> Cc: Michal Hocko <mhocko@suse.com> Cc: Mike Rapoport <rppt@kernel.org> Cc: Suren Baghdasaryan <surenb@google.com> Cc: Zenghui Yu <zenghui.yu@linux.dev> Cc: Matthew Brost <matthew.brost@intel.com> Cc: <stable@vger.kernel.org> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> [ kept the existing simpler `dmirror_device_evict_chunk()` body instead of the upstream compound-folio version ] Signed-off-by: Sasha Levin <sashal@kernel.org> Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
1 parent 11869ce commit bf477ab

1 file changed

Lines changed: 49 additions & 37 deletions

File tree

lib/test_hmm.c

Lines changed: 49 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -183,11 +183,60 @@ static int dmirror_fops_open(struct inode *inode, struct file *filp)
183183
return 0;
184184
}
185185

186+
static void dmirror_device_evict_chunk(struct dmirror_chunk *chunk)
187+
{
188+
unsigned long start_pfn = chunk->pagemap.range.start >> PAGE_SHIFT;
189+
unsigned long end_pfn = chunk->pagemap.range.end >> PAGE_SHIFT;
190+
unsigned long npages = end_pfn - start_pfn + 1;
191+
unsigned long i;
192+
unsigned long *src_pfns;
193+
unsigned long *dst_pfns;
194+
195+
src_pfns = kvcalloc(npages, sizeof(*src_pfns), GFP_KERNEL | __GFP_NOFAIL);
196+
dst_pfns = kvcalloc(npages, sizeof(*dst_pfns), GFP_KERNEL | __GFP_NOFAIL);
197+
198+
migrate_device_range(src_pfns, start_pfn, npages);
199+
for (i = 0; i < npages; i++) {
200+
struct page *dpage, *spage;
201+
202+
spage = migrate_pfn_to_page(src_pfns[i]);
203+
if (!spage || !(src_pfns[i] & MIGRATE_PFN_MIGRATE))
204+
continue;
205+
206+
if (WARN_ON(!is_device_private_page(spage) &&
207+
!is_device_coherent_page(spage)))
208+
continue;
209+
spage = BACKING_PAGE(spage);
210+
dpage = alloc_page(GFP_HIGHUSER_MOVABLE | __GFP_NOFAIL);
211+
lock_page(dpage);
212+
copy_highpage(dpage, spage);
213+
dst_pfns[i] = migrate_pfn(page_to_pfn(dpage));
214+
if (src_pfns[i] & MIGRATE_PFN_WRITE)
215+
dst_pfns[i] |= MIGRATE_PFN_WRITE;
216+
}
217+
migrate_device_pages(src_pfns, dst_pfns, npages);
218+
migrate_device_finalize(src_pfns, dst_pfns, npages);
219+
kvfree(src_pfns);
220+
kvfree(dst_pfns);
221+
}
222+
186223
static int dmirror_fops_release(struct inode *inode, struct file *filp)
187224
{
188225
struct dmirror *dmirror = filp->private_data;
226+
struct dmirror_device *mdevice = dmirror->mdevice;
227+
int i;
189228

190229
mmu_interval_notifier_remove(&dmirror->notifier);
230+
231+
if (mdevice->devmem_chunks) {
232+
for (i = 0; i < mdevice->devmem_count; i++) {
233+
struct dmirror_chunk *devmem =
234+
mdevice->devmem_chunks[i];
235+
236+
dmirror_device_evict_chunk(devmem);
237+
}
238+
}
239+
191240
xa_destroy(&dmirror->pt);
192241
kfree(dmirror);
193242
return 0;
@@ -1217,43 +1266,6 @@ static int dmirror_snapshot(struct dmirror *dmirror,
12171266
return ret;
12181267
}
12191268

1220-
static void dmirror_device_evict_chunk(struct dmirror_chunk *chunk)
1221-
{
1222-
unsigned long start_pfn = chunk->pagemap.range.start >> PAGE_SHIFT;
1223-
unsigned long end_pfn = chunk->pagemap.range.end >> PAGE_SHIFT;
1224-
unsigned long npages = end_pfn - start_pfn + 1;
1225-
unsigned long i;
1226-
unsigned long *src_pfns;
1227-
unsigned long *dst_pfns;
1228-
1229-
src_pfns = kvcalloc(npages, sizeof(*src_pfns), GFP_KERNEL | __GFP_NOFAIL);
1230-
dst_pfns = kvcalloc(npages, sizeof(*dst_pfns), GFP_KERNEL | __GFP_NOFAIL);
1231-
1232-
migrate_device_range(src_pfns, start_pfn, npages);
1233-
for (i = 0; i < npages; i++) {
1234-
struct page *dpage, *spage;
1235-
1236-
spage = migrate_pfn_to_page(src_pfns[i]);
1237-
if (!spage || !(src_pfns[i] & MIGRATE_PFN_MIGRATE))
1238-
continue;
1239-
1240-
if (WARN_ON(!is_device_private_page(spage) &&
1241-
!is_device_coherent_page(spage)))
1242-
continue;
1243-
spage = BACKING_PAGE(spage);
1244-
dpage = alloc_page(GFP_HIGHUSER_MOVABLE | __GFP_NOFAIL);
1245-
lock_page(dpage);
1246-
copy_highpage(dpage, spage);
1247-
dst_pfns[i] = migrate_pfn(page_to_pfn(dpage));
1248-
if (src_pfns[i] & MIGRATE_PFN_WRITE)
1249-
dst_pfns[i] |= MIGRATE_PFN_WRITE;
1250-
}
1251-
migrate_device_pages(src_pfns, dst_pfns, npages);
1252-
migrate_device_finalize(src_pfns, dst_pfns, npages);
1253-
kvfree(src_pfns);
1254-
kvfree(dst_pfns);
1255-
}
1256-
12571269
/* Removes free pages from the free list so they can't be re-allocated */
12581270
static void dmirror_remove_free_pages(struct dmirror_chunk *devmem)
12591271
{

0 commit comments

Comments
 (0)