diff mbox series

[RFT,v1,5/6] vfio/ccw: Add kmap_local_page() for memcpy

Message ID 20220616235212.15185-6-nicolinc@nvidia.com (mailing list archive)
State New, archived
Headers show
Series Update vfio_pin/unpin_pages API | expand

Commit Message

Nicolin Chen June 16, 2022, 11:52 p.m. UTC
The pinned PFN list returned from vfio_pin_pages() is simply converted
using page_to_pfn() without protection, so direct access via memcpy()
will crash on S390 if the PFN is an IO PFN. Instead, the pages should
be touched using kmap_local_page().

Add kmap_local_page() before doing memcpy on "from".

Suggested-by: Jason Gunthorpe <jgg@nvidia.com>
Signed-off-by: Nicolin Chen <nicolinc@nvidia.com>
---
 drivers/s390/cio/vfio_ccw_cp.c | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

Comments

Christoph Hellwig June 17, 2022, 8:44 a.m. UTC | #1
On Thu, Jun 16, 2022 at 04:52:11PM -0700, Nicolin Chen wrote:
> The pinned PFN list returned from vfio_pin_pages() is simply converted
> using page_to_pfn() without protection, so direct access via memcpy()
> will crash on S390 if the PFN is an IO PFN. Instead, the pages should
> be touched using kmap_local_page().

I don't see how this helps.  kmap_local_page only works for either
pages in the kernel direct map or highmem, but not for memory that needs
to be ioremapped.  And there is no highmem on s390.
Nicolin Chen June 17, 2022, 9:58 p.m. UTC | #2
On Fri, Jun 17, 2022 at 01:44:30AM -0700, Christoph Hellwig wrote:
> On Thu, Jun 16, 2022 at 04:52:11PM -0700, Nicolin Chen wrote:
> > The pinned PFN list returned from vfio_pin_pages() is simply converted
> > using page_to_pfn() without protection, so direct access via memcpy()
> > will crash on S390 if the PFN is an IO PFN. Instead, the pages should
> > be touched using kmap_local_page().
> 
> I don't see how this helps.  kmap_local_page only works for either
> pages in the kernel direct map or highmem, but not for memory that needs
> to be ioremapped.  And there is no highmem on s390.

Oh..I will discuss with Jason once he's back, to see if we should
simply drop this change. Thanks!
Jason Gunthorpe June 20, 2022, 2:57 a.m. UTC | #3
On Fri, Jun 17, 2022 at 01:44:30AM -0700, Christoph Hellwig wrote:
> On Thu, Jun 16, 2022 at 04:52:11PM -0700, Nicolin Chen wrote:
> > The pinned PFN list returned from vfio_pin_pages() is simply converted
> > using page_to_pfn() without protection, so direct access via memcpy()
> > will crash on S390 if the PFN is an IO PFN. Instead, the pages should
> > be touched using kmap_local_page().
> 
> I don't see how this helps.  kmap_local_page only works for either
> pages in the kernel direct map or highmem, but not for memory that needs
> to be ioremapped.  And there is no highmem on s390.

The remark about io memory is because on s390 memcpy() will crash even
on ioremapped memory, you have to use the memcpy_to/fromio() which
uses the special s390 io access instructions.

This helps because we now block io memory from ever getting into these
call paths. I'm pretty sure this is a serious security bug, but would
let the IBM folks remark as I don't know it all that well..

As for the kmap, I thought it was standard practice even if it is a
non-highmem? Aren't people trying to use this for other security
stuff these days?

Jason
Christoph Hellwig June 20, 2022, 6:32 a.m. UTC | #4
On Sun, Jun 19, 2022 at 11:57:26PM -0300, Jason Gunthorpe wrote:
> The remark about io memory is because on s390 memcpy() will crash even
> on ioremapped memory, you have to use the memcpy_to/fromio() which
> uses the special s390 io access instructions.

Yes.  The same is true for various other architectures, inluding arm64
under the right circumstances.

> This helps because we now block io memory from ever getting into these
> call paths. I'm pretty sure this is a serious security bug, but would
> let the IBM folks remark as I don't know it all that well..

Prevent as in crash when trying to convert it to a page?

> As for the kmap, I thought it was standard practice even if it is a
> non-highmem? Aren't people trying to use this for other security
> stuff these days?

