unshare: Unsharing a thread does not require unsharing a vm
In the logic in the initial commit of unshare made creating a new
thread group for a process, contingent upon creating a new memory
address space for that process. That is wrong. Two separate
processes in different thread groups can share a memory address space
and clone allows creation of such proceses.
This is significant because it was observed that mm_users > 1 does not
mean that a process is multi-threaded, as reading /proc/PID/maps
temporarily increments mm_users, which allows other processes to
(accidentally) interfere with unshare() calls.
Correct the check in check_unshare_flags() to test for
!thread_group_empty() for CLONE_THREAD, CLONE_SIGHAND, and CLONE_VM.
For sighand->count > 1 for CLONE_SIGHAND and CLONE_VM.
For !current_is_single_threaded instead of mm_users > 1 for CLONE_VM.
By using the correct checks in unshare this removes the possibility of
an accidental denial of service attack.
Additionally using the correct checks in unshare ensures that only an
explicit unshare(CLONE_VM) can possibly trigger the slow path of
current_is_single_threaded(). As an explict unshare(CLONE_VM) is
pointless it is not expected there are many applications that make
that call.
Cc: stable@vger.kernel.org
Fixes: b2e0d98705
userns: Implement unshare of the user namespace
Reported-by: Ricky Zhou <rickyz@chromium.org>
Reported-by: Kees Cook <keescook@chromium.org>
Reviewed-by: Kees Cook <keescook@chromium.org>
Signed-off-by: "Eric W. Biederman" <ebiederm@xmission.com>
This commit is contained in:
parent
75509fd88f
commit
12c641ab82
1 changed files with 18 additions and 10 deletions
|
@ -1866,13 +1866,21 @@ static int check_unshare_flags(unsigned long unshare_flags)
|
|||
CLONE_NEWUSER|CLONE_NEWPID))
|
||||
return -EINVAL;
|
||||
/*
|
||||
* Not implemented, but pretend it works if there is nothing to
|
||||
* unshare. Note that unsharing CLONE_THREAD or CLONE_SIGHAND
|
||||
* needs to unshare vm.
|
||||
* Not implemented, but pretend it works if there is nothing
|
||||
* to unshare. Note that unsharing the address space or the
|
||||
* signal handlers also need to unshare the signal queues (aka
|
||||
* CLONE_THREAD).
|
||||
*/
|
||||
if (unshare_flags & (CLONE_THREAD | CLONE_SIGHAND | CLONE_VM)) {
|
||||
/* FIXME: get_task_mm() increments ->mm_users */
|
||||
if (atomic_read(¤t->mm->mm_users) > 1)
|
||||
if (!thread_group_empty(current))
|
||||
return -EINVAL;
|
||||
}
|
||||
if (unshare_flags & (CLONE_SIGHAND | CLONE_VM)) {
|
||||
if (atomic_read(¤t->sighand->count) > 1)
|
||||
return -EINVAL;
|
||||
}
|
||||
if (unshare_flags & CLONE_VM) {
|
||||
if (!current_is_single_threaded())
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
|
@ -1940,16 +1948,16 @@ SYSCALL_DEFINE1(unshare, unsigned long, unshare_flags)
|
|||
*/
|
||||
if (unshare_flags & CLONE_NEWUSER)
|
||||
unshare_flags |= CLONE_THREAD | CLONE_FS;
|
||||
/*
|
||||
* If unsharing a thread from a thread group, must also unshare vm.
|
||||
*/
|
||||
if (unshare_flags & CLONE_THREAD)
|
||||
unshare_flags |= CLONE_VM;
|
||||
/*
|
||||
* If unsharing vm, must also unshare signal handlers.
|
||||
*/
|
||||
if (unshare_flags & CLONE_VM)
|
||||
unshare_flags |= CLONE_SIGHAND;
|
||||
/*
|
||||
* If unsharing a signal handlers, must also unshare the signal queues.
|
||||
*/
|
||||
if (unshare_flags & CLONE_SIGHAND)
|
||||
unshare_flags |= CLONE_THREAD;
|
||||
/*
|
||||
* If unsharing namespace, must also unshare filesystem information.
|
||||
*/
|
||||
|
|
Loading…
Reference in a new issue