seccomp: don't leak memory when filter install races

In seccomp_set_mode_filter() with TSYNC | NEW_LISTENER, we first initialize
the listener fd, then check to see if we can actually use it later in
seccomp_may_assign_mode(), which can fail if anyone else in our thread
group has installed a filter and caused some divergence. If we can't, we
partially clean up the newly allocated file: we put the fd, put the file,
but don't actually clean up the *memory* that was allocated at
filter->notif. Let's clean that up too.

To accomplish this, let's hoist the actual "detach a notifier from a
filter" code to its own helper out of seccomp_notify_release(), so that in
case anyone adds stuff to init_listener(), they only have to add the
cleanup code in one spot. This does a bit of extra locking and such on the
failure path when the filter is not attached, but it's a slow failure path
anyway.

Fixes: 51891498f2 ("seccomp: allow TSYNC and USER_NOTIF together")
Reported-by: syzbot+3ad9614a12f80994c32e@syzkaller.appspotmail.com
Signed-off-by: Tycho Andersen <tycho@tycho.pizza>
Acked-by: Christian Brauner <christian.brauner@ubuntu.com>
Link: https://lore.kernel.org/r/20200902014017.934315-1-tycho@tycho.pizza
Signed-off-by: Kees Cook <keescook@chromium.org>
This commit is contained in:
Tycho Andersen 2020-09-01 19:40:16 -06:00 committed by Kees Cook
parent d012a7190f
commit a566a9012a

View File

@ -1109,13 +1109,12 @@ static long seccomp_set_mode_strict(void)
}
#ifdef CONFIG_SECCOMP_FILTER
static int seccomp_notify_release(struct inode *inode, struct file *file)
static void seccomp_notify_detach(struct seccomp_filter *filter)
{
struct seccomp_filter *filter = file->private_data;
struct seccomp_knotif *knotif;
if (!filter)
return 0;
return;
mutex_lock(&filter->notify_lock);
@ -1142,6 +1141,13 @@ static int seccomp_notify_release(struct inode *inode, struct file *file)
kfree(filter->notif);
filter->notif = NULL;
mutex_unlock(&filter->notify_lock);
}
static int seccomp_notify_release(struct inode *inode, struct file *file)
{
struct seccomp_filter *filter = file->private_data;
seccomp_notify_detach(filter);
__put_seccomp_filter(filter);
return 0;
}
@ -1581,6 +1587,7 @@ static long seccomp_set_mode_filter(unsigned int flags,
listener_f->private_data = NULL;
fput(listener_f);
put_unused_fd(listener);
seccomp_notify_detach(prepared);
} else {
fd_install(listener, listener_f);
ret = listener;