Re: [PATCH v6] uio: Fix uio driver to refcount device

From: Greg Kroah-Hartman
Date: Thu Mar 19 2015 - 10:23:37 EST


On Thu, Mar 19, 2015 at 02:11:19PM +0000, Brian Russell wrote:
> Protect uio driver from its owner being unplugged while there are open fds.
> Embed struct device in struct uio_device, use refcounting on device, free uio_device on release.
> info struct passed in uio_register_device can be freed on unregister, so null out the field in
> uio_unregister_device and check accesses.
>
> Signed-off-by: Brian Russell <brussell@xxxxxxxxxxx>
> ---
> drivers/uio/uio.c | 63 +++++++++++++++++++++++++++++++---------------
> include/linux/uio_driver.h | 2 +-
> 2 files changed, 44 insertions(+), 21 deletions(-)
>
> v6: ... and put them in the right place
>
> v5: add patch version changes
>
> v4: info struct passed in uio_register_device can be freed on unregister, so null
> out the field in uio_unregister_device and check accesses.
>
> v3: Embed struct device in struct uio_device, use refcounting on device, free
> uio_device on release.
>
> v2: Add blank line to header
>
> diff --git a/drivers/uio/uio.c b/drivers/uio/uio.c
> index 6276f13..394cae0 100644
> --- a/drivers/uio/uio.c
> +++ b/drivers/uio/uio.c
> @@ -270,7 +270,7 @@ static int uio_dev_add_attributes(struct uio_device *idev)
> if (!map_found) {
> map_found = 1;
> idev->map_dir = kobject_create_and_add("maps",
> - &idev->dev->kobj);
> + &idev->dev.kobj);
> if (!idev->map_dir)
> goto err_map;
> }
> @@ -295,7 +295,7 @@ static int uio_dev_add_attributes(struct uio_device *idev)
> if (!portio_found) {
> portio_found = 1;
> idev->portio_dir = kobject_create_and_add("portio",
> - &idev->dev->kobj);
> + &idev->dev.kobj);
> if (!idev->portio_dir)
> goto err_portio;
> }
> @@ -334,7 +334,7 @@ err_map_kobj:
> kobject_put(&map->kobj);
> }
> kobject_put(idev->map_dir);
> - dev_err(idev->dev, "error creating sysfs files (%d)\n", ret);
> + dev_err(&idev->dev, "error creating sysfs files (%d)\n", ret);
> return ret;
> }
>
> @@ -371,7 +371,7 @@ static int uio_get_minor(struct uio_device *idev)
> idev->minor = retval;
> retval = 0;
> } else if (retval == -ENOSPC) {
> - dev_err(idev->dev, "too many uio devices\n");
> + dev_err(&idev->dev, "too many uio devices\n");
> retval = -EINVAL;
> }
> mutex_unlock(&minor_lock);
> @@ -434,9 +434,11 @@ static int uio_open(struct inode *inode, struct file *filep)
> goto out;
> }
>
> + get_device(&idev->dev);
> +
> if (!try_module_get(idev->owner)) {
> ret = -ENODEV;
> - goto out;
> + goto err_module_get;
> }
>
> listener = kmalloc(sizeof(*listener), GFP_KERNEL);
> @@ -462,6 +464,9 @@ err_infoopen:
> err_alloc_listener:
> module_put(idev->owner);
>
> +err_module_get:
> + put_device(&idev->dev);
> +
> out:
> return ret;
> }
> @@ -480,11 +485,12 @@ static int uio_release(struct inode *inode, struct file *filep)
> struct uio_listener *listener = filep->private_data;
> struct uio_device *idev = listener->dev;
>
> - if (idev->info->release)
> + if (idev->info && idev->info->release)
> ret = idev->info->release(idev->info, inode);
>
> module_put(idev->owner);
> kfree(listener);
> + put_device(&idev->dev);
> return ret;
> }
>
> @@ -493,7 +499,7 @@ static unsigned int uio_poll(struct file *filep, poll_table *wait)
> struct uio_listener *listener = filep->private_data;
> struct uio_device *idev = listener->dev;
>
> - if (!idev->info->irq)
> + if (!idev->info || !idev->info->irq)
> return -EIO;
>
> poll_wait(filep, &idev->wait, wait);
> @@ -511,7 +517,7 @@ static ssize_t uio_read(struct file *filep, char __user *buf,
> ssize_t retval;
> s32 event_count;
>
> - if (!idev->info->irq)
> + if (!idev->info || !idev->info->irq)
> return -EIO;
>
> if (count != sizeof(s32))
> @@ -559,7 +565,7 @@ static ssize_t uio_write(struct file *filep, const char __user *buf,
> ssize_t retval;
> s32 irq_on;
>
> - if (!idev->info->irq)
> + if (!idev->info || !idev->info->irq)
> return -EIO;
>
> if (count != sizeof(s32))
> @@ -785,6 +791,13 @@ static void release_uio_class(void)
> uio_major_cleanup();
> }
>
> +static void uio_device_release(struct device *dev)
> +{
> + struct uio_device *idev = dev_get_drvdata(dev);
> +
> + kfree(idev);
> +}
> +
> /**
> * uio_register_device - register a new userspace IO device
> * @owner: module that creates the new device
> @@ -805,7 +818,7 @@ int __uio_register_device(struct module *owner,
>
> info->uio_dev = NULL;
>
> - idev = devm_kzalloc(parent, sizeof(*idev), GFP_KERNEL);
> + idev = kzalloc(sizeof(*idev), GFP_KERNEL);
> if (!idev) {
> return -ENOMEM;
> }
> @@ -819,14 +832,19 @@ int __uio_register_device(struct module *owner,
> if (ret)
> return ret;
>
> - idev->dev = device_create(&uio_class, parent,
> - MKDEV(uio_major, idev->minor), idev,
> - "uio%d", idev->minor);
> - if (IS_ERR(idev->dev)) {
> - printk(KERN_ERR "UIO: device register failed\n");
> - ret = PTR_ERR(idev->dev);
> + idev->dev.devt = MKDEV(uio_major, idev->minor);
> + idev->dev.class = &uio_class;
> + idev->dev.parent = parent;
> + idev->dev.release = uio_device_release;
> + dev_set_drvdata(&idev->dev, idev);
> +
> + ret = kobject_set_name(&idev->dev.kobj, "uio%d", idev->minor);

You are working with a device, why call a kobject function? Please use
dev_set_name().

> + if (ret)
> + goto err_device_create;
> +
> + ret = device_register(&idev->dev);
> + if (ret)
> goto err_device_create;
> - }
>
> ret = uio_dev_add_attributes(idev);
> if (ret)
> @@ -835,7 +853,7 @@ int __uio_register_device(struct module *owner,
> info->uio_dev = idev;
>
> if (info->irq && (info->irq != UIO_IRQ_CUSTOM)) {
> - ret = devm_request_irq(idev->dev, info->irq, uio_interrupt,
> + ret = request_irq(info->irq, uio_interrupt,
> info->irq_flags, info->name, idev);

Why move this away from the device lifecycle?

> if (ret)
> goto err_request_irq;
> @@ -846,7 +864,10 @@ int __uio_register_device(struct module *owner,
> err_request_irq:
> uio_dev_del_attributes(idev);
> err_uio_dev_add_attributes:
> - device_destroy(&uio_class, MKDEV(uio_major, idev->minor));
> + uio_free_minor(idev);
> + device_unregister(&idev->dev);
> + return ret;

This doesn't make sense here, shouldn't these just line up and allow the
error path to fall though?

> err_device_create:
> uio_free_minor(idev);
> return ret;
> @@ -871,8 +892,10 @@ void uio_unregister_device(struct uio_info *info)
>
> uio_dev_del_attributes(idev);
>
> - device_destroy(&uio_class, MKDEV(uio_major, idev->minor));
> + device_unregister(&idev->dev);
> + free_irq(idev->info->irq, idev);
>
> + idev->info = NULL;

Why? If this was the last reference count, isn't idev now gone? You
shouldn't be referencing it anymore.

> return;
> }
> EXPORT_SYMBOL_GPL(uio_unregister_device);
> diff --git a/include/linux/uio_driver.h b/include/linux/uio_driver.h
> index 32c0e83..a11feeb 100644
> --- a/include/linux/uio_driver.h
> +++ b/include/linux/uio_driver.h
> @@ -65,7 +65,7 @@ struct uio_port {
>
> struct uio_device {
> struct module *owner;
> - struct device *dev;
> + struct device dev;

I like this change, I just think some of the details above aren't quite
right.

thanks,

greg k-h
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@xxxxxxxxxxxxxxx
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/