diff options
author | Daniel Jordan <daniel.m.jordan@oracle.com> | 2021-03-25 04:05:52 +0300 |
---|---|---|
committer | Alex Williamson <alex.williamson@redhat.com> | 2021-03-25 21:48:38 +0300 |
commit | 60c988bc154108bd522a21289e389143006a1ff0 (patch) | |
tree | c970b68f92ec70b92e4e882fe2ebfb11ad0a0399 /drivers/vfio/vfio_iommu_type1.c | |
parent | 0d02ec6b3136c73c09e7859f0d0e4e2c4c07b49b (diff) | |
download | linux-60c988bc154108bd522a21289e389143006a1ff0.tar.xz |
vfio/type1: Empty batch for pfnmap pages
When vfio_pin_pages_remote() returns with a partial batch consisting of
a single VM_PFNMAP pfn, a subsequent call will unfortunately try
restoring it from batch->pages, resulting in vfio mapping the wrong page
and unbalancing the page refcount.
Prevent the function from returning with this kind of partial batch to
avoid the issue. There's no explicit check for a VM_PFNMAP pfn because
it's awkward to do so, so infer it from characteristics of the batch
instead. This may result in occasional false positives but keeps the
code simpler.
Fixes: 4d83de6da265 ("vfio/type1: Batch page pinning")
Link: https://lkml.kernel.org/r/20210323133254.33ed9161@omen.home.shazbot.org/
Reported-by: Alex Williamson <alex.williamson@redhat.com>
Suggested-by: Alex Williamson <alex.williamson@redhat.com>
Signed-off-by: Daniel Jordan <daniel.m.jordan@oracle.com>
Message-Id: <20210325010552.185481-1-daniel.m.jordan@oracle.com>
Signed-off-by: Alex Williamson <alex.williamson@redhat.com>
Diffstat (limited to 'drivers/vfio/vfio_iommu_type1.c')
-rw-r--r-- | drivers/vfio/vfio_iommu_type1.c | 6 |
1 files changed, 6 insertions, 0 deletions
diff --git a/drivers/vfio/vfio_iommu_type1.c b/drivers/vfio/vfio_iommu_type1.c index be444407664a..45cbfd4879a5 100644 --- a/drivers/vfio/vfio_iommu_type1.c +++ b/drivers/vfio/vfio_iommu_type1.c @@ -739,6 +739,12 @@ out: ret = vfio_lock_acct(dma, lock_acct, false); unpin_out: + if (batch->size == 1 && !batch->offset) { + /* May be a VM_PFNMAP pfn, which the batch can't remember. */ + put_pfn(pfn, dma->prot); + batch->size = 0; + } + if (ret < 0) { if (pinned && !rsvd) { for (pfn = *pfn_base ; pinned ; pfn++, pinned--) |