]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
vduse: fix compat handling for VDUSE_IOTLB_GET_FD/VDUSE_VQ_GET_INFO
authorArnd Bergmann <arnd@arndb.de>
Fri, 13 Feb 2026 15:40:46 +0000 (16:40 +0100)
committerMichael S. Tsirkin <mst@redhat.com>
Wed, 10 Jun 2026 06:16:59 +0000 (02:16 -0400)
These two ioctls are incompatible on 32-bit x86 userspace, because
the data structures are shorter than they are on 64-bit.

Add a proper .compat_ioctl handler for x86 that reads the structures
with the smaller padding before calling the internal handlers. On
all other architectures, CONFIG_COMPAT_FOR_U64_ALIGNMENT is disabled
and no special handling is required.

Fixes: ad146355bfad ("vduse: Support querying information of IOVA regions")
Fixes: c8a6153b6c59 ("vduse: Introduce VDUSE - vDPA Device in Userspace")
Acked-by: Eugenio Pérez <eperezma@redhat.com>
Signed-off-by: Arnd Bergmann <arnd@arndb.de>
Signed-off-by: Michael S. Tsirkin <mst@redhat.com>
Message-ID: <20260213154051.4172275-1-arnd@kernel.org>

drivers/vdpa/vdpa_user/vduse_dev.c

index 9ebbe654115708ef284999b105b2fc82781d1cd3..f15ad425e01faece20ac85ac8ede8a3871c98d5d 100644 (file)
@@ -1641,6 +1641,127 @@ static long vduse_dev_ioctl(struct file *file, unsigned int cmd,
        return ret;
 }
 
+#ifdef CONFIG_COMPAT_FOR_U64_ALIGNMENT
+/*
+ * i386 has different alignment constraints than x86_64,
+ * so there are only 3 bytes of padding instead of 7.
+ */
+struct compat_vduse_iotlb_entry {
+       compat_u64 offset;
+       compat_u64 start;
+       compat_u64 last;
+       __u8 perm;
+       __u8 padding[3];
+};
+#define COMPAT_VDUSE_IOTLB_GET_FD      _IOWR(VDUSE_BASE, 0x10, struct compat_vduse_iotlb_entry)
+
+struct compat_vduse_vq_info {
+       __u32 index;
+       __u32 num;
+       compat_u64 desc_addr;
+       compat_u64 driver_addr;
+       compat_u64 device_addr;
+       union {
+               struct vduse_vq_state_split split;
+               struct vduse_vq_state_packed packed;
+       };
+       __u8 ready;
+       __u8 padding[3];
+};
+#define COMPAT_VDUSE_VQ_GET_INFO       _IOWR(VDUSE_BASE, 0x15, struct compat_vduse_vq_info)
+
+static long vduse_dev_compat_ioctl(struct file *file, unsigned int cmd,
+                                  unsigned long arg)
+{
+       struct vduse_dev *dev = file->private_data;
+       void __user *argp = (void __user *)arg;
+       int ret;
+
+       if (unlikely(dev->broken))
+               return -EPERM;
+
+       switch (cmd) {
+       case COMPAT_VDUSE_IOTLB_GET_FD: {
+               struct vduse_iotlb_entry_v2 entry = {0};
+               struct file *f = NULL;
+
+               ret = -EFAULT;
+               if (copy_from_user(&entry, argp, _IOC_SIZE(cmd)))
+                       break;
+
+               ret = vduse_dev_iotlb_entry(dev, &entry, &f, NULL);
+               if (ret)
+                       break;
+
+               ret = -EINVAL;
+               if (!f)
+                       break;
+
+               ret = copy_to_user(argp, &entry, _IOC_SIZE(cmd));
+               if (ret) {
+                       ret = -EFAULT;
+                       fput(f);
+                       break;
+               }
+               ret = receive_fd(f, NULL, perm_to_file_flags(entry.perm));
+               fput(f);
+               break;
+       }
+       case COMPAT_VDUSE_VQ_GET_INFO: {
+               struct vduse_vq_info vq_info = {};
+               struct vduse_virtqueue *vq;
+               u32 index;
+
+               ret = -EFAULT;
+               if (copy_from_user(&vq_info, argp,
+                                  sizeof(struct compat_vduse_vq_info)))
+                       break;
+
+               ret = -EINVAL;
+               if (vq_info.index >= dev->vq_num)
+                       break;
+
+               index = array_index_nospec(vq_info.index, dev->vq_num);
+               vq = dev->vqs[index];
+               vq_info.desc_addr = vq->desc_addr;
+               vq_info.driver_addr = vq->driver_addr;
+               vq_info.device_addr = vq->device_addr;
+               vq_info.num = vq->num;
+
+               if (dev->driver_features & BIT_ULL(VIRTIO_F_RING_PACKED)) {
+                       vq_info.packed.last_avail_counter =
+                               vq->state.packed.last_avail_counter;
+                       vq_info.packed.last_avail_idx =
+                               vq->state.packed.last_avail_idx;
+                       vq_info.packed.last_used_counter =
+                               vq->state.packed.last_used_counter;
+                       vq_info.packed.last_used_idx =
+                               vq->state.packed.last_used_idx;
+               } else
+                       vq_info.split.avail_index =
+                               vq->state.split.avail_index;
+
+               vq_info.ready = vq->ready;
+
+               ret = -EFAULT;
+               if (copy_to_user(argp, &vq_info,
+                   sizeof(struct compat_vduse_vq_info)))
+                       break;
+
+               ret = 0;
+               break;
+       }
+       default:
+               ret = -ENOIOCTLCMD;
+               break;
+       }
+
+       return vduse_dev_ioctl(file, cmd, (unsigned long)compat_ptr(arg));
+}
+#else
+#define vduse_dev_compat_ioctl compat_ptr_ioctl
+#endif
+
 static int vduse_dev_release(struct inode *inode, struct file *file)
 {
        struct vduse_dev *dev = file->private_data;
@@ -1694,7 +1815,7 @@ static const struct file_operations vduse_dev_fops = {
        .write_iter     = vduse_dev_write_iter,
        .poll           = vduse_dev_poll,
        .unlocked_ioctl = vduse_dev_ioctl,
-       .compat_ioctl   = compat_ptr_ioctl,
+       .compat_ioctl   = vduse_dev_compat_ioctl,
        .llseek         = noop_llseek,
 };