diff --git a/drivers/usb/gadget/legacy/inode.c b/drivers/usb/gadget/legacy/inode.c index db961aaa3740..dc0cba86cf30 100644 --- a/drivers/usb/gadget/legacy/inode.c +++ b/drivers/usb/gadget/legacy/inode.c @@ -1626,6 +1626,16 @@ static int activate_ep_files (struct dev_data *dev) if (!data->req) goto enomem1; + /* + * The inode created below references this ep_data through + * ->i_private and gadgetfs_evict_inode() releases it again. + * Without this the inode could outlive the ep_data and a + * concurrent openat() -> ep_open() would dereference freed + * memory (reported as a slab-use-after-free in the mutex fast + * path). Keep the object alive for as long as any inode points + * to it. + */ + get_ep(data); err = gadgetfs_create_file (dev->sb, data->name, data, &ep_io_operations); if (err) @@ -2015,9 +2025,25 @@ static int gadgetfs_create_file (struct super_block *sb, char const *name, return 0; } +static void gadgetfs_evict_inode(struct inode *inode) +{ + /* + * EP file inodes keep their struct ep_data alive through ->i_private; + * drop that reference when the inode finally goes away. This closes + * the ep_open() vs. gadgetfs_unbind()/destroy_ep_files() race where + * the ep_data was freed while an inode still pointed to it. + */ + if (inode->i_fop == &ep_io_operations) + put_ep(inode->i_private); + + truncate_inode_pages_final(&inode->i_data); + clear_inode(inode); +} + static const struct super_operations gadget_fs_operations = { .statfs = simple_statfs, .drop_inode = inode_just_drop, + .evict_inode = gadgetfs_evict_inode, }; static int