Skip to content

QEMU USB usb_process_one out-of-bounds R/W (setup_len) VM escape

QEMU'nun USB core'u, guest-controlled bir setup_len'i validate etmeden önce atadı, böylece bozuk bir USB SETUP packet'i, do_token_in / do_token_out'u 4096-byte'lık data_buf'ın ötesine read veya write yapmaya sürükleyebilir ve host memory corruption'ı ile tam bir VM escape'i mümkün kılabilirdi (CVE-2020-14364).

Mechanism

İzolasyon invariant'ı: bir guest length alanı, host buffer erişimini geçirmeden önce validate edilmelidir

USB control transfer'leri, wLength alanı device'a kaç data byte'ının takip ettiğini söyleyen 8-byte'lık bir SETUP packet'i taşır. QEMU'nun USB core'u, bu byte'ları sabit bir buffer olan USBDevice->data_buf[4096] içinde saklar ve ilerlemeyi USBDevice->setup_len ile izler. Invariant şu: setup_len, asla sizeof(data_buf)'ı aşmamalıdır, çünkü sonraki data-stage handler'ları (do_token_in, do_token_out, usb_process_one'dan erişilir) data_buf'a/data_buf'tan setup_len byte kopyalar.

Vulnerable kodda, do_token_setup() / do_parameter(), s->setup_len = (s->setup_buf[7] << 8) | s->setup_buf[6] hesapladı ve bunu struct alanına atadı, ardından if (s->setup_len > sizeof(s->data_buf)) kontrol etti. Kontrol var olsa da, bazı path'lerde aralık dışı değer zaten s->setup_len'e işlenmişti ve etkisiz hâle getirilmeden önce data stage tarafından tüketilebiliyordu, böylece do_token_in/out bir index'i 4096'dan büyük guest tarafından seçilen bir değere kadar ilerletti. Bu, data_buf'a komşu host heap memory'sinin out-of-bounds read ve write'ıdır. data_buf, host QEMU process'inde yaşadığı için, bir guest host memory'sinde bir OOB R/W primitive'ini tamamen kontrol eder — bir VM escape'in temeli (QEMU process olarak arbitrary code execution, QEMU root olarak çalışıyorsa root).

Walkthrough

oss-security açıklamasına (Gerd Hoffmann'ın 0001-usb-fix-setup-len-init.patch'i) ve openEuler yazısına dayanır. Ön koşul: VM'in en az bir USB device'ı bağlı.

  1. Guest, SETUP stage'i setup_buf[6..7] içinde 4096'dan büyük bir wLength (örn. 0xffff) kodlayan bir USB control transfer'i verir.

  2. do_token_setup(), s->setup_len'i bu byte'lardan hesaplar ve atar. Data stage (usb_process_onedo_token_in / do_token_out) ardından data_buf'ı indekslemek için setup_len'i kullanır.

  3. setup_len > sizeof(data_buf) ile copy, 4096-byte'lık buffer'ın ötesine — IN'de OOB read, OUT'ta OOB write — komşu host heap'e yürür.

    Fix'in kavramsal şekli (bir local'e validate et, sonra commit et)

    /* hw/usb/core.c — direction of the upstream fix */
    int len = (s->setup_buf[7] << 8) | s->setup_buf[6];
    if (len > sizeof(s->data_buf)) {
        /* reject before it can gate data_buf access */
        s->setup_len = 0;
        s->setup_state = SETUP_STATE_ACK;
        return;
    }
    s->setup_len = len;   /* only assigned once known-good */
    
    Fix, uzunluğu bir local değişkende validate eder ve overflow'da setup_len/setup_state'i clamp eder, böylece data-stage handler'ları asla data_buf'ın ötesini indeksleyemez.

  4. Gözlemlenebilir sonuç: patch'siz core'da OOB erişimi host heap'i bozar. Özenle hazırlanmış bir layout, kontrollü OOB write'ı bir exploitation primitive'ine çevirir; minimal olarak QEMU'yu crash eder (DoS), en kötü durumda host code execution verir (escape).

Tarihî / patch'li — burada weaponize edilmiş chain yok

Bu kayıt yalnızca bug sınıfını ve public reproduction path'ini belgeler. Kasıtlı olarak heap-grooming, leak veya control-flow-hijack adımları sağlamaz. Yalnızca patch'li lab sistemlerine karşı, yetkili araştırma/savunma için kullan.

Detection

  • Crash signature: guest USB control transfer'leriyle tetiklenen QEMU SIGSEGV veya heap corruption (glibc "malloc(): corrupted" / data_buf üzerinde ASan heap-buffer-overflow) doğrudan bu sınıfı işaret eder.
  • ASan/Valgrind: QEMU'yu AddressSanitizer ile derlemek, fuzzing veya testing sırasında data_buf üzerindeki OOB read/write'ı hemen işaretler.
  • Fuzzing: SETUP wLength alanını mutate eden USB control-transfer fuzzing'i bunu yeniden üretir; structured device fuzzing harness'leri bu path'i kapsar (bkz. ../hypervisor/snapshot-based-greybox-hypervisor-fuzzing.md).
  • Guest telemetry: aşırı büyük wLength (4096'nın çok ötesinde) bildiren tekrarlayan control transfer'leri anormaldir ve device model'inde gözlemlenebilir.

Mitigation

  • Patch: SETUP uzunluğunu data_buf erişimini geçirmeden önce validate eden Gerd Hoffmann'ın usb: fix setup_len init fix'ini taşıyan QEMU 5.2.0 veya sonrasına güncelle.
  • Reduce surface: gerekmedikçe USB controller/device'ları güvenilmeyen guest'lere bağlama; bug en az bir USB device'ının mevcut olmasına ihtiyaç duyar.
  • Drop host privilege: QEMU'yu asla root olarak çalıştırma — adanmış unprivileged bir uid olarak çalıştır, böylece bir escape root değil, düşük ayrıcalıklı bir context'e düşer.
  • Confinement: seccomp (-sandbox on), sVirt/SELinux ve namespacing'i etkinleştir, böylece bozulmuş bir QEMU process'i sıkıca contained olur ve herhangi bir escape'in blast radius'unu sınırlar.

References