inotify: do not leak inode marks in inotify_add_watch
authorEric Paris <eparis@redhat.com>
Tue, 7 Jul 2009 14:28:23 +0000 (10:28 -0400)
committerEric Paris <eparis@redhat.com>
Tue, 21 Jul 2009 19:26:26 +0000 (15:26 -0400)
inotify_add_watch had a couple of problems.  The biggest being that if
inotify_add_watch was called on the same inode twice (to update or change the
event mask) a refence was taken on the original inode mark by
fsnotify_find_mark_entry but was not being dropped at the end of the
inotify_add_watch call.  Thus if inotify_rm_watch was called although the mark
was removed from the inode, the refcnt wouldn't hit zero and we would leak
memory.

Reported-by: Catalin Marinas <catalin.marinas@arm.com>
Signed-off-by: Eric Paris <eparis@redhat.com>
fs/notify/inotify/inotify_user.c

index 1a870f9..aff4214 100644 (file)
@@ -463,9 +463,6 @@ retry:
                        goto out_err;
 
                spin_lock(&group->inotify_data.idr_lock);
-               /* if entry is added to the idr we keep the reference obtained
-                * through fsnotify_mark_add.  remember to drop this reference
-                * when entry is removed from idr */
                ret = idr_get_new_above(&group->inotify_data.idr, entry,
                                        ++group->inotify_data.last_wd,
                                        &ientry->wd);
@@ -476,8 +473,13 @@ retry:
                        goto out_err;
                }
                atomic_inc(&group->inotify_data.user->inotify_watches);
+
+               /* we put the mark on the idr, take a reference */
+               fsnotify_get_mark(entry);
        }
 
+       ret = ientry->wd;
+
        spin_lock(&entry->lock);
 
        old_mask = entry->mask;
@@ -508,7 +510,11 @@ retry:
                        fsnotify_recalc_group_mask(group);
        }
 
-       return ientry->wd;
+       /* this either matches fsnotify_find_mark_entry, or init_mark_entry
+        * depending on which path we took... */
+       fsnotify_put_mark(entry);
+
+       return ret;
 
 out_err:
        /* see this isn't supposed to happen, just kill the watch */