Double-fetch / shared-memory ring TOCTOU from guest memory¶
Bir device model, guest ile paylaşılan bir değeri iki kez okur; guest aradaki süreçte onu mutate eder.
Mechanism¶
Bir hypervisor/device-model, guest ile paylaşılan bellekten bir değeri (offset, length, command type) okur, validate eder ve ardından kullanım için onu tekrar okur. Validate eden fetch ile kullanım fetch'i arasında ikinci bir guest vCPU değeri mutate eder; böylece bounds check güvenli bir değer üzerinde geçer, kullanım ise kötü niyetli bir değer üzerinde işler. Bu, tek bir paylaşılan konuma özelleşmiş CWE-367 (TOCTOU)'dur — dolayısıyla "double-fetch".
Warning
C'de bir field'ı "bir kez" okuyan kod bile iki memory load'a derlenebilir ve
paylaşılan pointer volatile olmadığı sürece race'i yeniden ortaya çıkarır.
QEMU'nun rehberliği açıktır: device emulation, descriptor'ları/struct'ları guest
RAM'inden kopyalamalı ve yalnızca yerel kopyayı işlemelidir, çünkü multi-vCPU
guest'ler emulation çalışırken guest RAM'i mutate edebilir.
Walkthrough¶
CVE-2018-2844 (VirtualBox VBVA/VDMA): handler pCmd->enmType'ı guest ile paylaşılan
VRAM'den okur. GCC, bounds check'i ve jump-table dispatch'ini aynı field'ın iki ayrı
load'u olarak derledi:
cmp dword ptr [r12], 0Ah ; FETCH 1: bounds-check enmType <= 10
ja DEFAULT_CASE
mov eax, [r12] ; FETCH 2: re-read enmType for jump index
movsxd rax, dword ptr [rbx+rax*4]
add rax, rbx
jmp rax ; dispatch on the second, attacker-mutated value
İkinci bir vCPU, iki load arasında shared buffer'daki enmType'ı yeniden yazar;
böylece check geçer (değer <= 10) ama jump out-of-bounds bir index kullanır ve
control flow'u yeniden yönlendirir. virtio analoğu, bir vring descriptor'ının
addr/len'i üzerinde aynı pattern'dir: length'i validate et, sonra kopya için onu
yeniden oku — bu yüzden QEMU descriptor'ların kullanımdan önce yerel olarak
kopyalanmasını zorunlu kılar.
Detection¶
Dynamic double-fetch detector'lar (örn. cache-coherence / record-replay tooling) tek
bir handler içinde aynı guest adresinin iki load'unu işaretler. Guest RAM'in
non-volatile read'lerini takip eden re-read'ler için code review; shared ring'i
mutate eden concurrent bir vCPU ile fuzzing.
Mitigation¶
Değeri/descriptor'ı bir kez hypervisor-local storage'a kopyala, sonra yalnızca
kopyayı validate et ve kullan (single-fetch disiplini). Compiler'ın bir read'i ikiye
bölmesini durdurmak için shared mapping'leri volatile olarak işaretle.