Commit d0de4dc5 authored by Eric Paris's avatar Eric Paris Committed by Linus Torvalds

inotify: fix double free/corruption of stuct user

On an error path in inotify_init1 a normal user can trigger a double
free of struct user.  This is a regression introduced by a2ae4cc9
("inotify: stop kernel memory leak on file creation failure").

We fix this by making sure that if a group exists the user reference is
dropped when the group is cleaned up.  We should not explictly drop the
reference on error and also drop the reference when the group is cleaned
up.

The new lifetime rules are that an inotify group lives from
inotify_new_group to the last fsnotify_put_group.  Since the struct user
and inotify_devs are directly tied to this lifetime they are only
changed/updated in those two locations.  We get rid of all special
casing of struct user or user->inotify_devs.
Signed-off-by: default avatarEric Paris <eparis@redhat.com>
Cc: stable@kernel.org (2.6.37 and up)
Signed-off-by: default avatarLinus Torvalds <torvalds@linux-foundation.org>
parent 623dda65
...@@ -198,6 +198,7 @@ static void inotify_free_group_priv(struct fsnotify_group *group) ...@@ -198,6 +198,7 @@ static void inotify_free_group_priv(struct fsnotify_group *group)
idr_for_each(&group->inotify_data.idr, idr_callback, group); idr_for_each(&group->inotify_data.idr, idr_callback, group);
idr_remove_all(&group->inotify_data.idr); idr_remove_all(&group->inotify_data.idr);
idr_destroy(&group->inotify_data.idr); idr_destroy(&group->inotify_data.idr);
atomic_dec(&group->inotify_data.user->inotify_devs);
free_uid(group->inotify_data.user); free_uid(group->inotify_data.user);
} }
......
...@@ -290,7 +290,6 @@ static int inotify_fasync(int fd, struct file *file, int on) ...@@ -290,7 +290,6 @@ static int inotify_fasync(int fd, struct file *file, int on)
static int inotify_release(struct inode *ignored, struct file *file) static int inotify_release(struct inode *ignored, struct file *file)
{ {
struct fsnotify_group *group = file->private_data; struct fsnotify_group *group = file->private_data;
struct user_struct *user = group->inotify_data.user;
pr_debug("%s: group=%p\n", __func__, group); pr_debug("%s: group=%p\n", __func__, group);
...@@ -299,8 +298,6 @@ static int inotify_release(struct inode *ignored, struct file *file) ...@@ -299,8 +298,6 @@ static int inotify_release(struct inode *ignored, struct file *file)
/* free this group, matching get was inotify_init->fsnotify_obtain_group */ /* free this group, matching get was inotify_init->fsnotify_obtain_group */
fsnotify_put_group(group); fsnotify_put_group(group);
atomic_dec(&user->inotify_devs);
return 0; return 0;
} }
...@@ -697,7 +694,7 @@ static int inotify_update_watch(struct fsnotify_group *group, struct inode *inod ...@@ -697,7 +694,7 @@ static int inotify_update_watch(struct fsnotify_group *group, struct inode *inod
return ret; return ret;
} }
static struct fsnotify_group *inotify_new_group(struct user_struct *user, unsigned int max_events) static struct fsnotify_group *inotify_new_group(unsigned int max_events)
{ {
struct fsnotify_group *group; struct fsnotify_group *group;
...@@ -710,8 +707,14 @@ static struct fsnotify_group *inotify_new_group(struct user_struct *user, unsign ...@@ -710,8 +707,14 @@ static struct fsnotify_group *inotify_new_group(struct user_struct *user, unsign
spin_lock_init(&group->inotify_data.idr_lock); spin_lock_init(&group->inotify_data.idr_lock);
idr_init(&group->inotify_data.idr); idr_init(&group->inotify_data.idr);
group->inotify_data.last_wd = 0; group->inotify_data.last_wd = 0;
group->inotify_data.user = user;
group->inotify_data.fa = NULL; group->inotify_data.fa = NULL;
group->inotify_data.user = get_current_user();
if (atomic_inc_return(&group->inotify_data.user->inotify_devs) >
inotify_max_user_instances) {
fsnotify_put_group(group);
return ERR_PTR(-EMFILE);
}
return group; return group;
} }
...@@ -721,7 +724,6 @@ static struct fsnotify_group *inotify_new_group(struct user_struct *user, unsign ...@@ -721,7 +724,6 @@ static struct fsnotify_group *inotify_new_group(struct user_struct *user, unsign
SYSCALL_DEFINE1(inotify_init1, int, flags) SYSCALL_DEFINE1(inotify_init1, int, flags)
{ {
struct fsnotify_group *group; struct fsnotify_group *group;
struct user_struct *user;
int ret; int ret;
/* Check the IN_* constants for consistency. */ /* Check the IN_* constants for consistency. */
...@@ -731,31 +733,16 @@ SYSCALL_DEFINE1(inotify_init1, int, flags) ...@@ -731,31 +733,16 @@ SYSCALL_DEFINE1(inotify_init1, int, flags)
if (flags & ~(IN_CLOEXEC | IN_NONBLOCK)) if (flags & ~(IN_CLOEXEC | IN_NONBLOCK))
return -EINVAL; return -EINVAL;
user = get_current_user();
if (unlikely(atomic_read(&user->inotify_devs) >=
inotify_max_user_instances)) {
ret = -EMFILE;
goto out_free_uid;
}
/* fsnotify_obtain_group took a reference to group, we put this when we kill the file in the end */ /* fsnotify_obtain_group took a reference to group, we put this when we kill the file in the end */
group = inotify_new_group(user, inotify_max_queued_events); group = inotify_new_group(inotify_max_queued_events);
if (IS_ERR(group)) { if (IS_ERR(group))
ret = PTR_ERR(group); return PTR_ERR(group);
goto out_free_uid;
}
atomic_inc(&user->inotify_devs);
ret = anon_inode_getfd("inotify", &inotify_fops, group, ret = anon_inode_getfd("inotify", &inotify_fops, group,
O_RDONLY | flags); O_RDONLY | flags);
if (ret >= 0) if (ret < 0)
return ret; fsnotify_put_group(group);
fsnotify_put_group(group);
atomic_dec(&user->inotify_devs);
out_free_uid:
free_uid(user);
return ret; return ret;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment