Starting a virtio backend in a PV domain would panic the kernel in
alloc_ioreq, trying to dereference vma->vm_private_data as a pages
pointer when in reality it stayed as PRIV_VMA_LOCKED.

Avoid crashing by handling the PRIV_VMA_LOCKED case in alloc_ioreq.
PV support requires mapping the virtio ioreq page explicitly into
the kernel's page tables, so do it on-demand when PRIV_VMA_LOCKED
is seen.

Signed-off-by: Val Packett <[email protected]>
---

Hi. This was previously submitted as an RFC[1] but got no feedback
in many many months.. Let's try again :)

This is all that's needed to make the ioeventfd facility work with
a PV dom0, which we still use in Qubes OS (yeah yeah we should move
to PVH dom0 but there are still regressions to debug there).
So we would really like to get this merged.

[1]: 
https://lore.kernel.org/all/[email protected]/

Thanks,
~val

---
 drivers/xen/privcmd.c | 34 ++++++++++++++++++++++++++++++++--
 1 file changed, 32 insertions(+), 2 deletions(-)

diff --git a/drivers/xen/privcmd.c b/drivers/xen/privcmd.c
index 725a49a0eee7..05775a518948 100644
--- a/drivers/xen/privcmd.c
+++ b/drivers/xen/privcmd.c
@@ -833,6 +833,8 @@ static long privcmd_ioctl_mmap_resource(struct file *file,
                        DOMID_SELF : kdata.dom;
                int num, *errs = (int *)pfns;
 
+               vma->vm_pgoff = pfns[0]; /* store the acquired pfn for 
ioeventfd access */
+
                BUILD_BUG_ON(sizeof(*errs) > sizeof(*pfns));
                num = xen_remap_domain_mfn_array(vma,
                                                 kdata.addr & PAGE_MASK,
@@ -1264,10 +1266,38 @@ struct privcmd_kernel_ioreq *alloc_ioreq(struct 
privcmd_ioeventfd *ioeventfd)
                goto error_kfree;
        }
 
-       pages = vma->vm_private_data;
-       kioreq->ioreq = (struct ioreq *)(page_to_virt(pages[0]));
        mmap_write_unlock(mm);
 
+       /* In a PV domain, we must manually map the pages into the kernel */
+       if (vma->vm_private_data == PRIV_VMA_LOCKED) {
+               /* This should never ever happen outside of PV */
+               if (WARN_ON_ONCE(!xen_pv_domain())) {
+                       ret = -EINVAL;
+                       goto error_kfree;
+               }
+
+               /* xen_remap_domain_mfn_array only really needs the mm */
+               struct vm_area_struct kern_vma = {
+                       .vm_flags = VM_PFNMAP | VM_IO,
+                       .vm_mm = &init_mm,
+               };
+               xen_pfn_t pfn = vma->vm_pgoff;
+               int num, err;
+
+               /* Don't provide NULL as the errors array as that results in 
pfn increment */
+               num = xen_remap_domain_mfn_array(&kern_vma, (unsigned 
long)pfn_to_kaddr(pfn),
+                                               &pfn, 1, &err, PAGE_KERNEL, 
ioeventfd->dom);
+               if (num < 0) {
+                       ret = num;
+                       goto error_kfree;
+               }
+
+               kioreq->ioreq = (struct ioreq *)(pfn_to_kaddr(pfn));
+       } else {
+               pages = vma->vm_private_data;
+               kioreq->ioreq = (struct ioreq *)(page_to_virt(pages[0]));
+       }
+
        ports = memdup_array_user(u64_to_user_ptr(ioeventfd->ports),
                                  kioreq->vcpus, sizeof(*ports));
        if (IS_ERR(ports)) {
-- 
2.54.0


Reply via email to