Ira has been lookin into the protection keys, although they don't
apply to s390.  Either way I don't object to using kmap, but the
commit log doesn't make much sense to me.
Jason Gunthorpe June 20, 2022, 3:39 p.m. UTC | #5
On Sun, Jun 19, 2022 at 11:32:07PM -0700, Christoph Hellwig wrote:

> > This helps because we now block io memory from ever getting into these
> > call paths. I'm pretty sure this is a serious security bug, but would
> > let the IBM folks remark as I don't know it all that well..
> 
> Prevent as in crash when trying to convert it to a page?

That or when calling memcpy() on an IO memory PFN that the guest
passed into the dma s/g list the ccw driver is processing.

Jason
Nicolin Chen June 21, 2022, 9:21 p.m. UTC | #6
On Sun, Jun 19, 2022 at 11:32:07PM -0700, Christoph Hellwig wrote:
> On Sun, Jun 19, 2022 at 11:57:26PM -0300, Jason Gunthorpe wrote:
> > The remark about io memory is because on s390 memcpy() will crash even
> > on ioremapped memory, you have to use the memcpy_to/fromio() which
> > uses the special s390 io access instructions.
> 
> Yes.  The same is true for various other architectures, inluding arm64
> under the right circumstances.
> 
> > This helps because we now block io memory from ever getting into these
> > call paths. I'm pretty sure this is a serious security bug, but would
> > let the IBM folks remark as I don't know it all that well..
> 
> Prevent as in crash when trying to convert it to a page?
> 
> > As for the kmap, I thought it was standard practice even if it is a
> > non-highmem? Aren't people trying to use this for other security
> > stuff these days?
> 
> Ira has been lookin into the protection keys, although they don't
> apply to s390.  Either way I don't object to using kmap, but the
> commit log doesn't make much sense to me.

How about the updated commit log below? Thanks.

The pinned PFN list returned from vfio_pin_pages() is converted using
page_to_pfn(), so direct access via memcpy() will crash on S390 if the
PFN is an IO PFN, as we have to use the memcpy_to/fromio(), which uses
the special s390 IO access instructions.

As a standard practice for security purpose, add kmap_local_page() to
block any IO memory from ever getting into this call path.
Jason Gunthorpe June 24, 2022, 1:56 p.m. UTC | #7
On Tue, Jun 21, 2022 at 02:21:22PM -0700, Nicolin Chen wrote:
> On Sun, Jun 19, 2022 at 11:32:07PM -0700, Christoph Hellwig wrote:
> > On Sun, Jun 19, 2022 at 11:57:26PM -0300, Jason Gunthorpe wrote:
> > > The remark about io memory is because on s390 memcpy() will crash even
> > > on ioremapped memory, you have to use the memcpy_to/fromio() which
> > > uses the special s390 io access instructions.
> > 
> > Yes.  The same is true for various other architectures, inluding arm64
> > under the right circumstances.
> > 
> > > This helps because we now block io memory from ever getting into these
> > > call paths. I'm pretty sure this is a serious security bug, but would
> > > let the IBM folks remark as I don't know it all that well..
> > 
> > Prevent as in crash when trying to convert it to a page?
> > 
> > > As for the kmap, I thought it was standard practice even if it is a
> > > non-highmem? Aren't people trying to use this for other security
> > > stuff these days?
> > 
> > Ira has been lookin into the protection keys, although they don't
> > apply to s390.  Either way I don't object to using kmap, but the
> > commit log doesn't make much sense to me.
> 
> How about the updated commit log below? Thanks.
> 
> The pinned PFN list returned from vfio_pin_pages() is converted using
> page_to_pfn(), so direct access via memcpy() will crash on S390 if the
> PFN is an IO PFN, as we have to use the memcpy_to/fromio(), which uses
> the special s390 IO access instructions.
> 
> As a standard practice for security purpose, add kmap_local_page() to
> block any IO memory from ever getting into this call path.

The kmap_local_page is not about the IO memory, the switch to struct
page is what is protecting against IO memory.

Use kmap_local_page() is just the correct way to convert a struct page
into a CPU address to use with memcpy and it is a NOP on S390 because
it doesn't use highmem/etc.

Jason
Nicolin Chen June 24, 2022, 7:22 p.m. UTC | #8
On Fri, Jun 24, 2022 at 10:56:15AM -0300, Jason Gunthorpe wrote:

