Skip to content

QEMU address_space_map / DMA reentrancy reentry into device emulation

Bir device emulation bug sınıfı: bir guest bir DMA descriptor'ını device MMIO'ya işaret ettirir; böylece QEMU'nun address_space_map() / dispatch'i aynı (ya da başka bir) device'ın I/O callback'ine o operation'ın ortasındayken re-enter eder ve host state'ini corrupt eder. Upstream'de per-MemoryRegion mem_reentrancy_guard ile mitigate edildi.

Mechanism

Note

Guest DMA'sına hizmet etmek için QEMU bir guest physical address'i translate eder ve onu address_space_map() ile host process'ine map'ler (ya da erişimleri memory_region_dispatch_read/write üzerinden dispatch eder). Gizli varsayım, bir DMA hedefinin sıradan guest RAM olduğudur. Ama bir guest, descriptor address'inin onun yerine bir device MMIO region'ına çözülmesini sağlayabilir. QEMU sonra DMA erişimini gerçekleştirdiğinde, dispatch katmanı o region'ın MemoryRegionOps read/write callback'ini invoke eder — yani erişim device emulation'ına re-enter eder. Bu, originating device callback'i hâlâ stack'teyken olursa, device'ın handler'ı kendi yarı-güncellenmiş state'i üzerinde recursive olarak çalışır. QEMU'nun tutması gereken isolation invariant'ı şu: bir device bir isteği işlerken, guest tarafından tetiklenen bir DMA, bir device handler'ının örtüşen state üzerinde tekrar çalışmasına yol açmamalıdır. Bunu ihlal etmek, write primitive'i host QEMU process'inde çalışan klasik memory-safety bug'ları üretir: use-after-free (e1000e, NVMe), double-free (virtio) ya da corrupt olmuş bir SCSI script pointer'ı (lsi53c895a / CVE-2023-0330). İki somut şekil vardır: mmio -> dma -> mmio (bir device MMIO handler'ı, MMIO'ya re-enter eden bir DMA gönderir) ve bh -> dma write -> mmio (schedule edilmiş bir bottom half bunu yapar). Re-entry, guest tarafından kontrol edilen descriptor verisinden host control flow'una geçtiği için, bu sınıf tekrar tekrar guest->host escape'leri üretir.

Walkthrough

Herkese açık, patch'lenmiş materyal: Alexander Bulekov'un upstream serisi "memory: prevent dma-reentrancy issues" (QEMU 8.1'de merge edildi, GitLab issue #556; lsi53c895a reentrancy / CVE-2023-0330 tarafından motive edildi). Yalnızca kavramsal path:

  1. Guest, bir device'ın descriptor'ını (ring entry, SGL, command block) buffer/next-pointer address'i RAM yerine bir device MMIO region'ı içine düşecek şekilde programlar.
  2. Device'ın I/O callback'i (ya da onun bottom half'i) DMA helper'larına çağrı yapar; bunlar erişimi address_space_map() / dispatch eder ve bir device callback'ini tekrar invoke eder.
  3. Nested invocation paylaşılan device state'ini mutate eder — dış call'un hâlâ referans verdiği bir nesneyi free ederek (UAF / double-free) ya da bir script/DMA pointer'ı yeniden yazarak — tamamen guest input'uyla sürülen bir host tarafı corruption primitive'i verir.

Temsili fix şekli (atıf yapılan seriden):

/* MemoryRegion gains a reentrancy guard */
struct MemReentrancyGuard { bool engaged_in_io; };

/* dispatch checks/sets it around the device callback */
if (mr->dev && mr->dev->mem_reentrancy_guard.engaged_in_io) {
    /* nested re-entry into a device already in I/O -> reject/abort the access */
}
mr->dev->mem_reentrancy_guard.engaged_in_io = true;
... call MemoryRegionOps->write() ...
mr->dev->mem_reentrancy_guard.engaged_in_io = false;

bh -> dma -> mmio durumu için seri, bir device'ın kendi reentrancy guard'ını kendi bottom half'ine bağlayabilmesi için qemu_bh_new_guarded() ekledi; SCSI/USB/virtio/ NVMe/display/net boyunca ~40 dosya guarded creator'a geçirildi.

Patch'lenmemiş bir build'de gözlemlenebilir etki

Guard'dan önceki build'lerde, device MMIO'ya nişanlanan descriptor'lar bir device callback'i içinde ASan use-after-free / double-free raporları olarak ya da corrupt olmuş bir DMA/script pointer'ından gelen bir SIGSEGV olarak ortaya çıkar. Guard, recursive re-entry'yi reddedilmiş bir erişime (ve bir tracepoint'e) dönüştürür, dolayısıyla patolojik path artık state'i corrupt etmez.

Warning

Tarihsel, düzeltilmiş bir bug sınıfı (guard QEMU 8.1'de merge edildi; CVE-2023-0330 ve ilgili e1000e/NVMe/virtio reentrancy CVE'leri). Yalnızca açıklanmış yapı ve upstream guard gösterilir — hiçbir offset, heap-grooming ya da uçtan uca escape chain yok.

Detection

  • Guest bir DMA descriptor'ını MMIO'ya nişanladıktan sonra, bir device'ın MemoryRegionOps callback'inde ya da bottom half'inde kök salan host tarafı ASan UAF/double-free ya da SIGSEGV raporları.
  • Guarded build'lerde, mem_reentrancy_guard reddetme path'i (tracepoint'ler / rate-limited "nested I/O" uyarıları) re-enter edilen device'ları işaretler.
  • DMA-reentrancy fuzzing'i (device MMIO'ya işaret eden descriptor'lar; QEMU --fuzz generic device harness'i, HYPERPILL tarzı hardware-assisted fuzzing) sınıfı yeniden üretir.

Mitigation

  • mem_reentrancy_guard / qemu_bh_new_guarded() serisini taşıyan QEMU 8.1+'a (ya da bir distro backport'una) güncelle; üstüne per-device takip fix'lerini (e1000e, NVMe, virtio CVE-2024-3446) uygula.
  • Meşru biçimde re-enter eden ve opt-out olması gereken bir device için, upstream yaklaşımı açık, audit edilmiş bir per-device disable'dır — guard'ı global olarak kaldırmak değil.
  • QEMU'yu sVirt/SELinux + seccomp ile unprivileged çalıştır; gereksiz emulated device'ları guest'e açma (DMA-capable attack surface'i küçült).

References