aboutsummaryrefslogtreecommitdiffstats
path: root/fs/proc
diff options
context:
space:
mode:
authorAl Viro <viro@zeniv.linux.org.uk>2013-04-03 19:57:00 -0400
committerAl Viro <viro@zeniv.linux.org.uk>2013-04-09 15:16:51 -0400
commitca469f35a8e9ef12571a4b80ac6d7fdc0260fb44 (patch)
tree228daeec1f54db72c32d64bf8b54413c65d0ab30 /fs/proc
parent866ad9a747bbf5461739fcae6d0a41c8971bbe1d (diff)
deal with races between remove_proc_entry() and proc_reg_release()
* serialize the call of ->release() on per-pdeo mutex * don't remove pdeo from per-pde list until we are through with it Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>
Diffstat (limited to 'fs/proc')
-rw-r--r--fs/proc/inode.c85
-rw-r--r--fs/proc/internal.h2
2 files changed, 34 insertions, 53 deletions
diff --git a/fs/proc/inode.c b/fs/proc/inode.c
index 0cd9d80f28e8..b5b204d6b07f 100644
--- a/fs/proc/inode.c
+++ b/fs/proc/inode.c
@@ -156,6 +156,29 @@ static void unuse_pde(struct proc_dir_entry *pde)
156 spin_unlock(&pde->pde_unload_lock); 156 spin_unlock(&pde->pde_unload_lock);
157} 157}
158 158
159/* pde is locked */
160static void close_pdeo(struct proc_dir_entry *pde, struct pde_opener *pdeo)
161{
162 pdeo->count++;
163 if (!mutex_trylock(&pdeo->mutex)) {
164 /* somebody else is doing that, just wait */
165 spin_unlock(&pde->pde_unload_lock);
166 mutex_lock(&pdeo->mutex);
167 spin_lock(&pde->pde_unload_lock);
168 WARN_ON(!list_empty(&pdeo->lh));
169 } else {
170 struct file *file;
171 spin_unlock(&pde->pde_unload_lock);
172 file = pdeo->file;
173 pde->proc_fops->release(file_inode(file), file);
174 spin_lock(&pde->pde_unload_lock);
175 list_del_init(&pdeo->lh);
176 }
177 mutex_unlock(&pdeo->mutex);
178 if (!--pdeo->count)
179 kfree(pdeo);
180}
181
159void proc_entry_rundown(struct proc_dir_entry *de) 182void proc_entry_rundown(struct proc_dir_entry *de)
160{ 183{
161 spin_lock(&de->pde_unload_lock); 184 spin_lock(&de->pde_unload_lock);
@@ -173,15 +196,8 @@ void proc_entry_rundown(struct proc_dir_entry *de)
173 196
174 while (!list_empty(&de->pde_openers)) { 197 while (!list_empty(&de->pde_openers)) {
175 struct pde_opener *pdeo; 198 struct pde_opener *pdeo;
176 struct file *file;
177
178 pdeo = list_first_entry(&de->pde_openers, struct pde_opener, lh); 199 pdeo = list_first_entry(&de->pde_openers, struct pde_opener, lh);
179 list_del(&pdeo->lh); 200 close_pdeo(de, pdeo);
180 spin_unlock(&de->pde_unload_lock);
181 file = pdeo->file;
182 de->proc_fops->release(file_inode(file), file);
183 kfree(pdeo);
184 spin_lock(&de->pde_unload_lock);
185 } 201 }
186 spin_unlock(&de->pde_unload_lock); 202 spin_unlock(&de->pde_unload_lock);
187} 203}
@@ -357,6 +373,8 @@ static int proc_reg_open(struct inode *inode, struct file *file)
357 spin_lock(&pde->pde_unload_lock); 373 spin_lock(&pde->pde_unload_lock);
358 if (rv == 0 && release) { 374 if (rv == 0 && release) {
359 /* To know what to release. */ 375 /* To know what to release. */
376 mutex_init(&pdeo->mutex);
377 pdeo->count = 0;
360 pdeo->file = file; 378 pdeo->file = file;
361 /* Strictly for "too late" ->release in proc_reg_release(). */ 379 /* Strictly for "too late" ->release in proc_reg_release(). */
362 list_add(&pdeo->lh, &pde->pde_openers); 380 list_add(&pdeo->lh, &pde->pde_openers);
@@ -367,58 +385,19 @@ static int proc_reg_open(struct inode *inode, struct file *file)
367 return rv; 385 return rv;
368} 386}
369 387
370static struct pde_opener *find_pde_opener(struct proc_dir_entry *pde,
371 struct file *file)
372{
373 struct pde_opener *pdeo;
374
375 list_for_each_entry(pdeo, &pde->pde_openers, lh) {
376 if (pdeo->file == file)
377 return pdeo;
378 }
379 return NULL;
380}
381
382static int proc_reg_release(struct inode *inode, struct file *file) 388static int proc_reg_release(struct inode *inode, struct file *file)
383{ 389{
384 struct proc_dir_entry *pde = PDE(inode); 390 struct proc_dir_entry *pde = PDE(inode);
385 int rv = 0;
386 int (*release)(struct inode *, struct file *);
387 struct pde_opener *pdeo; 391 struct pde_opener *pdeo;
388
389 spin_lock(&pde->pde_unload_lock); 392 spin_lock(&pde->pde_unload_lock);
390 pdeo = find_pde_opener(pde, file); 393 list_for_each_entry(pdeo, &pde->pde_openers, lh) {
391 if (pde->pde_users < 0) { 394 if (pdeo->file == file) {
392 /* 395 close_pdeo(pde, pdeo);
393 * Can't simply exit, __fput() will think that everything is OK, 396 break;
394 * and move on to freeing struct file. remove_proc_entry() will 397 }
395 * find slacker in opener's list and will try to do non-trivial
396 * things with struct file. Therefore, remove opener from list.
397 *
398 * But if opener is removed from list, who will ->release it?
399 */
400 if (pdeo) {
401 list_del(&pdeo->lh);
402 spin_unlock(&pde->pde_unload_lock);
403 rv = pde->proc_fops->release(inode, file);
404 kfree(pdeo);
405 } else
406 spin_unlock(&pde->pde_unload_lock);
407 return rv;
408 }
409 pde->pde_users++;
410 release = pde->proc_fops->release;
411 if (pdeo) {
412 list_del(&pdeo->lh);
413 kfree(pdeo);
414 } 398 }
415 spin_unlock(&pde->pde_unload_lock); 399 spin_unlock(&pde->pde_unload_lock);
416 400 return 0;
417 if (release)
418 rv = release(inode, file);
419
420 unuse_pde(pde);
421 return rv;
422} 401}
423 402
424static const struct file_operations proc_reg_file_ops = { 403static const struct file_operations proc_reg_file_ops = {
diff --git a/fs/proc/internal.h b/fs/proc/internal.h
index c43d536f93b9..e2fa9345a9a8 100644
--- a/fs/proc/internal.h
+++ b/fs/proc/internal.h
@@ -153,6 +153,8 @@ int proc_readdir_de(struct proc_dir_entry *de, struct file *filp, void *dirent,
153struct pde_opener { 153struct pde_opener {
154 struct file *file; 154 struct file *file;
155 struct list_head lh; 155 struct list_head lh;
156 int count; /* number of threads in close_pdeo() */
157 struct mutex mutex;
156}; 158};
157 159
158ssize_t __proc_file_read(struct file *, char __user *, size_t, loff_t *); 160ssize_t __proc_file_read(struct file *, char __user *, size_t, loff_t *);