diff options
author | Eric Paris <eparis@redhat.com> | 2009-08-24 16:03:35 -0400 |
---|---|---|
committer | Eric Paris <eparis@redhat.com> | 2009-08-27 08:02:04 -0400 |
commit | dead537dd8a1c9495322c1d6f7c780697f474af0 (patch) | |
tree | 5263cf56e792cfc5ddf37748d2868fce9866af68 /fs/notify | |
parent | cf4374267fbe966e8e4e7db68f5dc7b267439780 (diff) |
inotify: fix locking around inotify watching in the idr
The are races around the idr storage of inotify watches. It's possible
that a watch could be found from sys_inotify_rm_watch() in the idr, but it
could be removed from the idr before that code does it's removal. Move the
locking and the refcnt'ing so that these have to happen atomically.
Signed-off-by: Eric Paris <eparis@redhat.com>
Diffstat (limited to 'fs/notify')
-rw-r--r-- | fs/notify/inotify/inotify_user.c | 50 |
1 files changed, 40 insertions, 10 deletions
diff --git a/fs/notify/inotify/inotify_user.c b/fs/notify/inotify/inotify_user.c index d8f73c253073..ce1f5823e2c2 100644 --- a/fs/notify/inotify/inotify_user.c +++ b/fs/notify/inotify/inotify_user.c | |||
@@ -364,20 +364,53 @@ static int inotify_find_inode(const char __user *dirname, struct path *path, uns | |||
364 | return error; | 364 | return error; |
365 | } | 365 | } |
366 | 366 | ||
367 | /* | ||
368 | * Remove the mark from the idr (if present) and drop the reference | ||
369 | * on the mark because it was in the idr. | ||
370 | */ | ||
367 | static void inotify_remove_from_idr(struct fsnotify_group *group, | 371 | static void inotify_remove_from_idr(struct fsnotify_group *group, |
368 | struct inotify_inode_mark_entry *ientry) | 372 | struct inotify_inode_mark_entry *ientry) |
369 | { | 373 | { |
370 | struct idr *idr; | 374 | struct idr *idr; |
375 | struct fsnotify_mark_entry *entry; | ||
376 | struct inotify_inode_mark_entry *found_ientry; | ||
377 | int wd; | ||
371 | 378 | ||
372 | spin_lock(&group->inotify_data.idr_lock); | 379 | spin_lock(&group->inotify_data.idr_lock); |
373 | idr = &group->inotify_data.idr; | 380 | idr = &group->inotify_data.idr; |
374 | idr_remove(idr, ientry->wd); | 381 | wd = ientry->wd; |
375 | spin_unlock(&group->inotify_data.idr_lock); | 382 | |
383 | if (wd == -1) | ||
384 | goto out; | ||
385 | |||
386 | entry = idr_find(&group->inotify_data.idr, wd); | ||
387 | if (unlikely(!entry)) | ||
388 | goto out; | ||
389 | |||
390 | found_ientry = container_of(entry, struct inotify_inode_mark_entry, fsn_entry); | ||
391 | if (unlikely(found_ientry != ientry)) { | ||
392 | /* We found an entry in the idr with the right wd, but it's | ||
393 | * not the entry we were told to remove. eparis seriously | ||
394 | * fucked up somewhere. */ | ||
395 | WARN_ON(1); | ||
396 | ientry->wd = -1; | ||
397 | goto out; | ||
398 | } | ||
399 | |||
400 | /* One ref for being in the idr, one ref held by the caller */ | ||
401 | BUG_ON(atomic_read(&entry->refcnt) < 2); | ||
402 | |||
403 | idr_remove(idr, wd); | ||
376 | ientry->wd = -1; | 404 | ientry->wd = -1; |
405 | |||
406 | /* removed from the idr, drop that ref */ | ||
407 | fsnotify_put_mark(entry); | ||
408 | out: | ||
409 | spin_unlock(&group->inotify_data.idr_lock); | ||
377 | } | 410 | } |
411 | |||
378 | /* | 412 | /* |
379 | * Send IN_IGNORED for this wd, remove this wd from the idr, and drop the | 413 | * Send IN_IGNORED for this wd, remove this wd from the idr. |
380 | * internal reference help on the mark because it is in the idr. | ||
381 | */ | 414 | */ |
382 | void inotify_ignored_and_remove_idr(struct fsnotify_mark_entry *entry, | 415 | void inotify_ignored_and_remove_idr(struct fsnotify_mark_entry *entry, |
383 | struct fsnotify_group *group) | 416 | struct fsnotify_group *group) |
@@ -417,9 +450,6 @@ skip_send_ignore: | |||
417 | /* remove this entry from the idr */ | 450 | /* remove this entry from the idr */ |
418 | inotify_remove_from_idr(group, ientry); | 451 | inotify_remove_from_idr(group, ientry); |
419 | 452 | ||
420 | /* removed from idr, drop that reference */ | ||
421 | fsnotify_put_mark(entry); | ||
422 | |||
423 | atomic_dec(&group->inotify_data.user->inotify_watches); | 453 | atomic_dec(&group->inotify_data.user->inotify_watches); |
424 | } | 454 | } |
425 | 455 | ||
@@ -535,6 +565,9 @@ retry: | |||
535 | goto out_err; | 565 | goto out_err; |
536 | } | 566 | } |
537 | 567 | ||
568 | /* we put the mark on the idr, take a reference */ | ||
569 | fsnotify_get_mark(&tmp_ientry->fsn_entry); | ||
570 | |||
538 | /* we are on the idr, now get on the inode */ | 571 | /* we are on the idr, now get on the inode */ |
539 | ret = fsnotify_add_mark(&tmp_ientry->fsn_entry, group, inode); | 572 | ret = fsnotify_add_mark(&tmp_ientry->fsn_entry, group, inode); |
540 | if (ret) { | 573 | if (ret) { |
@@ -543,9 +576,6 @@ retry: | |||
543 | goto out_err; | 576 | goto out_err; |
544 | } | 577 | } |
545 | 578 | ||
546 | /* we put the mark on the idr, take a reference */ | ||
547 | fsnotify_get_mark(&tmp_ientry->fsn_entry); | ||
548 | |||
549 | /* update the idr hint, who cares about races, it's just a hint */ | 579 | /* update the idr hint, who cares about races, it's just a hint */ |
550 | group->inotify_data.last_wd = tmp_ientry->wd; | 580 | group->inotify_data.last_wd = tmp_ientry->wd; |
551 | 581 | ||