Skip to content

Commit dfe719f

Browse files
thejhkees
authored andcommitted
seccomp: Make duplicate listener detection non-racy
Currently, init_listener() tries to prevent adding a filter with SECCOMP_FILTER_FLAG_NEW_LISTENER if one of the existing filters already has a listener. However, this check happens without holding any lock that would prevent another thread from concurrently installing a new filter (potentially with a listener) on top of the ones we already have. Theoretically, this is also a data race: The plain load from current->seccomp.filter can race with concurrent writes to the same location. Fix it by moving the check into the region that holds the siglock to guard against concurrent TSYNC. (The "Fixes" tag points to the commit that introduced the theoretical data race; concurrent installation of another filter with TSYNC only became possible later, in commit 5189149 ("seccomp: allow TSYNC and USER_NOTIF together").) Fixes: 6a21cc5 ("seccomp: add a return code to trap to userspace") Reviewed-by: Tycho Andersen <tycho@tycho.pizza> Signed-off-by: Jann Horn <jannh@google.com> Signed-off-by: Kees Cook <keescook@chromium.org> Cc: stable@vger.kernel.org Link: https://lore.kernel.org/r/20201005014401.490175-1-jannh@google.com
1 parent 282a181 commit dfe719f

1 file changed

Lines changed: 31 additions & 7 deletions

File tree

kernel/seccomp.c

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1476,13 +1476,7 @@ static const struct file_operations seccomp_notify_ops = {
14761476

14771477
static struct file *init_listener(struct seccomp_filter *filter)
14781478
{
1479-
struct file *ret = ERR_PTR(-EBUSY);
1480-
struct seccomp_filter *cur;
1481-
1482-
for (cur = current->seccomp.filter; cur; cur = cur->prev) {
1483-
if (cur->notif)
1484-
goto out;
1485-
}
1479+
struct file *ret;
14861480

14871481
ret = ERR_PTR(-ENOMEM);
14881482
filter->notif = kzalloc(sizeof(*(filter->notif)), GFP_KERNEL);
@@ -1508,6 +1502,31 @@ static struct file *init_listener(struct seccomp_filter *filter)
15081502
return ret;
15091503
}
15101504

1505+
/*
1506+
* Does @new_child have a listener while an ancestor also has a listener?
1507+
* If so, we'll want to reject this filter.
1508+
* This only has to be tested for the current process, even in the TSYNC case,
1509+
* because TSYNC installs @child with the same parent on all threads.
1510+
* Note that @new_child is not hooked up to its parent at this point yet, so
1511+
* we use current->seccomp.filter.
1512+
*/
1513+
static bool has_duplicate_listener(struct seccomp_filter *new_child)
1514+
{
1515+
struct seccomp_filter *cur;
1516+
1517+
/* must be protected against concurrent TSYNC */
1518+
lockdep_assert_held(&current->sighand->siglock);
1519+
1520+
if (!new_child->notif)
1521+
return false;
1522+
for (cur = current->seccomp.filter; cur; cur = cur->prev) {
1523+
if (cur->notif)
1524+
return true;
1525+
}
1526+
1527+
return false;
1528+
}
1529+
15111530
/**
15121531
* seccomp_set_mode_filter: internal function for setting seccomp filter
15131532
* @flags: flags to change filter behavior
@@ -1579,6 +1598,11 @@ static long seccomp_set_mode_filter(unsigned int flags,
15791598
if (!seccomp_may_assign_mode(seccomp_mode))
15801599
goto out;
15811600

1601+
if (has_duplicate_listener(prepared)) {
1602+
ret = -EBUSY;
1603+
goto out;
1604+
}
1605+
15821606
ret = seccomp_attach_filter(flags, prepared);
15831607
if (ret)
15841608
goto out;

0 commit comments

Comments
 (0)