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-MemoryRegionmem_reentrancy_guardile 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:
- 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.
- 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. - 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
MemoryRegionOpscallback'inde ya da bottom half'inde kök salan host tarafı ASan UAF/double-free ya da SIGSEGV raporları. - Guarded build'lerde,
mem_reentrancy_guardreddetme 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
--fuzzgeneric 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¶
- QEMU patch series "memory: prevent dma-reentrancy issues" (v10, Alexander Bulekov)
- GitLab issue #556 — Fix DMA MMIO reentrancy issues
- Red Hat — CVE-2024-3446 virtio DMA reentrancy (guard gap)
- Related: virtio DMA reentrancy double-free, e1000e DMA reentrancy UAF, NVMe DMA reentrancy UAF, Tulip device DMA reentrancy