> > How about the updated commit log below? Thanks.
> > 
> > The pinned PFN list returned from vfio_pin_pages() is converted using
> > page_to_pfn(), so direct access via memcpy() will crash on S390 if the
> > PFN is an IO PFN, as we have to use the memcpy_to/fromio(), which uses
> > the special s390 IO access instructions.
> > 
> > As a standard practice for security purpose, add kmap_local_page() to
> > block any IO memory from ever getting into this call path.
> 
> The kmap_local_page is not about the IO memory, the switch to struct
> page is what is protecting against IO memory.
> 
> Use kmap_local_page() is just the correct way to convert a struct page
> into a CPU address to use with memcpy and it is a NOP on S390 because
> it doesn't use highmem/etc.

I thought the whole purpose of switching to "struct page *" was to use
kmap_local_page() for the memcpy call, and the combination of these two
does the protection. Do you mind explaining how the switching part does
the protection?

Thanks!
Jason Gunthorpe June 24, 2022, 7:30 p.m. UTC | #9
On Fri, Jun 24, 2022 at 12:22:36PM -0700, Nicolin Chen wrote:
> On Fri, Jun 24, 2022 at 10:56:15AM -0300, Jason Gunthorpe wrote:
> 
> > > How about the updated commit log below? Thanks.
> > > 
> > > The pinned PFN list returned from vfio_pin_pages() is converted using
> > > page_to_pfn(), so direct access via memcpy() will crash on S390 if the
> > > PFN is an IO PFN, as we have to use the memcpy_to/fromio(), which uses
> > > the special s390 IO access instructions.
> > > 
> > > As a standard practice for security purpose, add kmap_local_page() to
> > > block any IO memory from ever getting into this call path.
> > 
> > The kmap_local_page is not about the IO memory, the switch to struct
> > page is what is protecting against IO memory.
> > 
> > Use kmap_local_page() is just the correct way to convert a struct page
> > into a CPU address to use with memcpy and it is a NOP on S390 because
> > it doesn't use highmem/etc.
> 
> I thought the whole purpose of switching to "struct page *" was to use
> kmap_local_page() for the memcpy call, and the combination of these two
> does the protection. Do you mind explaining how the switching part does
> the protection?

A 'struct page' (ignoring ZONE_DEVICE) cannot represent IO memory
inherently because a 'struct page' is always a CPU coherent thing.

So, when VFIO returns only struct pages it also is promising that the
memory is not IO.

The kmap_local_page() arose because the code doing memcpy had to be
updated to go from a struct page to a void * for use with memcpy and
the kmap_local_page() is the correct API to use for that.

The existing code which casts a pfn to a void * is improper.

Jason
Nicolin Chen June 24, 2022, 8:12 p.m. UTC | #10
On Fri, Jun 24, 2022 at 04:30:42PM -0300, Jason Gunthorpe wrote:
> On Fri, Jun 24, 2022 at 12:22:36PM -0700, Nicolin Chen wrote:
> > On Fri, Jun 24, 2022 at 10:56:15AM -0300, Jason Gunthorpe wrote:
> > 
> > > > How about the updated commit log below? Thanks.
> > > > 
> > > > The pinned PFN list returned from vfio_pin_pages() is converted using
> > > > page_to_pfn(), so direct access via memcpy() will crash on S390 if the
> > > > PFN is an IO PFN, as we have to use the memcpy_to/fromio(), which uses
> > > > the special s390 IO access instructions.
> > > > 
> > > > As a standard practice for security purpose, add kmap_local_page() to
> > > > block any IO memory from ever getting into this call path.
> > > 
> > > The kmap_local_page is not about the IO memory, the switch to struct
> > > page is what is protecting against IO memory.
> > > 
> > > Use kmap_local_page() is just the correct way to convert a struct page
> > > into a CPU address to use with memcpy and it is a NOP on S390 because
> > > it doesn't use highmem/etc.
> > 
> > I thought the whole purpose of switching to "struct page *" was to use
> > kmap_local_page() for the memcpy call, and the combination of these two
> > does the protection. Do you mind explaining how the switching part does
> > the protection?
> 
> A 'struct page' (ignoring ZONE_DEVICE) cannot represent IO memory
> inherently because a 'struct page' is always a CPU coherent thing.
> 
> So, when VFIO returns only struct pages it also is promising that the
> memory is not IO.

