[v2] vfio/type1: check pfn valid before converting to struct page

Message ID 20230519065843.10653-1-yan.y.zhao@intel.com
State New
Headers
Series [v2] vfio/type1: check pfn valid before converting to struct page |

Commit Message

Yan Zhao May 19, 2023, 6:58 a.m. UTC
  Check physical PFN is valid before converting the PFN to a struct page
pointer to be returned to caller of vfio_pin_pages().

vfio_pin_pages() pins user pages with contiguous IOVA.
If the IOVA of a user page to be pinned belongs to vma of vm_flags
VM_PFNMAP, pin_user_pages_remote() will return -EFAULT without returning
struct page address for this PFN. This is because usually this kind of PFN
(e.g. MMIO PFN) has no valid struct page address associated.
Upon this error, vaddr_get_pfns() will obtain the physical PFN directly.

While previously vfio_pin_pages() returns to caller PFN arrays directly,
after commit
34a255e67615 ("vfio: Replace phys_pfn with pages for vfio_pin_pages()"),
PFNs will be converted to "struct page *" unconditionally and therefore
the returned "struct page *" array may contain invalid struct page
addresses.

Given current in-tree users of vfio_pin_pages() only expect "struct page *
returned, check PFN validity and return -EINVAL to let the caller be
aware of IOVAs to be pinned containing PFN not able to be returned in
"struct page *" array. So that, the caller will not consume the returned
pointer (e.g. test PageReserved()) and avoid error like "supervisor read
access in kernel mode".

Fixes: 34a255e67615 ("vfio: Replace phys_pfn with pages for vfio_pin_pages()")
Cc: Sean Christopherson <seanjc@google.com>
Reviewed-by: Jason Gunthorpe <jgg@nvidia.com>
Signed-off-by: Yan Zhao <yan.y.zhao@intel.com>

---
v2: update commit message to explain background/problem clearly. (Sean)
---
 drivers/vfio/vfio_iommu_type1.c | 5 +++++
 1 file changed, 5 insertions(+)


base-commit: b3c98052d46948a8d65d2778c7f306ff38366aac
  

Comments

Sean Christopherson May 19, 2023, 3:52 p.m. UTC | #1
On Fri, May 19, 2023, Yan Zhao wrote:
> Check physical PFN is valid before converting the PFN to a struct page
> pointer to be returned to caller of vfio_pin_pages().
> 
> vfio_pin_pages() pins user pages with contiguous IOVA.
> If the IOVA of a user page to be pinned belongs to vma of vm_flags
> VM_PFNMAP, pin_user_pages_remote() will return -EFAULT without returning
> struct page address for this PFN. This is because usually this kind of PFN
> (e.g. MMIO PFN) has no valid struct page address associated.
> Upon this error, vaddr_get_pfns() will obtain the physical PFN directly.
> 
> While previously vfio_pin_pages() returns to caller PFN arrays directly,
> after commit
> 34a255e67615 ("vfio: Replace phys_pfn with pages for vfio_pin_pages()"),
> PFNs will be converted to "struct page *" unconditionally and therefore
> the returned "struct page *" array may contain invalid struct page
> addresses.
> 
> Given current in-tree users of vfio_pin_pages() only expect "struct page *
> returned, check PFN validity and return -EINVAL to let the caller be
> aware of IOVAs to be pinned containing PFN not able to be returned in
> "struct page *" array. So that, the caller will not consume the returned
> pointer (e.g. test PageReserved()) and avoid error like "supervisor read
> access in kernel mode".
> 
> Fixes: 34a255e67615 ("vfio: Replace phys_pfn with pages for vfio_pin_pages()")
> Cc: Sean Christopherson <seanjc@google.com>
> Reviewed-by: Jason Gunthorpe <jgg@nvidia.com>
> Signed-off-by: Yan Zhao <yan.y.zhao@intel.com>

Reviewed-by: Sean Christopherson <seanjc@google.com>
  
Alex Williamson May 22, 2023, 7 p.m. UTC | #2
On Fri, 19 May 2023 14:58:43 +0800
Yan Zhao <yan.y.zhao@intel.com> wrote:

