vhost_vdpa_mmap() and vhost_vdpa_fault() use vma->vm_pgoff as a
virtqueue index for get_vq_notification(), but they do not validate
that the index is smaller than v->nvqs.

The ioctl path already performs both a bounds check and
array_index_nospec(), but the mmap/fault path only checks that the
index fits in u16. This allows an out-of-range queue index to reach
driver-specific get_vq_notification() callbacks.

Fix this by extracting a unified vhost_vdpa_get_vq_notification()
helper that validates the queue index against v->nvqs and applies
array_index_nospec() before calling the driver callback. Both the
mmap and fault paths use this helper, and the bounds checking is
consolidated into a single location.

>From source inspection, the most defensible impact is out-of-bounds
access in the callback path, potentially leading to invalid PFN
remaps and crash/DoS.

Fixes: ddd89d0a059d ("vhost_vdpa: support doorbell mapping via mmap")
Acked-by: Eugenio PĂ©rez <[email protected]>
Acked-by: Michael S. Tsirkin <[email protected]>
Signed-off-by: Qihang Tang <[email protected]>
---
 drivers/vhost/vdpa.c | 29 ++++++++++++++++++++++-------
 1 file changed, 22 insertions(+), 7 deletions(-)

diff --git a/drivers/vhost/vdpa.c b/drivers/vhost/vdpa.c
index 692564b1bcbb..ac55275fa0d0 100644
--- a/drivers/vhost/vdpa.c
+++ b/drivers/vhost/vdpa.c
@@ -1482,16 +1482,32 @@ static int vhost_vdpa_release(struct inode *inode, 
struct file *filep)
 }
 
 #ifdef CONFIG_MMU
-static vm_fault_t vhost_vdpa_fault(struct vm_fault *vmf)
+static int
+vhost_vdpa_get_vq_notification(struct vhost_vdpa *v, unsigned long index,
+                              struct vdpa_notification_area *notify)
 {
-       struct vhost_vdpa *v = vmf->vma->vm_file->private_data;
        struct vdpa_device *vdpa = v->vdpa;
        const struct vdpa_config_ops *ops = vdpa->config;
+
+       if (index > 65535 || index >= v->nvqs)
+               return -EINVAL;
+
+       index = array_index_nospec(index, v->nvqs);
+
+       *notify = ops->get_vq_notification(vdpa, index);
+
+       return 0;
+}
+
+static vm_fault_t vhost_vdpa_fault(struct vm_fault *vmf)
+{
+       struct vhost_vdpa *v = vmf->vma->vm_file->private_data;
        struct vdpa_notification_area notify;
        struct vm_area_struct *vma = vmf->vma;
-       u16 index = vma->vm_pgoff;
+       unsigned long index = vma->vm_pgoff;
 
-       notify = ops->get_vq_notification(vdpa, index);
+       if (vhost_vdpa_get_vq_notification(v, index, &notify))
+               return VM_FAULT_SIGBUS;
 
        return vmf_insert_pfn(vma, vmf->address & PAGE_MASK, 
PFN_DOWN(notify.addr));
 }
@@ -1514,8 +1530,6 @@ static int vhost_vdpa_mmap(struct file *file, struct 
vm_area_struct *vma)
                return -EINVAL;
        if (vma->vm_flags & VM_READ)
                return -EINVAL;
-       if (index > 65535)
-               return -EINVAL;
        if (!ops->get_vq_notification)
                return -ENOTSUPP;
 
@@ -1523,7 +1537,8 @@ static int vhost_vdpa_mmap(struct file *file, struct 
vm_area_struct *vma)
         * support the doorbell which sits on the page boundary and
         * does not share the page with other registers.
         */
-       notify = ops->get_vq_notification(vdpa, index);
+       if (vhost_vdpa_get_vq_notification(v, index, &notify))
+               return -EINVAL;
        if (notify.addr & (PAGE_SIZE - 1))
                return -EINVAL;
        if (vma->vm_end - vma->vm_start != notify.size)
-- 
2.39.5 (Apple Git-154)


Reply via email to