Skip to content

Commit 8cfbdbd

Browse files
paulusmackmpe
authored andcommitted
KVM: PPC: Book3S: Fix guest DMA when guest partially backed by THP pages
Commit 76fa497 ("KVM: PPC: Check if IOMMU page is contained in the pinned physical page", 2018-07-17) added some checks to ensure that guest DMA mappings don't attempt to map more than the guest is entitled to access. However, errors in the logic mean that legitimate guest requests to map pages for DMA are being denied in some situations. Specifically, if the first page of the range passed to mm_iommu_get() is mapped with a normal page, and subsequent pages are mapped with transparent huge pages, we end up with mem->pageshift == 0. That means that the page size checks in mm_iommu_ua_to_hpa() and mm_iommu_up_to_hpa_rm() will always fail for every page in that region, and thus the guest can never map any memory in that region for DMA, typically leading to a flood of error messages like this: qemu-system-ppc64: VFIO_MAP_DMA: -22 qemu-system-ppc64: vfio_dma_map(0x10005f47780, 0x800000000000000, 0x10000, 0x7fff63ff0000) = -22 (Invalid argument) The logic errors in mm_iommu_get() are: (a) use of 'ua' not 'ua + (i << PAGE_SHIFT)' in the find_linux_pte() call (meaning that find_linux_pte() returns the pte for the first address in the range, not the address we are currently up to); (b) use of 'pageshift' as the variable to receive the hugepage shift returned by find_linux_pte() - for a normal page this gets set to 0, leading to us setting mem->pageshift to 0 when we conclude that the pte returned by find_linux_pte() didn't match the page we were looking at; (c) comparing 'compshift', which is a page order, i.e. log base 2 of the number of pages, with 'pageshift', which is a log base 2 of the number of bytes. To fix these problems, this patch introduces 'cur_ua' to hold the current user address and uses that in the find_linux_pte() call; introduces 'pteshift' to hold the hugepage shift found by find_linux_pte(); and compares 'pteshift' with 'compshift + PAGE_SHIFT' rather than 'compshift'. The patch also moves the local_irq_restore to the point after the PTE pointer returned by find_linux_pte() has been dereferenced because otherwise the PTE could change underneath us, and adds a check to avoid doing the find_linux_pte() call once mem->pageshift has been reduced to PAGE_SHIFT, as an optimization. Fixes: 76fa497 ("KVM: PPC: Check if IOMMU page is contained in the pinned physical page") Cc: [email protected] # v4.12+ Signed-off-by: Paul Mackerras <[email protected]> Signed-off-by: Michael Ellerman <[email protected]>
1 parent f08d08f commit 8cfbdbd

File tree

1 file changed

+10
-7
lines changed

1 file changed

+10
-7
lines changed

arch/powerpc/mm/mmu_context_iommu.c

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ long mm_iommu_get(struct mm_struct *mm, unsigned long ua, unsigned long entries,
129129
long i, j, ret = 0, locked_entries = 0;
130130
unsigned int pageshift;
131131
unsigned long flags;
132+
unsigned long cur_ua;
132133
struct page *page = NULL;
133134

134135
mutex_lock(&mem_list_mutex);
@@ -177,7 +178,8 @@ long mm_iommu_get(struct mm_struct *mm, unsigned long ua, unsigned long entries,
177178
}
178179

179180
for (i = 0; i < entries; ++i) {
180-
if (1 != get_user_pages_fast(ua + (i << PAGE_SHIFT),
181+
cur_ua = ua + (i << PAGE_SHIFT);
182+
if (1 != get_user_pages_fast(cur_ua,
181183
1/* pages */, 1/* iswrite */, &page)) {
182184
ret = -EFAULT;
183185
for (j = 0; j < i; ++j)
@@ -196,7 +198,7 @@ long mm_iommu_get(struct mm_struct *mm, unsigned long ua, unsigned long entries,
196198
if (is_migrate_cma_page(page)) {
197199
if (mm_iommu_move_page_from_cma(page))
198200
goto populate;
199-
if (1 != get_user_pages_fast(ua + (i << PAGE_SHIFT),
201+
if (1 != get_user_pages_fast(cur_ua,
200202
1/* pages */, 1/* iswrite */,
201203
&page)) {
202204
ret = -EFAULT;
@@ -210,20 +212,21 @@ long mm_iommu_get(struct mm_struct *mm, unsigned long ua, unsigned long entries,
210212
}
211213
populate:
212214
pageshift = PAGE_SHIFT;
213-
if (PageCompound(page)) {
215+
if (mem->pageshift > PAGE_SHIFT && PageCompound(page)) {
214216
pte_t *pte;
215217
struct page *head = compound_head(page);
216218
unsigned int compshift = compound_order(head);
219+
unsigned int pteshift;
217220

218221
local_irq_save(flags); /* disables as well */
219-
pte = find_linux_pte(mm->pgd, ua, NULL, &pageshift);
220-
local_irq_restore(flags);
222+
pte = find_linux_pte(mm->pgd, cur_ua, NULL, &pteshift);
221223

222224
/* Double check it is still the same pinned page */
223225
if (pte && pte_page(*pte) == head &&
224-
pageshift == compshift)
225-
pageshift = max_t(unsigned int, pageshift,
226+
pteshift == compshift + PAGE_SHIFT)
227+
pageshift = max_t(unsigned int, pteshift,
226228
PAGE_SHIFT);
229+
local_irq_restore(flags);
227230
}
228231
mem->pageshift = min(mem->pageshift, pageshift);
229232
mem->hpas[i] = page_to_pfn(page) << PAGE_SHIFT;

0 commit comments

Comments
 (0)