Skip to content

Commit

Permalink
inotify: fix locking around inotify watching in the idr
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Eric Paris committed Aug 27, 2009
1 parent cf43742 commit dead537
Showing 1 changed file with 40 additions and 10 deletions.
50 changes: 40 additions & 10 deletions fs/notify/inotify/inotify_user.c
Original file line number Diff line number Diff line change
Expand Up @@ -364,20 +364,53 @@ static int inotify_find_inode(const char __user *dirname, struct path *path, uns
return error;
}

/*
* Remove the mark from the idr (if present) and drop the reference
* on the mark because it was in the idr.
*/
static void inotify_remove_from_idr(struct fsnotify_group *group,
struct inotify_inode_mark_entry *ientry)
{
struct idr *idr;
struct fsnotify_mark_entry *entry;
struct inotify_inode_mark_entry *found_ientry;
int wd;

spin_lock(&group->inotify_data.idr_lock);
idr = &group->inotify_data.idr;
idr_remove(idr, ientry->wd);
spin_unlock(&group->inotify_data.idr_lock);
wd = ientry->wd;

if (wd == -1)
goto out;

entry = idr_find(&group->inotify_data.idr, wd);
if (unlikely(!entry))
goto out;

found_ientry = container_of(entry, struct inotify_inode_mark_entry, fsn_entry);
if (unlikely(found_ientry != ientry)) {
/* We found an entry in the idr with the right wd, but it's
* not the entry we were told to remove. eparis seriously
* fucked up somewhere. */
WARN_ON(1);
ientry->wd = -1;
goto out;
}

/* One ref for being in the idr, one ref held by the caller */
BUG_ON(atomic_read(&entry->refcnt) < 2);

idr_remove(idr, wd);
ientry->wd = -1;

/* removed from the idr, drop that ref */
fsnotify_put_mark(entry);
out:
spin_unlock(&group->inotify_data.idr_lock);
}

/*
* Send IN_IGNORED for this wd, remove this wd from the idr, and drop the
* internal reference help on the mark because it is in the idr.
* Send IN_IGNORED for this wd, remove this wd from the idr.
*/
void inotify_ignored_and_remove_idr(struct fsnotify_mark_entry *entry,
struct fsnotify_group *group)
Expand Down Expand Up @@ -417,9 +450,6 @@ void inotify_ignored_and_remove_idr(struct fsnotify_mark_entry *entry,
/* remove this entry from the idr */
inotify_remove_from_idr(group, ientry);

/* removed from idr, drop that reference */
fsnotify_put_mark(entry);

atomic_dec(&group->inotify_data.user->inotify_watches);
}

Expand Down Expand Up @@ -535,6 +565,9 @@ static int inotify_new_watch(struct fsnotify_group *group,
goto out_err;
}

/* we put the mark on the idr, take a reference */
fsnotify_get_mark(&tmp_ientry->fsn_entry);

/* we are on the idr, now get on the inode */
ret = fsnotify_add_mark(&tmp_ientry->fsn_entry, group, inode);
if (ret) {
Expand All @@ -543,9 +576,6 @@ static int inotify_new_watch(struct fsnotify_group *group,
goto out_err;
}

/* we put the mark on the idr, take a reference */
fsnotify_get_mark(&tmp_ientry->fsn_entry);

/* update the idr hint, who cares about races, it's just a hint */
group->inotify_data.last_wd = tmp_ientry->wd;

Expand Down

0 comments on commit dead537

Please sign in to comment.