vduse: fix compat handling for VDUSE_IOTLB_GET_FD/VDUSE_VQ_GET_INFO

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: ad146355bf ("vduse: Support querying information of IOVA regions")
Fixes: c8a6153b6c ("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>
This commit is contained in:
Arnd Bergmann 2026-02-13 16:40:46 +01:00 committed by Michael S. Tsirkin
parent ec6177dfe9
commit 455a2a1af9

View 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,
};