History

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 e5bf5ee26663 ("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.

Al Viro committed 9 months ago

Commit 2005aab1 parent 351ea482 files · +53 −57Browse files

Unified · Split

2 files
  1. drivers/usb/gadget/function/f_fs.c +52 −56
  2. drivers/usb/gadget/function/u_fs.h +1 −1
drivers/usb/gadget/function/f_fs.c+52−56
@@ -59,7 +59,6 @@ static struct ffs_data *__must_check ffs_data_new(const char *dev_name) · 58 unmodified lines
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 @@ done_mutex: · 570 unmodified lines
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) · 543 unmodified lines
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) · 3 unmodified lines
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) · 861 unmodified lines
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) · 50 unmodified lines
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) · 11 unmodified lines
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) · 10 unmodified lines
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) · 45 unmodified lines
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) · 17 unmodified lines
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, · 1461 unmodified lines
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, · 6 unmodified lines
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) · 11 unmodified lines
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+1−1
@@ -176,7 +176,7 @@ struct ffs_data { · 175 unmodified lines
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;