Skip to content

Commit 2005aab

Browse files
author
Al Viro
committed
functionfs: use spinlock for FFS_DEACTIVATED/FFS_CLOSING transitions
When all files are closed, functionfs needs ffs_data_reset() to be done before any further opens are allowed. During that time we have ffs->state set to FFS_CLOSING; that makes ->open() fail with -EBUSY. Once ffs_data_reset() is done, it switches state (to FFS_READ_DESCRIPTORS) indicating that opening that thing is allowed again. There's a couple of additional twists: * mounting with -o no_disconnect delays ffs_data_reset() from doing that at the final ->release() to the first subsequent open(). That's indicated by ffs->state set to FFS_DEACTIVATED; if open() sees that, it immediately switches to FFS_CLOSING and proceeds with doing ffs_data_reset() before returning to userland. * a couple of usb callbacks need to force the delayed transition; unfortunately, they are done in locking environment that does not allow blocking and ffs_data_reset() can block. As the result, if these callbacks see FFS_DEACTIVATED, they change state to FFS_CLOSING and use schedule_work() to get ffs_data_reset() executed asynchronously. Unfortunately, the locking is rather insufficient. A fix attempted in e5bf5ee ("functionfs: fix the open/removal races") had closed a bunch of UAF, but it didn't do anything to the callbacks, lacked barriers in transition from FFS_CLOSING to FFS_READ_DESCRIPTORS _and_ it had been too heavy-handed in open()/open() serialization - I've used ffs->mutex for that, and it's being held over actual IO on ep0, complete with copy_from_user(), etc. Even more unfortunately, the userland side is apparently racy enough to have the resulting timing changes (no failures, just a delayed return of open(2)) disrupt the things quite badly. Userland bugs or not, it's a clear regression that needs to be dealt with. Solution is to use a spinlock for serializing these state checks and transitions - unlike ffs->mutex it can be taken in these callbacks and it doesn't disrupt the timings in open(). We could introduce a new spinlock, but it's easier to use the one that is already there (ffs->eps_lock) instead - the locking environment is safe for it in all affected places. Since now it is held over all places that alter or check the open count (ffs->opened), there's no need to keep that atomic_t - int would serve just fine and it's simpler that way. Fixes: e5bf5ee ("functionfs: fix the open/removal races") Fixes: 18d6b32 ("usb: gadget: f_fs: add "no_disconnect" mode") # v4.0 Tested-by: Samuel Wu <[email protected]> Signed-off-by: Al Viro <[email protected]>
1 parent 351ea48 commit 2005aab

2 files changed

Lines changed: 53 additions & 57 deletions

File tree

drivers/usb/gadget/function/f_fs.c

Lines changed: 52 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ static struct ffs_data *__must_check ffs_data_new(const char *dev_name)
5959
__attribute__((malloc));
6060

6161
/* Opened counter handling. */
62-
static void ffs_data_opened(struct ffs_data *ffs);
6362
static void ffs_data_closed(struct ffs_data *ffs);
6463

6564
/* Called with ffs->mutex held; take over ownership of data. */
@@ -636,23 +635,25 @@ static ssize_t ffs_ep0_read(struct file *file, char __user *buf,
636635
return ret;
637636
}
638637