Ah. The "switch to struct page" means the next patch that changes the
vfio_pin_pages, not the pfn_to_page in this patch.

> The kmap_local_page() arose because the code doing memcpy had to be
> updated to go from a struct page to a void * for use with memcpy and
> the kmap_local_page() is the correct API to use for that.
> 
> The existing code which casts a pfn to a void * is improper.

Yes.

If I understand everything correctly:

A PFN is not secure enough to promise that the memory is not IO. And
direct access via memcpy() that only handles CPU memory will crash on
S390 if the PFN is an IO PFN, as we have to use the memcpy_to/fromio()
that uses the special S390 IO access instructions. On the other hand,
a "struct page *" is always a CPU coherent thing that fits memcpy().

Also, casting a PFN to "void *" for memcpy() is not an proper practice,
kmap_local_page() is the correct API to call here, though S390 doesn't
use highmem, which means kmap_local_page() is a NOP.

There's a following patch changing the vfio_pin_pages() API to return
a list of "struct page *" instead of PFNs. It will block any IO memory
from ever getting into this call path, for such a security purpose. In
this patch, add kmap_local_page() to prepare for that.
Jason Gunthorpe June 24, 2022, 10:42 p.m. UTC | #11
On Fri, Jun 24, 2022 at 01:12:56PM -0700, Nicolin Chen wrote:

> > The kmap_local_page() arose because the code doing memcpy had to be
> > updated to go from a struct page to a void * for use with memcpy and
> > the kmap_local_page() is the correct API to use for that.
> > 
> > The existing code which casts a pfn to a void * is improper.
> 
> Yes.
> 
> If I understand everything correctly:
> 
> A PFN is not secure enough to promise that the memory is not IO. And
> direct access via memcpy() that only handles CPU memory will crash on
> S390 if the PFN is an IO PFN, as we have to use the memcpy_to/fromio()
> that uses the special S390 IO access instructions. On the other hand,
> a "struct page *" is always a CPU coherent thing that fits memcpy().
> 
> Also, casting a PFN to "void *" for memcpy() is not an proper practice,
> kmap_local_page() is the correct API to call here, though S390 doesn't
> use highmem, which means kmap_local_page() is a NOP.
> 
> There's a following patch changing the vfio_pin_pages() API to return
> a list of "struct page *" instead of PFNs. It will block any IO memory
> from ever getting into this call path, for such a security purpose. In
> this patch, add kmap_local_page() to prepare for that.

Yes, basically

Jason
diff mbox series

Patch

diff --git a/drivers/s390/cio/vfio_ccw_cp.c b/drivers/s390/cio/vfio_ccw_cp.c
index e2b01115b3ec..12cbe66721af 100644
--- a/drivers/s390/cio/vfio_ccw_cp.c
+++ b/drivers/s390/cio/vfio_ccw_cp.c
@@ -11,6 +11,7 @@ 
 #include <linux/ratelimit.h>
 #include <linux/mm.h>
 #include <linux/slab.h>
+#include <linux/highmem.h>
 #include <linux/iommu.h>
 #include <linux/vfio.h>
 #include <asm/idals.h>
@@ -235,7 +236,6 @@  static long copy_from_iova(struct vfio_device *vdev, void *to, u64 iova,
 			   unsigned long n)
 {
 	struct pfn_array pa = {0};
-	u64 from;
 	int i, ret;
 	unsigned long l, m;
 
@@ -251,7 +251,9 @@  static long copy_from_iova(struct vfio_device *vdev, void *to, u64 iova,
 
 	l = n;
 	for (i = 0; i < pa.pa_nr; i++) {
-		from = pa.pa_pfn[i] << PAGE_SHIFT;
+		struct page *page = pfn_to_page(pa.pa_pfn[i]);
+		void *from = kmap_local_page(page);
+
 		m = PAGE_SIZE;
 		if (i == 0) {
 			from += iova & (PAGE_SIZE - 1);
@@ -259,7 +261,8 @@  static long copy_from_iova(struct vfio_device *vdev, void *to, u64 iova,
 		}
 
 		m = min(l, m);
-		memcpy(to + (n - l), (void *)from, m);
+		memcpy(to + (n - l), from, m);
+		kunmap_local(from);
 
 		l -= m;
 		if (l == 0)