Skip to content

Commit

Permalink
usb: gadget: f_cdev: Fix use after free of port in f_cdev
Browse files Browse the repository at this point in the history
With the configfs filesystem it’s possible to manipulate kernel
object by creating/deleting folders into /config path. Here port
object is created by a mkdir and leads to allocate this object,
while the rmdir system call leads to free this object.
If one thread does these two operations of creation and deletion
of the folder and one tries to open it, it can lead to a
race condition where port object can be freed by the time
it is used in f_cdev_open leading to use after free error.

Fix this by using embedded struct device and the refcounting
mechanism built-in which increases and decreases refcount upon
creation and deletion of port and port will be freed when
reference count is zero ensuring that "port" object survives
until the last user releases it.

Bug: 178720043
Change-Id: I88701ef161c9f3215631da81c3a8d4c980d12b25
Signed-off-by: Rohith Kollalsi <[email protected]>
  • Loading branch information
Rohith Kollalsi authored and chrmhoffmann committed Dec 31, 2022
1 parent 57144df commit c024f2c
Showing 1 changed file with 28 additions and 28 deletions.
56 changes: 28 additions & 28 deletions drivers/usb/gadget/function/f_cdev.c
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2011, 2013-2017, The Linux Foundation. All rights reserved.
* Copyright (c) 2011, 2013-2021, The Linux Foundation. All rights reserved.
* Linux Foundation chooses to take subject only to the GPLv2 license terms,
* and distributes only under these terms.
*
Expand Down Expand Up @@ -91,7 +91,7 @@ struct cserial {

struct f_cdev {
struct cdev fcdev_cdev;
struct device *dev;
struct device dev;
unsigned port_num;
char name[sizeof(DEVICE_NAME) + 2];
int minor;
Expand Down Expand Up @@ -824,12 +824,15 @@ static void cser_free_inst(struct usb_function_instance *fi)
opts = container_of(fi, struct f_cdev_opts, func_inst);

if (opts->port) {
device_destroy(fcdev_classp, MKDEV(major, opts->port->minor));
cdev_del(&opts->port->fcdev_cdev);
cdev_device_del(&opts->port->fcdev_cdev, &opts->port->dev);
mutex_lock(&chardev_ida_lock);
ida_simple_remove(&chardev_ida, opts->port->minor);
mutex_unlock(&chardev_ida_lock);
put_device(&opts->port->dev);
}

usb_cser_chardev_deinit();
kfree(opts->func_name);
kfree(opts->port);
kfree(opts);
}

Expand Down Expand Up @@ -1050,13 +1053,10 @@ int f_cdev_open(struct inode *inode, struct file *file)
struct f_cdev *port;

port = container_of(inode->i_cdev, struct f_cdev, fcdev_cdev);
if (!port) {
pr_err("Port is NULL.\n");
return -EINVAL;
}

if (port && port->port_open) {
get_device(&port->dev);
if (port->port_open) {
pr_err("port is already opened.\n");
put_device(&port->dev);
return -EBUSY;
}

Expand All @@ -1066,6 +1066,7 @@ int f_cdev_open(struct inode *inode, struct file *file)
port->is_connected);
if (ret) {
pr_debug("open interrupted.\n");
put_device(&port->dev);
return ret;
}

Expand All @@ -1085,16 +1086,12 @@ int f_cdev_release(struct inode *inode, struct file *file)
struct f_cdev *port;

port = file->private_data;
if (!port) {
pr_err("port is NULL.\n");
return -EINVAL;
}

spin_lock_irqsave(&port->port_lock, flags);
port->port_open = false;
port->cbits_updated = false;
spin_unlock_irqrestore(&port->port_lock, flags);
pr_debug("port(%s)(%pK) is closed.\n", port->name, port);
put_device(&port->dev);

return 0;
}
Expand Down Expand Up @@ -1507,11 +1504,17 @@ static const struct file_operations f_cdev_fops = {
.compat_ioctl = f_cdev_ioctl,
};

static void cdev_device_release(struct device *dev)
{
struct f_cdev *port = container_of(dev, struct f_cdev, dev);

pr_debug("Free cdev port(%d)\n", port->port_num);
kfree(port);
}

static struct f_cdev *f_cdev_alloc(char *func_name, int portno)
{
int ret;
dev_t dev;
struct device *device;
struct f_cdev *port;

port = kzalloc(sizeof(struct f_cdev), GFP_KERNEL);
Expand Down Expand Up @@ -1561,25 +1564,22 @@ static struct f_cdev *f_cdev_alloc(char *func_name, int portno)

/* create char device */
cdev_init(&port->fcdev_cdev, &f_cdev_fops);
dev = MKDEV(major, port->minor);
ret = cdev_add(&port->fcdev_cdev, dev, 1);
device_initialize(&port->dev);
port->dev.class = fcdev_classp;
port->dev.parent = NULL;
port->dev.release = cdev_device_release;
port->dev.devt = MKDEV(major, port->minor);
dev_set_name(&port->dev, port->name);
ret = cdev_device_add(&port->fcdev_cdev, &port->dev);
if (ret) {
pr_err("Failed to add cdev for port(%s)\n", port->name);
goto err_cdev_add;
}

device = device_create(fcdev_classp, NULL, dev, NULL, port->name);
if (IS_ERR(device)) {
ret = PTR_ERR(device);
goto err_create_dev;
}

pr_info("port_name:%s (%pK) portno:(%d)\n",
port->name, port, port->port_num);
return port;

err_create_dev:
cdev_del(&port->fcdev_cdev);
err_cdev_add:
destroy_workqueue(port->fcdev_wq);
err_get_ida:
Expand Down

0 comments on commit c024f2c

Please sign in to comment.