638+
639+
static void ffs_data_reset(struct ffs_data *ffs);
640+
639641
static int ffs_ep0_open(struct inode *inode, struct file *file)
640642
{
641643
struct ffs_data *ffs = inode->i_sb->s_fs_info;
642-
int ret;
643644

644-
/* Acquire mutex */
645-
ret = ffs_mutex_lock(&ffs->mutex, file->f_flags & O_NONBLOCK);
646-
if (ret < 0)
647-
return ret;
648-
649-
ffs_data_opened(ffs);
645+
spin_lock_irq(&ffs->eps_lock);
650646
if (ffs->state == FFS_CLOSING) {
651-
ffs_data_closed(ffs);
652-
mutex_unlock(&ffs->mutex);
647+
spin_unlock_irq(&ffs->eps_lock);
653648
return -EBUSY;
654649
}
655-
mutex_unlock(&ffs->mutex);
650+
if (!ffs->opened++ && ffs->state == FFS_DEACTIVATED) {
651+
ffs->state = FFS_CLOSING;
652+
spin_unlock_irq(&ffs->eps_lock);
653+
ffs_data_reset(ffs);
654+
} else {
655+
spin_unlock_irq(&ffs->eps_lock);
656+
}
656657
file->private_data = ffs;
657658

658659
return stream_open(inode, file);
@@ -1202,15 +1203,10 @@ ffs_epfile_open(struct inode *inode, struct file *file)
12021203
{
12031204
struct ffs_data *ffs = inode->i_sb->s_fs_info;
12041205
struct ffs_epfile *epfile;
1205-
int ret;
1206-
1207-
/* Acquire mutex */
1208-
ret = ffs_mutex_lock(&ffs->mutex, file->f_flags & O_NONBLOCK);
1209-
if (ret < 0)
1210-
return ret;
12111206

1212-
if (!atomic_inc_not_zero(&ffs->opened)) {
1213-
mutex_unlock(&ffs->mutex);
1207+
spin_lock_irq(&ffs->eps_lock);
1208+
if (!ffs->opened) {
1209+
spin_unlock_irq(&ffs->eps_lock);
12141210
return -ENODEV;
12151211
}
12161212
/*
@@ -1220,11 +1216,11 @@ ffs_epfile_open(struct inode *inode, struct file *file)
12201216
*/
12211217
epfile = smp_load_acquire(&inode->i_private);
12221218
if (unlikely(ffs->state != FFS_ACTIVE || !epfile)) {
1223-
mutex_unlock(&ffs->mutex);
1224-
ffs_data_closed(ffs);
1219+
spin_unlock_irq(&ffs->eps_lock);
12251220
return -ENODEV;
12261221
}
1227-
mutex_unlock(&ffs->mutex);
1222+
ffs->opened++;
1223+
spin_unlock_irq(&ffs->eps_lock);
12281224

12291225
file->private_data = epfile;
12301226
return stream_open(inode, file);
@@ -2092,8 +2088,6 @@ static int ffs_fs_init_fs_context(struct fs_context *fc)
20922088
return 0;
20932089
}
20942090

2095-
static void ffs_data_reset(struct ffs_data *ffs);
2096-
20972091
static void
20982092
ffs_fs_kill_sb(struct super_block *sb)
20992093
{
@@ -2150,15 +2144,6 @@ static void ffs_data_get(struct ffs_data *ffs)
21502144
refcount_inc(&ffs->ref);
21512145
}
21522146

2153-
static void ffs_data_opened(struct ffs_data *ffs)
2154-
{
2155-
if (atomic_add_return(1, &ffs->opened) == 1 &&
2156-
ffs->state == FFS_DEACTIVATED) {
2157-
ffs->state = FFS_CLOSING;
2158-
ffs_data_reset(ffs);
2159-
}
2160-
}
2161-
21622147
static void ffs_data_put(struct ffs_data *ffs)
21632148
{
21642149
if (refcount_dec_and_test(&ffs->ref)) {
@@ -2176,28 +2161,29 @@ static void ffs_data_put(struct ffs_data *ffs)
21762161

21772162
static void ffs_data_closed(struct ffs_data *ffs)
21782163
{
2179-
if (atomic_dec_and_test(&ffs->opened)) {
2180-
if (ffs->no_disconnect) {
2181-
struct ffs_epfile *epfiles;
2182-
unsigned long flags;
2183-
2184-
ffs->state = FFS_DEACTIVATED;
2185-
spin_lock_irqsave(&ffs->eps_lock, flags);
2186-
epfiles = ffs->epfiles;
2187-
ffs->epfiles = NULL;
2188-
spin_unlock_irqrestore(&ffs->eps_lock,
2189-
flags);
2190-
2191-
if (epfiles)
2192-
ffs_epfiles_destroy(ffs->sb, epfiles,
2193-
ffs->eps_count);
2194-
2195-
if (ffs->setup_state == FFS_SETUP_PENDING)
2196-
__ffs_ep0_stall(ffs);
2197-
} else {
2198-
ffs->state = FFS_CLOSING;
2199-
ffs_data_reset(ffs);
2200-
}
2164+
spin_lock_irq(&ffs->eps_lock);
2165+
if (--ffs->opened) { // not the last opener?
2166+
spin_unlock_irq(&ffs->eps_lock);
2167+
return;
2168+
}
2169+
if (ffs->no_disconnect) {
2170+
struct ffs_epfile *epfiles;
2171+
2172+
ffs->state = FFS_DEACTIVATED;
2173+
epfiles = ffs->epfiles;
2174+
ffs->epfiles = NULL;
2175+
spin_unlock_irq(&ffs->eps_lock);
2176+
2177+
if (epfiles)
2178+
ffs_epfiles_destroy(ffs->sb, epfiles,
2179+
ffs->eps_count);
2180+
2181+
if (ffs->setup_state == FFS_SETUP_PENDING)
2182+
__ffs_ep0_stall(ffs);
2183+
} else {
2184+
ffs->state = FFS_CLOSING;
2185+
spin_unlock_irq(&ffs->eps_lock);
2186+
ffs_data_reset(ffs);
22012187
}
22022188
}
22032189

@@ -2214,7 +2200,7 @@ static struct ffs_data *ffs_data_new(const char *dev_name)
22142200
}
22152201

22162202
refcount_set(&ffs->ref, 1);
2217-
atomic_set(&ffs->opened, 0);
2203+
ffs->opened = 0;
22182204
ffs->state = FFS_READ_DESCRIPTORS;
22192205
mutex_init(&ffs->mutex);
22202206
spin_lock_init(&ffs->eps_lock);
@@ -2266,6 +2252,7 @@ static void ffs_data_reset(struct ffs_data *ffs)
22662252
{
22672253
ffs_data_clear(ffs);
22682254

2255+
spin_lock_irq(&ffs->eps_lock);
22692256
ffs->raw_descs_data = NULL;
22702257
ffs->raw_descs = NULL;
22712258
ffs->raw_strings = NULL;
@@ -2289,6 +2276,7 @@ static void ffs_data_reset(struct ffs_data *ffs)
22892276
ffs->ms_os_descs_ext_prop_count = 0;
22902277
ffs->ms_os_descs_ext_prop_name_len = 0;
22912278
ffs->ms_os_descs_ext_prop_data_len = 0;
2279+
spin_unlock_irq(&ffs->eps_lock);
22922280
}
22932281

22942282

@@ -3756,6 +3744,7 @@ static int ffs_func_set_alt(struct usb_function *f,
37563744
{
37573745
struct ffs_function *func = ffs_func_from_usb(f);
37583746
struct ffs_data *ffs = func->ffs;
3747+
unsigned long flags;
37593748
int ret = 0, intf;
37603749

37613750
if (alt > MAX_ALT_SETTINGS)
@@ -3768,12 +3757,15 @@ static int ffs_func_set_alt(struct usb_function *f,
37683757
if (ffs->func)
37693758
ffs_func_eps_disable(ffs->func);
37703759

3760+
spin_lock_irqsave(&ffs->eps_lock, flags);
37713761
if (ffs->state == FFS_DEACTIVATED) {
37723762
ffs->state = FFS_CLOSING;
3763+
spin_unlock_irqrestore(&ffs->eps_lock, flags);
37733764
INIT_WORK(&ffs->reset_work, ffs_reset_work);
37743765
schedule_work(&ffs->reset_work);
37753766
return -ENODEV;
37763767
}
3768+
spin_unlock_irqrestore(&ffs->eps_lock, flags);
37773769

37783770
if (ffs->state != FFS_ACTIVE)
37793771
return -ENODEV;
@@ -3791,16 +3783,20 @@ static void ffs_func_disable(struct usb_function *f)
37913783
{
37923784
struct ffs_function *func = ffs_func_from_usb(f);
37933785
struct ffs_data *ffs = func->ffs;
3786+
unsigned long flags;
37943787

37953788
if (ffs->func)
37963789
ffs_func_eps_disable(ffs->func);
37973790

3791+
spin_lock_irqsave(&ffs->eps_lock, flags);
37983792
if (ffs->state == FFS_DEACTIVATED) {
37993793
ffs->state = FFS_CLOSING;
3794+
spin_unlock_irqrestore(&ffs->eps_lock, flags);
38003795
INIT_WORK(&ffs->reset_work, ffs_reset_work);
38013796
schedule_work(&ffs->reset_work);
38023797
return;
38033798
}
3799+
spin_unlock_irqrestore(&ffs->eps_lock, flags);
38043800

38053801
if (ffs->state == FFS_ACTIVE) {
38063802
ffs->func = NULL;

drivers/usb/gadget/function/u_fs.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ struct ffs_data {
176176
/* reference counter */
177177
refcount_t ref;
178178
/* how many files are opened (EP0 and others) */
179-
atomic_t opened;
179+
int opened;
180180

181181
/* EP0 state */
182182
enum ffs_state state;

0 commit comments

Comments
 (0)