Open Source im professionellen Einsatz

[Update] Qemu-KVM: Gastsystem bricht aus VM aus

Bei der Virtualisierung spielt Linux unter anderem mit KVM in der obersten Liga mit. Dabei verlässt sich der KVM-Anwender darauf, dass das Gastsystem ausschließlich innerhalb der virtuellen Maschine (VM) läuft und dem eigentlichen Host nichts anhaben kann. Dem ist aber nicht immer so, wie eine Schwachstelle zeigt, die in der jüngsten Qemu-Version korrigiert wurde.

Die Kernel-based Virtual Machine (KVM) ist eine Linux-Kernel-Infrastruktur für Virtualisierung, die sich auf Systemen mit entsprechenden Prozessoren (Intel VT oder AMD-V) zum Laufen bringen lässt. KVM selbst ist zunächst einmal nur eine Ansammlung von Kernel-Modulen (wie "kvm.ko", "kvm-intel.ko" oder "kvm-amd.ko"), Sie übernimmt nicht die eigentliche Emulation, sondern bietet lediglich die notwendige Infrastruktur an. Zur Emulation kommt unter Linux ein modifiziertes Qemu zum Einsatz, und der Linux-Kernel selbst arbeitet dabei als Hypervisor. Auch unterstützt KVM Paravirtualisierung zum Zugriff auf Hardware, was speziell bei I/O-intensive Gastanwendungen zu erheblichen Performance-Steigerungen bei Latenz und Durchsatz führt.

Eine Ende April erstmals entdeckte Schwachstelle in Qemu wurde kürzlich in einigen Red-Hat-Linux-Versionen korrigiert. Sie erlaubt es einem Angreifer auf dem Gastsystem, die VM zum Absturz zu bringen oder sogar Befehle auf dem Hostsystem auszuführen.

Die Entdeckung des Problems begann mit dem Kernelentwickler Conor Murphy, der einen paravirtualisierten Virtio-blk-Treiber für Solaris schreiben wollte, und dabei über einen Qemu-Crash durch folgenden Befehl stolperte:

dd if=/dev/zero of=/dev/rdsk/c2d10p0 bs=5000 count=1

Das Kommando versucht, auf ein Block-Device 5000 Bytes zu schreiben. Es stellte sich heraus, dass Qemu hier Probleme hat, weil die 5000 Bytes nicht von "virtio_blk_handle_write()" in 512 Byte-Blöcke aufgeteilt werden können. Im Prinzip sollte sich Qemu über eine solche Schreibanfrage beschweren und nicht abstürzen. Zu dem Absturz aber kommt es, weil Qemu intern die Sektorenzahl via

blkreq->nb_sectors = req->qiov.size / BDRV_SECTOR_SIZE;

berechnet. Das ergibt im vorliegenden Fall als Integer-Operation (5000/512)=9. Bei "blkreq" handelt es sich um eine Struktur aus "block.h", die für den Blockzugriff relevante Informationen speichert:

typedef struct BlockRequest {
    /* Fields to be filled by multiwrite caller */
    int64_t sector;
    int nb_sectors;
    QEMUIOVector *qiov;
    BlockDriverCompletionFunc *cb;
    void *opaque;

    /* Filled by multiwrite implementation */
    int error;
} BlockRequest;

Nachdem die Sektorenzahl bestimmt ist, alloziert der Code einen Speicherbereich der Größe 9 * 512 Bytes. Das ist natürlich zu klein, um die ganzen 5000 Bytes zu halten, und als Folge davon schreibt Qemu über die Grenzen des Puffers hinaus - ein typischer Buffer Overflow, der entweder zum Absturz von Qemu führt, oder aber, falls geschickt angestellt, zum Ausführen von Befehlen auf dem Hostsystem ausgenutzt werden kann.

Der naheliegende Patch besteht darin, in der Funktion "virtio_blk_handle_write()"-Funktion (in "qemu/hw/virtio-blk.c") die Größe des Schreibzugriffs zu prüfen, und diesen nur dann zuzulassen, wenn die Bytezahl ein Vielfaches der logischen Blockgröße des Geräts beträgt:

if (req->qiov.size % req->dev->conf->logical_block_size) {
   virtio_blk_rw_complete(req, -EIO);
   return;
}

Betroffen sind Qemu-Versionen vor 0.14.1.

comments powered by Disqus

Stellenmarkt

Artikelserien und interessante Workshops aus dem Magazin können Sie hier als Bundle erwerben.