Protect against iommu driver disconnect
Subsystems (2)
cover Protect against iommu driver disconnect
Michael Tsirkin pointed out that file operations on /dev/vfio/vfio dereference iommu_driver and iommu_data without a lock. If releasing a group or unsetting the container occurs concurrently, we could race. We currently use a mutex when setting this association, so we can convert to a rwsem keeping the existing mutex critical sections as down_writes and add down_reads where these are used. Thanks,
Alex
---
Alex Williamson (2):
vfio: Convert container->group_lock to rwsem
vfio: Use down_reads to protect iommu disconnects
drivers/vfio/vfio.c | 83 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 57 insertions(+), 26 deletions(-)
PATCH 1/2 vfio: Convert container->group_lock to rwsem
All current users are writers, maintaining current mutual exclusion. This lets us add read users next.
--- drivers/vfio/vfio.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-)
drivers/vfio/vfio.c +11 -10
@@ -24,6 +24,7 @@#include <linux/list.h> #include <linux/module.h> #include <linux/mutex.h> +#include <linux/rwsem.h> #include <linux/sched.h> #include <linux/slab.h> #include <linux/string.h>
@@ -57,7 +58,7 @@ struct vfio_iommu_driver {struct vfio_container {struct kref kref;struct list_head group_list; -struct mutex group_lock; +struct rw_semaphore group_lock;struct vfio_iommu_driver *iommu_driver;void *iommu_data; };
@@ -738,7 +739,7 @@ static long vfio_ioctl_check_extension(struct vfio_container *container,return ret; } -/* hold container->group_lock */ +/* hold write lock on container->group_lock */ static int __vfio_container_attach_groups (struct vfio_container *container,struct vfio_iommu_driver *driver,void *data)
@@ -769,7 +770,7 @@ static long vfio_ioctl_set_iommu(struct vfio_container *container,struct vfio_iommu_driver *driver;long ret = -ENODEV ; -mutex_lock (&container->group_lock); +down_write (&container->group_lock);/* * The container is designed to be an unprivileged interface while
@@ -780,7 +781,7 @@ static long vfio_ioctl_set_iommu(struct vfio_container *container,* the container is deprivileged and returns to an unset state. */ if (list_empty (&container->group_list) || container->iommu_driver) { -mutex_unlock (&container->group_lock); +up_write (&container->group_lock);return -EINVAL ; }
@@ -827,7 +828,7 @@ static long vfio_ioctl_set_iommu(struct vfio_container *container,mutex_unlock (&vfio.iommu_drivers_lock); skip_drivers_unlock: -mutex_unlock (&container->group_lock); +up_write (&container->group_lock);return ret; }
@@ -882,7 +883,7 @@ static int vfio_fops_open(struct inode *inode, struct file *filep)return -ENOMEM ;INIT_LIST_HEAD (&container->group_list); -mutex_init (&container->group_lock); +init_rwsem (&container->group_lock);kref_init (&container->kref); filep->private_data = container;
@@ -961,7 +962,7 @@ static void __vfio_group_unset_container(struct vfio_group *group)struct vfio_container *container = group->container;struct vfio_iommu_driver *driver; -mutex_lock (&container->group_lock); +down_write (&container->group_lock); driver = container->iommu_driver;if (driver)
@@ -979,7 +980,7 @@ static void __vfio_group_unset_container(struct vfio_group *group) container->iommu_data =NULL ; } -mutex_unlock (&container->group_lock); +up_write (&container->group_lock);vfio_container_put (container); }
@@ -1039,7 +1040,7 @@ static int vfio_group_set_container(struct vfio_group *group, int container_fd) container = f.file->private_data;WARN_ON (!container);/* fget ensures we don't race vfio_release */ -mutex_lock (&container->group_lock); +down_write (&container->group_lock); driver = container->iommu_driver;if (driver) {
@@ -1057,7 +1058,7 @@ static int vfio_group_set_container(struct vfio_group *group, int container_fd)atomic_inc (&group->container_users); unlock_out: -mutex_unlock (&container->group_lock); +up_write (&container->group_lock);fdput (f);return ret; }
PATCH 2/2 vfio: Use down_reads to protect iommu disconnects
In reply to Alex WilliamsonIf a group or device is released or a container is unset from a group it can race against file ops on the container. Protect these with down_reads to allow concurrent users.
--- drivers/vfio/vfio.c | 62 ++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 46 insertions(+), 16 deletions(-)
drivers/vfio/vfio.c +46 -16
@@ -704,9 +704,13 @@ EXPORT_SYMBOL_GPL(vfio_del_group_dev);static long vfio_ioctl_check_extension (struct vfio_container *container,unsigned long arg) { -struct vfio_iommu_driver *driver = container->iommu_driver; +struct vfio_iommu_driver *driver;long ret =0 ; +down_read (&container->group_lock); + + driver = container->iommu_driver; +switch (arg) {/* No base extensions yet */ default :
@@ -736,6 +740,8 @@ static long vfio_ioctl_check_extension(struct vfio_container *container, VFIO_CHECK_EXTENSION, arg); } +up_read (&container->group_lock); +return ret; }
@@ -844,9 +850,6 @@ static long vfio_fops_unl_ioctl(struct file *filep,if (!container)return ret; - driver = container->iommu_driver; - data = container->iommu_data; -switch (cmd) {case VFIO_GET_API_VERSION : ret =VFIO_API_VERSION ;
@@ -858,8 +861,15 @@ static long vfio_fops_unl_ioctl(struct file *filep, ret =vfio_ioctl_set_iommu (container, arg);break ;default : +down_read (&container->group_lock); + + driver = container->iommu_driver; + data = container->iommu_data; +if (driver)/* passthrough all unrecognized ioctls */ ret = driver->ops->ioctl (data, cmd, arg); + +up_read (&container->group_lock); }return ret;
@@ -910,35 +920,55 @@ static ssize_t vfio_fops_read(struct file *filep, char __user *buf, size_t count, loff_t *ppos) { struct vfio_container *container = filep->private_data; -struct vfio_iommu_driver *driver = container->iommu_driver; +struct vfio_iommu_driver *driver; +ssize_t ret = -EINVAL ; -if (unlikely (!driver || !driver->ops->read)) -return -EINVAL ; +down_read (&container->group_lock); + + driver = container->iommu_driver; +if (likely (driver && driver->ops->read)) + ret = driver->ops->read (container->iommu_data, + buf, count, ppos); -return driver->ops->read (container->iommu_data, buf, count, ppos); +up_read (&container->group_lock); + +return ret; }static ssize_t vfio_fops_write (struct file *filep,const char __user *buf,size_t count,loff_t *ppos) {struct vfio_container *container = filep->private_data; -struct vfio_iommu_driver *driver = container->iommu_driver; +struct vfio_iommu_driver *driver; +ssize_t ret = -EINVAL ; -if (unlikely (!driver || !driver->ops->write)) -return -EINVAL ; +down_read (&container->group_lock); + + driver = container->iommu_driver; +if (likely (driver && driver->ops->write)) + ret = driver->ops->write (container->iommu_data, + buf, count, ppos); + +up_read (&container->group_lock); -return driver->ops->write (container->iommu_data, buf, count, ppos); +return ret; }static int vfio_fops_mmap (struct file *filep,struct vm_area_struct *vma) {struct vfio_container *container = filep->private_data; -struct vfio_iommu_driver *driver = container->iommu_driver; +struct vfio_iommu_driver *driver; +int ret = -EINVAL ; -if (unlikely (!driver || !driver->ops->mmap)) -return -EINVAL ; +down_read (&container->group_lock); -return driver->ops->mmap (container->iommu_data, vma); + driver = container->iommu_driver; +if (likely (driver && driver->ops->mmap)) + ret = driver->ops->mmap (container->iommu_data, vma); + +up_read (&container->group_lock); + +return ret; } static const struct file_operations vfio_fops = {