Skip to content

Hypercall fuzzing (vmcall handler fuzzing)

Bir hypervisor'ın hypercall / VMCALL(Intel) / VMMCALL(AMD) handler interface'inin coverage-guided fuzzing'i ile privileged call surface'inde guest-to-host memory-safety bug'larını bulma.

Mechanism

Note

Hypercall, bir guest'in hypervisor'ın içine call yapmasının mimari olarak tanımlanmış yoludur. Intel'de VMCALL instruction'ı, AMD'de VMMCALL'dır; Hyper-V opcode'u guest'e map'lenmiş bir hypercall page arkasında soyutlar, böylece guest sadece onun ilk byte'ını CALL'lar. Her durumda CPU bir VM-exit alır ve hypervisor, guest'in kontrol ettiği bir call code'a göre dispatch eder. Microsoft TLFS'e göre guest, RCX içinde 64-bit bir hypercall input value geçirir: bit 15-0 call code, bit 16 Fast flag'i (0 = memory / "slow", parametreler bir guest-physical address'te durur; 1 = register / "fast"), bit 26-17 bir variable-header size, bit 43-32 bir rep count'tur. Slow call'lar için RDX input-parameter GPA'sını, R8 output GPA'sını tutar; fast call'lar için RDX/R8 (ve 112-byte'lık XMM-fast formu için XMM0-5) parametreleri doğrudan taşır. Sonuç RAX içinde bir HV_STATUS code olarak geri döner.

Bir fuzzer'ın suistimal ettiği invariant tam olarak budur: call code ve parameter block tamamen guest-attacker-controlled'dur ve handler, sistemdeki en yüksek privilege boundary'sinde çalışır. Eğer bir handler gömülü bir length, GPA veya rep count'a validation yapmadan güvenirse, hatalı biçimlenmiş bir input value host içinde bir OOB read/write'a yol açar. Fuzzing, o handler'lara ulaşmak için RCX'i artı parameter block'u sistematik olarak mutate eder.

Walkthrough

Microsoft TLFS hypercall interface'ine ve SafeBreach/Guardicore'un hAFL1'ine (kAFL tabanlı Hyper-V fuzzer) dayanır.

  1. ABI'yi konuş. Guest tarafındaki bir harness, interface'i TLFS'in öngördüğü şekilde kurar: CPUID 0x40000001 ile Hv#1'i doğrula, Guest OS ID MSR'ını (HV_X64_MSR_GUEST_OS_ID, 0x40000000) yaz, sonra hypercall page GPFN'ini + enable bit'ini HV_X64_MSR_HYPERCALL'a (0x40000001) programla. Ardından ham bir input value birleştirilir, örneğin Fast bit'i set edilmiş 0x0008 call code'u:
// memory-based ("slow") hypercall: input/output via GPAs
uint64_t input_value = (call_code & 0xFFFF);   // Fast=0 => slow
// RCX = input_value, RDX = input_gpa, R8 = output_gpa, then CALL hv_page

Fuzzer'ın mutation target'ı {input_value, *input_gpa}'dır.

  1. Coverage-guided bir harness altında sür. hAFL1, modifiye edilmiş bir kAFL'dir. Burada iki ayrı hypercall rolünü karıştırmamak gerekir: (1) control channel — L0'daki kAFL/Nyx fuzzer, fuzzing payload'unu guest user-mode harness'a kAFL'in kendi agent-protocol hypercall'ları (guest↔KVM sync/payload handoff) üzerinden teslim eder; harness sonra bunu IOCTL ile bir kernel harness driver'ına (Harness.sys) iletir. (2) attack surface — asıl mutate edilen şey ise target hypervisor'ın hypercall input value/parameter block'udur, yani harness'ın hedefe issue ettiği hypercall'ın kendisi. Yani kAFL'in transport hypercall'ı, fuzz'lanan Hyper-V hypercall'ı ile aynı şey değildir. loader.exe her crash'ten sonra dönülecek VM state'inin snapshot'ını alır; coverage feedback Intel-PT'den gelir. hAFL1 vmswitch.sys'i (VMBus üzerinden RNDIS) hedefler ve libprotobuf-mutator ile structure-awareness ekler, ama aynı harness pattern'i generic hypercall dispatcher'ı da fuzz'lar.

  2. Snapshot + restore loop. Hatalı bir hypercall host state'ini bozabildiği için loop snapshot tabanlıdır: restore et, bir mutate edilmiş input inject et, çalıştır, exit/crash'i yakala, tekrarla — kAFL/Nyx modeli.

  3. Beklenen çıktı. Çoğu input RAX içinde zararsız bir HV_STATUS döner (örneğin bilinmeyen bir call code için HV_STATUS_INVALID_HYPERCALL_CODE, bozuk bir rep count / reserved bit için HV_STATUS_INVALID_HYPERCALL_INPUT). Bir bug ise kendini host hang, bir hypervisor bugcheck, ya da bir handler'a giden Intel-PT ile doğrulanmış yeni bir path (sonrasında fault'lanan) olarak gösterir — fuzzer'ın triage ettiği sinyal budur.

Warning

Reserved-bit ve alignment check'leri (HV_STATUS_INVALID_ALIGNMENT, 8-byte GPA alignment, page-crossing parameter list yasağı) naif mutation'ların büyük bir kısmını erkenden reddeder. Input value layout'una ve call-code başına parameter struct'larına saygı gösteren structure-aware bir mutator, handler'lara RCX'i byte-flip'lemekten çok daha derin ulaşır.

Detection

  • Host/defender tarafından: anormal oranda başarısız olan HV_STATUS code'ları, alışılmadık call-code dağılımları ya da tek bir partition'dan gelen tekrarlı invalid-input dönüşleri, fuzz'layan veya yoklayan bir guest'in güçlü göstergeleridir. Hyper-V/KVM hypercall failure'larını VP başına rate-limit'leyip loglayabilir.
  • Crash telemetry: snapshot-restore fuzzer'ları host tarafında patlamalar halinde fault ve VM reset üretir; tek bir guest'e bağlı kümelenmiş bugcheck'ler bir ipucudur.
  • Timing: snapshot/restore ve Intel-PT mekanizması guest timing'ini bozar; bu, wall-clock'u retired-cycle drift'ine karşı karşılaştıran guest içindeki bir watcher tarafından gözlemlenebilir.

Mitigation

  • Hypercall input value'nun guest tarafından kontrol edilen her field'ını (call code aralığı, Fast flag'i, variable-header size, rep count/start) parameter block'a dokunmadan önce, tam olarak TLFS common status code'larının öngördüğü gibi validate et.
  • Input/output GPA'larını untrusted kabul et: her erişimde mapped/readable/ writable'ı yeniden validate et ve alignment ile page-crossing yasağını zorla.
  • Dispatch table'ı minimal tut ve privileged call code'ları partition privilege'larına göre gate'le; ship etmeden önce kendi surface'ini fuzz'la (KVM için syzkaller, Hyper-V için hAFL1/kAFL/Nyx).

References