> Check physical PFN is valid before converting the PFN to a struct page
> pointer to be returned to caller of vfio_pin_pages().
> 
> vfio_pin_pages() pins user pages with contiguous IOVA.
> If the IOVA of a user page to be pinned belongs to vma of vm_flags
> VM_PFNMAP, pin_user_pages_remote() will return -EFAULT without returning
> struct page address for this PFN. This is because usually this kind of PFN
> (e.g. MMIO PFN) has no valid struct page address associated.
> Upon this error, vaddr_get_pfns() will obtain the physical PFN directly.
> 
> While previously vfio_pin_pages() returns to caller PFN arrays directly,
> after commit
> 34a255e67615 ("vfio: Replace phys_pfn with pages for vfio_pin_pages()"),
> PFNs will be converted to "struct page *" unconditionally and therefore
> the returned "struct page *" array may contain invalid struct page
> addresses.
> 
> Given current in-tree users of vfio_pin_pages() only expect "struct page *
> returned, check PFN validity and return -EINVAL to let the caller be
> aware of IOVAs to be pinned containing PFN not able to be returned in
> "struct page *" array. So that, the caller will not consume the returned
> pointer (e.g. test PageReserved()) and avoid error like "supervisor read
> access in kernel mode".
> 
> Fixes: 34a255e67615 ("vfio: Replace phys_pfn with pages for vfio_pin_pages()")
> Cc: Sean Christopherson <seanjc@google.com>
> Reviewed-by: Jason Gunthorpe <jgg@nvidia.com>
> Signed-off-by: Yan Zhao <yan.y.zhao@intel.com>
> 
> ---
> v2: update commit message to explain background/problem clearly. (Sean)
> ---
>  drivers/vfio/vfio_iommu_type1.c | 5 +++++
>  1 file changed, 5 insertions(+)
> 
> diff --git a/drivers/vfio/vfio_iommu_type1.c b/drivers/vfio/vfio_iommu_type1.c
> index 493c31de0edb..0620dbe5cca0 100644
> --- a/drivers/vfio/vfio_iommu_type1.c
> +++ b/drivers/vfio/vfio_iommu_type1.c
> @@ -860,6 +860,11 @@ static int vfio_iommu_type1_pin_pages(void *iommu_data,
>  		if (ret)
>  			goto pin_unwind;
>  
> +		if (!pfn_valid(phys_pfn)) {

Why wouldn't we use our is_invalid_reserved_pfn() test here?  Doing
so would also make it more consistent why we don't need to call
put_pfn() or rewind accounting for this page.  Thanks,

Alex

> +			ret = -EINVAL;
> +			goto pin_unwind;
> +		}
> +
>  		ret = vfio_add_to_pfn_list(dma, iova, phys_pfn);
>  		if (ret) {
>  			if (put_pfn(phys_pfn, dma->prot) && do_accounting)
> 
> base-commit: b3c98052d46948a8d65d2778c7f306ff38366aac
  
Yan Zhao May 23, 2023, 5:48 a.m. UTC | #3
On Mon, May 22, 2023 at 01:00:30PM -0600, Alex Williamson wrote:
> On Fri, 19 May 2023 14:58:43 +0800
> Yan Zhao <yan.y.zhao@intel.com> wrote:
> 
> > Check physical PFN is valid before converting the PFN to a struct page
> > pointer to be returned to caller of vfio_pin_pages().
> > 
> > vfio_pin_pages() pins user pages with contiguous IOVA.
> > If the IOVA of a user page to be pinned belongs to vma of vm_flags
> > VM_PFNMAP, pin_user_pages_remote() will return -EFAULT without returning
> > struct page address for this PFN. This is because usually this kind of PFN
> > (e.g. MMIO PFN) has no valid struct page address associated.
> > Upon this error, vaddr_get_pfns() will obtain the physical PFN directly.
> > 
> > While previously vfio_pin_pages() returns to caller PFN arrays directly,
> > after commit
> > 34a255e67615 ("vfio: Replace phys_pfn with pages for vfio_pin_pages()"),
> > PFNs will be converted to "struct page *" unconditionally and therefore
> > the returned "struct page *" array may contain invalid struct page
> > addresses.
> > 
> > Given current in-tree users of vfio_pin_pages() only expect "struct page *
> > returned, check PFN validity and return -EINVAL to let the caller be
> > aware of IOVAs to be pinned containing PFN not able to be returned in
> > "struct page *" array. So that, the caller will not consume the returned
> > pointer (e.g. test PageReserved()) and avoid error like "supervisor read
> > access in kernel mode".
> > 
> > Fixes: 34a255e67615 ("vfio: Replace phys_pfn with pages for vfio_pin_pages()")
> > Cc: Sean Christopherson <seanjc@google.com>
> > Reviewed-by: Jason Gunthorpe <jgg@nvidia.com>
> > Signed-off-by: Yan Zhao <yan.y.zhao@intel.com>
> > 
> > ---
> > v2: update commit message to explain background/problem clearly. (Sean)
> > ---
> >  drivers/vfio/vfio_iommu_type1.c | 5 +++++
> >  1 file changed, 5 insertions(+)
> > 
> > diff --git a/drivers/vfio/vfio_iommu_type1.c b/drivers/vfio/vfio_iommu_type1.c
> > index 493c31de0edb..0620dbe5cca0 100644
> > --- a/drivers/vfio/vfio_iommu_type1.c
> > +++ b/drivers/vfio/vfio_iommu_type1.c
> > @@ -860,6 +860,11 @@ static int vfio_iommu_type1_pin_pages(void *iommu_data,
> >  		if (ret)
> >  			goto pin_unwind;
> >  
> > +		if (!pfn_valid(phys_pfn)) {
> 
> Why wouldn't we use our is_invalid_reserved_pfn() test here?  Doing
> so would also make it more consistent why we don't need to call
> put_pfn() or rewind accounting for this page.  Thanks,
> 
I actually struggled in choosing is_invalid_reserved_pfn() or
pfn_valid() when writing this patch.

Choosing pfn_valid() is because invalid PFN obviously cannot have
struct page address and it's a bug fix.

While declining reserved pages will have the IOVA range supported by
vfio_pin_pages() even more reduced. So I don't know if there's enough
justification to do so, given that (1) device zone memory usually has
PG_reserved set. (2) vm_normal_page() also contains reserved page.

Thanks
Yan

> 
> > +			ret = -EINVAL;
> > +			goto pin_unwind;
> > +		}
> > +
> >  		ret = vfio_add_to_pfn_list(dma, iova, phys_pfn);
> >  		if (ret) {
> >  			if (put_pfn(phys_pfn, dma->prot) && do_accounting)
> > 
> > base-commit: b3c98052d46948a8d65d2778c7f306ff38366aac
>
  
Alex Williamson May 23, 2023, 8:15 p.m. UTC | #4
On Tue, 23 May 2023 13:48:22 +0800
Yan Zhao <yan.y.zhao@intel.com> wrote:

> On Mon, May 22, 2023 at 01:00:30PM -0600, Alex Williamson wrote:
> > On Fri, 19 May 2023 14:58:43 +0800
> > Yan Zhao <yan.y.zhao@intel.com> wrote:
> >   
> > > Check physical PFN is valid before converting the PFN to a struct page
> > > pointer to be returned to caller of vfio_pin_pages().
> > > 
> > > vfio_pin_pages() pins user pages with contiguous IOVA.
> > > If the IOVA of a user page to be pinned belongs to vma of vm_flags
> > > VM_PFNMAP, pin_user_pages_remote() will return -EFAULT without returning
> > > struct page address for this PFN. This is because usually this kind of PFN
> > > (e.g. MMIO PFN) has no valid struct page address associated.
> > > Upon this error, vaddr_get_pfns() will obtain the physical PFN directly.
> > > 
> > > While previously vfio_pin_pages() returns to caller PFN arrays directly,
> > > after commit
> > > 34a255e67615 ("vfio: Replace phys_pfn with pages for vfio_pin_pages()"),
> > > PFNs will be converted to "struct page *" unconditionally and therefore
> > > the returned "struct page *" array may contain invalid struct page
> > > addresses.
> > > 
> > > Given current in-tree users of vfio_pin_pages() only expect "struct page *
> > > returned, check PFN validity and return -EINVAL to let the caller be
> > > aware of IOVAs to be pinned containing PFN not able to be returned in
> > > "struct page *" array. So that, the caller will not consume the returned
> > > pointer (e.g. test PageReserved()) and avoid error like "supervisor read
> > > access in kernel mode".
> > > 
> > > Fixes: 34a255e67615 ("vfio: Replace phys_pfn with pages for vfio_pin_pages()")
> > > Cc: Sean Christopherson <seanjc@google.com>
> > > Reviewed-by: Jason Gunthorpe <jgg@nvidia.com>
> > > Signed-off-by: Yan Zhao <yan.y.zhao@intel.com>
> > > 
> > > ---
> > > v2: update commit message to explain background/problem clearly. (Sean)
> > > ---
> > >  drivers/vfio/vfio_iommu_type1.c | 5 +++++
> > >  1 file changed, 5 insertions(+)
> > > 
> > > diff --git a/drivers/vfio/vfio_iommu_type1.c b/drivers/vfio/vfio_iommu_type1.c
> > > index 493c31de0edb..0620dbe5cca0 100644
> > > --- a/drivers/vfio/vfio_iommu_type1.c
> > > +++ b/drivers/vfio/vfio_iommu_type1.c
> > > @@ -860,6 +860,11 @@ static int vfio_iommu_type1_pin_pages(void *iommu_data,
> > >  		if (ret)
> > >  			goto pin_unwind;
> > >  
> > > +		if (!pfn_valid(phys_pfn)) {  
> > 
> > Why wouldn't we use our is_invalid_reserved_pfn() test here?  Doing
> > so would also make it more consistent why we don't need to call
> > put_pfn() or rewind accounting for this page.  Thanks,
> >   
> I actually struggled in choosing is_invalid_reserved_pfn() or
> pfn_valid() when writing this patch.
> 
> Choosing pfn_valid() is because invalid PFN obviously cannot have
> struct page address and it's a bug fix.
> 
> While declining reserved pages will have the IOVA range supported by
> vfio_pin_pages() even more reduced. So I don't know if there's enough
> justification to do so, given that (1) device zone memory usually has
> PG_reserved set. (2) vm_normal_page() also contains reserved page.

Based on the exclusion we have in vaddr_get_pfn() where we unpin
zero-page pfns because they hit on the is_invalid_reserved_pfn() test
and break our accounting otherwise, this does seem like the correct
choice.  I can imagine a scenario where the device wants to do a DMA
read from VM memory backed by the zero page.  Ok.  Thanks,

Alex
  
Alex Williamson May 23, 2023, 9:44 p.m. UTC | #5
On Fri, 19 May 2023 14:58:43 +0800
Yan Zhao <yan.y.zhao@intel.com> wrote:

> Check physical PFN is valid before converting the PFN to a struct page
> pointer to be returned to caller of vfio_pin_pages().
> 
> vfio_pin_pages() pins user pages with contiguous IOVA.
> If the IOVA of a user page to be pinned belongs to vma of vm_flags
> VM_PFNMAP, pin_user_pages_remote() will return -EFAULT without returning
> struct page address for this PFN. This is because usually this kind of PFN
> (e.g. MMIO PFN) has no valid struct page address associated.
> Upon this error, vaddr_get_pfns() will obtain the physical PFN directly.
> 
> While previously vfio_pin_pages() returns to caller PFN arrays directly,
> after commit
> 34a255e67615 ("vfio: Replace phys_pfn with pages for vfio_pin_pages()"),
> PFNs will be converted to "struct page *" unconditionally and therefore
> the returned "struct page *" array may contain invalid struct page
> addresses.
> 
> Given current in-tree users of vfio_pin_pages() only expect "struct page *
> returned, check PFN validity and return -EINVAL to let the caller be
> aware of IOVAs to be pinned containing PFN not able to be returned in
> "struct page *" array. So that, the caller will not consume the returned
> pointer (e.g. test PageReserved()) and avoid error like "supervisor read
> access in kernel mode".
> 
> Fixes: 34a255e67615 ("vfio: Replace phys_pfn with pages for vfio_pin_pages()")
> Cc: Sean Christopherson <seanjc@google.com>
> Reviewed-by: Jason Gunthorpe <jgg@nvidia.com>
> Signed-off-by: Yan Zhao <yan.y.zhao@intel.com>
> 
> ---
> v2: update commit message to explain background/problem clearly. (Sean)
> ---
>  drivers/vfio/vfio_iommu_type1.c | 5 +++++
>  1 file changed, 5 insertions(+)
> 
> diff --git a/drivers/vfio/vfio_iommu_type1.c b/drivers/vfio/vfio_iommu_type1.c
> index 493c31de0edb..0620dbe5cca0 100644
> --- a/drivers/vfio/vfio_iommu_type1.c
> +++ b/drivers/vfio/vfio_iommu_type1.c
> @@ -860,6 +860,11 @@ static int vfio_iommu_type1_pin_pages(void *iommu_data,
>  		if (ret)
>  			goto pin_unwind;
>  
> +		if (!pfn_valid(phys_pfn)) {
> +			ret = -EINVAL;
> +			goto pin_unwind;
> +		}
> +
>  		ret = vfio_add_to_pfn_list(dma, iova, phys_pfn);
>  		if (ret) {
>  			if (put_pfn(phys_pfn, dma->prot) && do_accounting)
> 
> base-commit: b3c98052d46948a8d65d2778c7f306ff38366aac

Applied to vfio for-linus branch for v6.4.  Thanks!

Alex
  

Patch

diff --git a/drivers/vfio/vfio_iommu_type1.c b/drivers/vfio/vfio_iommu_type1.c
index 493c31de0edb..0620dbe5cca0 100644
--- a/drivers/vfio/vfio_iommu_type1.c
+++ b/drivers/vfio/vfio_iommu_type1.c
@@ -860,6 +860,11 @@  static int vfio_iommu_type1_pin_pages(void *iommu_data,
 		if (ret)
 			goto pin_unwind;
 
+		if (!pfn_valid(phys_pfn)) {
+			ret = -EINVAL;
+			goto pin_unwind;
+		}
+
 		ret = vfio_add_to_pfn_list(dma, iova, phys_pfn);
 		if (ret) {
 			if (put_pfn(phys_pfn, dma->prot) && do_accounting)