The character device needs to live until the last file descriptor has been closed (and until after ->release() is called in that case). Change the lifetime of our character devices to match the module, so as to avoid a use-after-free when closing a file descriptor after the gadget function has been deleted. Signed-off-by: John Keeping <john@xxxxxxxxxxxx> --- drivers/usb/gadget/function/f_hid.c | 45 ++++++++++++++++++++++------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/drivers/usb/gadget/function/f_hid.c b/drivers/usb/gadget/function/f_hid.c index 4d20347fe908..ee94348b85ef 100644 --- a/drivers/usb/gadget/function/f_hid.c +++ b/drivers/usb/gadget/function/f_hid.c @@ -24,6 +24,7 @@ static int major, minors; static struct class *hidg_class; +static struct cdev *hidg_cdev; static DEFINE_IDR(hidg_idr); static DEFINE_MUTEX(hidg_idr_lock); /* protects access to hidg_idr */ @@ -58,7 +59,6 @@ struct f_hidg { struct usb_request *req; int minor; - struct cdev cdev; struct usb_function func; struct usb_ep *in_ep; @@ -827,11 +827,7 @@ static int hidg_bind(struct usb_configuration *c, struct usb_function *f) INIT_LIST_HEAD(&hidg->completed_out_req); /* create char device */ - cdev_init(&hidg->cdev, &f_hidg_fops); dev = MKDEV(major, hidg->minor); - status = cdev_add(&hidg->cdev, dev, 1); - if (status) - goto fail_free_descs; mutex_lock(&hidg_idr_lock); idr_replace(&hidg_idr, hidg, hidg->minor); @@ -841,13 +837,14 @@ static int hidg_bind(struct usb_configuration *c, struct usb_function *f) "%s%d", "hidg", hidg->minor); if (IS_ERR(device)) { status = PTR_ERR(device); - goto del; + goto fail_idr_remove; } return 0; -del: - cdev_del(&hidg->cdev); -fail_free_descs: +fail_idr_remove: + mutex_lock(&hidg_idr_lock); + idr_replace(&hidg_idr, NULL, hidg->minor); + mutex_unlock(&hidg_idr_lock); usb_free_all_descriptors(f); fail: ERROR(f->config->cdev, "hidg_bind FAILED\n"); @@ -1071,7 +1068,10 @@ static void hidg_unbind(struct usb_configuration *c, struct usb_function *f) struct f_hidg *hidg = func_to_hidg(f); device_destroy(hidg_class, MKDEV(major, hidg->minor)); - cdev_del(&hidg->cdev); + + mutex_lock(&hidg_idr_lock); + idr_replace(&hidg_idr, NULL, hidg->minor); + mutex_unlock(&hidg_idr_lock); usb_free_all_descriptors(f); } @@ -1129,6 +1129,7 @@ MODULE_AUTHOR("Fabien Chouteau"); static int ghid_setup(void) { + struct cdev *cdev = NULL; int status; dev_t dev; @@ -1149,11 +1150,35 @@ static int ghid_setup(void) major = MAJOR(dev); minors = HIDG_MINORS; + status = -ENOMEM; + cdev = cdev_alloc(); + if (!cdev) + goto fail_unregister; + + cdev->owner = THIS_MODULE; + cdev->ops = &f_hidg_fops; + kobject_set_name(&cdev->kobj, "hidg"); + + status = cdev_add(cdev, dev, HIDG_MINORS); + if (status) + goto fail_put; + + hidg_cdev = cdev; return 0; + +fail_put: + kobject_put(&cdev->kobj); +fail_unregister: + unregister_chrdev_region(dev, HIDG_MINORS); + class_destroy(hidg_class); + hidg_class = NULL; + return status; } static void ghid_cleanup(void) { + cdev_del(hidg_cdev); + if (major) { unregister_chrdev_region(MKDEV(major, 0), minors); major = minors = 0; -- 2.23.0