Skip to content

QEMU MemoryRegionOps NULL deref MMIO (CVE-2020-15469)

Bazı emulated MemoryRegionOps, I/O region'larını read veya write callback'i olmadan register ediyordu; guest'in böyle bir region'a yaptığı MMIO access NULL bir handler'ı dereference ederek QEMU'yu crash'liyor (guest-to-host DoS).

Mechanism

Bug class: populate edilmemiş bir callback table üzerinden dispatch

QEMU, guest'in memory-mapped I/O'sunu davranışı bir MemoryRegionOps structure'ı — esasen onun .read ve .write function pointer'ları — tarafından tanımlanan MemoryRegion object'leri üzerinden yönlendirir. MMIO dispatch layer'ı, bir guest access'inde hedef region'ı bulup uygun ops->read() / ops->write() callback'ini çağırır.

Dispatcher'ın varsaydığı invariant, memory_region_init_io() ile register edilen herhangi bir region'ın kullanılabilir read ve write handler'ları sağladığıdır. QEMU 4.2.0'da çeşitli device/host bridge'lerdeki (PCI host controller'lar, PCIe MSI, VFIO ve diğerleri) birkaç MemoryRegionOps instance'ı, callback'lerinden biri veya her ikisi de eksik (NULL) olacak şekilde declare edilmişti. Guest böyle bir region'a denk gelen bir MMIO read ya da write yaptığında, dispatcher bir NULL function pointer'ı çağırır (veya bir NULL ops slot'unu dereference eder) ve host process'i fault'a sokar.

Sınır şu yüzden aşılır: tamamen legal bir guest MMIO access'i, hiç bağlanmamış bir host code path'ine ulaşır ve sıradan device access'ini bir host crash'ine dönüştürür.

Related variant: CVE-2020-27821 (ayrı, bağımsız disclose edilen CVE)

Yakın ama farklı bir bug, memory-region cache'in initialize edilmesi sırasında ortaya çıkar: bir cache size calculation kusuru, MMIO sırasında MSI-X table'a karşı bir out-of-bounds write'a yol açabilir (guest-to-host DoS). Bu, root cause'u (eksik MemoryRegionOps callback'i değil, hatalı cache size hesabı) ve disclosure'u bu not'un konusu olan CVE-2020-15469'dan ayrı olan CVE-2020-27821'dir. İkisini karıştırmamak gerekir: aynı emulated-MMIO alanına dokunsalar da bağımsız flaw'lardır.

Walkthrough

Public advisory'ye dayanan kavramsal reproduction.

  1. Guest, MemoryRegionOps'unda .read veya .write handler'ı eksik olan bir device/region tespit eder (bunlar etkilenen 4.2.0 source'larından enumerate edilebiliyordu).

  2. Guest, o region'ın BAR/aperture'üne bir MMIO read ya da write gönderir.

Conceptual dispatch (logical fragments, not an exploit)
# Emulator side (paraphrased):
static const MemoryRegionOps some_ops = {
    /* .read  = ...   <-- missing in the vulnerable code */
    /* .write = ...   <-- or this one missing */
    .endianness = DEVICE_NATIVE_ENDIAN,
};

# On a guest MMIO access the dispatcher does, roughly:
mr->ops->write(mr->opaque, addr, val, size);   # ops->write == NULL -> fault
  1. Dispatcher NULL pointer üzerinden çağrı yapar; QEMU SIGSEGV alır ve guest (ve o QEMU üzerindeki tüm co-tenant'lar) hypervisor process'ini kaybeder.

Tek bir device değil, tüm bir class

Fix birden fazla MemoryRegionOps tanımına dokundu. Çıkarılacak ders bir pattern: tek seferlik bir device bug'ı değil — herhangi bir memory_region_init_io() registration'ı handler sağlamalı (ya da framework eksik olanları reject/stub etmeli).

Düzeltme, etkilenen MemoryRegionOps structure'larına eksik read/write callback metotlarını ekledi; böylece erişilebilir her MMIO region'ı geçerli bir handler'a sahip oldu (ve dispatch artık check etmeden non-NULL ops varsaymıyor).

Detection

Host ve telemetry sinyalleri

  • Crash signature: memory dispatch path'i içinde (memory_region_dispatch_read/write -> indirect call) faulting instruction'ı NULL / near-NULL bir call target'ta olan, bir guest MMIO access'i ile korele QEMU SIGSEGV'i.
  • ASAN/debug build'leri: ops->read/ops->write invocation'ında rapor edilen NULL-pointer-call veya NULL read/write; ayrı CVE-2020-27821 (MSI-X cache) varyantında ise MSI-X table region'ına karşı bir out-of-bounds write.
  • Anomali: bir guest'in host-bridge / MSI-X / VFIO region'larının BAR'larını normal bir driver'ın yapmayacağı şekilde probe/poke etmesi.
  • Fleet pattern: MMIO access'inde tekrarlayan single-tenant QEMU crash'leri, handle edilmeyen bir region dispatch bug'ının güçlü bir göstergesidir.

Mitigation

Remediation

  • Patch: eksik MemoryRegionOps callback'lerini ekleyen bir QEMU sürümüne yükselt (upstream'de 5.0.x serisinde düzeltildi; distro backport'ları Debian LTS, Oracle, Ubuntu vb. tarafından dağıtıldı).
  • Attack surface'i azalt: guest'in yalnızca ihtiyaç duyduğu device'ları sun; daha az emulated MMIO region, daha az erişilebilir ops table'ı demektir.
  • Defense in depth: QEMU'yu -sandbox on (seccomp) ve SELinux/AppArmor svirt altında çalıştır; böylece bir crash izole ve atfedilebilir olur.
  • Engineering practice: NULL .read/.write içeren herhangi bir memory_region_init_io()'yu review/static analysis'te bir defect olarak gör — unset bırakmak yerine explicit handler'lar sağla (log/ignore edenler bile olsa).

References