diff options
author | Al Viro <viro@zeniv.linux.org.uk> | 2013-04-03 19:57:00 -0400 |
---|---|---|
committer | Al Viro <viro@zeniv.linux.org.uk> | 2013-04-09 15:16:51 -0400 |
commit | ca469f35a8e9ef12571a4b80ac6d7fdc0260fb44 (patch) | |
tree | 228daeec1f54db72c32d64bf8b54413c65d0ab30 /fs/proc | |
parent | 866ad9a747bbf5461739fcae6d0a41c8971bbe1d (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.c | 85 | ||||
-rw-r--r-- | fs/proc/internal.h | 2 |
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 */ | ||
160 | static 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 | |||
159 | void proc_entry_rundown(struct proc_dir_entry *de) | 182 | void 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 | ||
370 | static 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 | |||
382 | static int proc_reg_release(struct inode *inode, struct file *file) | 388 | static 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 | ||
424 | static const struct file_operations proc_reg_file_ops = { | 403 | static 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, | |||
153 | struct pde_opener { | 153 | struct 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 | ||
158 | ssize_t __proc_file_read(struct file *, char __user *, size_t, loff_t *); | 160 | ssize_t __proc_file_read(struct file *, char __user *, size_t, loff_t *); |