freenode
AnalysisDatabases & Infrastructure

QEMU device models and migration streams keep producing host CVEs

A dense run of USB, display, NIC, and UEFI fixes shows the project still treating guest and migration input as untrusted, while the underlying C surface remains large enough that clouds must keep asking how much trust that buys them.

Another week of QEMU development has landed the familiar pattern: guest- or migration-triggerable out-of-bounds writes, use-after-frees, and overflows in device models that sit on the host side of the isolation boundary. The bugs are not exotic new subsystems. They are USB redirection and xHCI, QXL and virtio-gpu, e1000e and igb, and the UEFI variable service. What ties the patches together is less any single root cause than a recurring question for multi-tenant clouds: can complex C device models and live-migration streams be hardened fast enough, or does each cluster of CVEs simply redraw where operators should place the trust line between guest and host?

The migration angle is the sharpest illustration. Laurent Vivier posted fixes for e1000e and igb after a crafted migration stream was shown to set rx_desc_len to an invalid value. That field is migrated as a raw uint8_t, yet it is a derived quantity. With a bad value (for example 64), e1000e_write_packet_to_guest() copies that many bytes into a 32-byte stack union. Vivier’s remedy is deliberately small: recalculate rx_desc_len in post_load from register state and ignore the untrusted stream value. “A crafted migration stream can set rx_desc_len to an invalid value, causing a stack buffer overflow,” he wrote, and the series resolves the corresponding GitLab issue while targeting stable.

Review did not stop at the one-line fix. Akihiko Odaki noted that the e1000e derivation is not solely from core.mac[RCTL] but also core.mac[RFCTL], and that related derived state (rxbuf_min_shift, rxbuf_sizes[], rx_desc_buf_size) is normally recomputed together in e1000e_set_rx_control(). “I suggest creating a function to replicate this sequence for e1000e_core_post_load() instead of only calling e1000e_calc_rxdesclen(),” he wrote. For igb he observed that igb_calc_rxdesclen() currently rests on a stub that always returns false, so the recalculation is fine but thinner than the comment implied. The exchange is the hardening philosophy in miniature: do not trust migrated derived fields; recompute them; and preferably recompute the whole dependent set so the next overflow is not waiting one register away.

Gerd Hoffmann’s UEFI variable-service series makes the same bet more systematically. Several CVEs in the OVMF-facing var-service path (including CVE-2026-61404, CVE-2026-58581, CVE-2026-58582, CVE-2026-61406, and CVE-2026-16288) are addressed by sanity checks on policy and signature-list sizes, accounting policy entries against storage limits, rejecting writes to SetupMode, removing a debug path that “was never meant to be present in production builds,” and adding post_load checks that fail migration on inconsistent state. Buffer size is capped; used storage is compared to max storage; invalid data aborts the load rather than proceeding into host memory corruption. Hoffmann’s cover letter is blunt about intent: add sanity checks to state loaded from the live migration data stream, and fail migration if invalid data or inconsistencies are found. Migration is treated as an adversarial channel, not a privileged continuation of a trusted peer.

Guest-triggerable device paths produced a parallel pile. Thomas Huth’s USB pull merged fixes for a use-after-free in usbredir buffered bulk when a queue overflow frees a shared buffer while earlier fragments still point into it (CVE-2026-15705), infinite loops and SIGFPE when a malicious usbredir peer resets max_packet_size to zero after bulk receive has started (CVE-2026-63319), a guest-triggerable assert in xhci_find_stream() turned into a logged error path, and an OOB heap access in sysbus xHCI interrupter raise when machines expose fewer interrupters than the guest programs (CVE-2026-16043). Marc-André Lureau’s description of the zero max-packet case is concrete: the splitting loop increments by zero, and completion paths divide or modulo by zero. The fix stops bulk receiving on a zero size, adds post-load checking, and asserts the invariant afterward.

Display code shows the same class of gap between “validated somewhere” and “safe on the host.” Lureau’s QXL primary-surface patch (CVE-2026-16271) notes that existing checks ensure abs(stride) * height fits in video memory and that stride is aligned, but never that stride is wide enough for one row at the declared width and format. A guest can advertise a tiny stride; the spice server’s red_validate_surface() rejects it, “but the return is void and QEMU unconditionally proceeds to set up the local rendering state.” The next VNC or SDL refresh then reads a full width of pixels from a row backed by only stride bytes: a host-side out-of-bounds read. Related virtio-gpu work rejects strides past INT_MAX, validates stride against width on scanout, caps 3D command buffer allocation, checks pixman image creation, and handles migration iov allocation failure. VNC lossy refresh had its own OOB write (CVE-2026-61475) when a display height not divisible by the stat tile size let a 64-row loop walk past the dirty bitmap into adjacent VncState fields.

Stable maintainers are absorbing the blast radius rather than debating architecture in the pull threads. Michael Tokarev, looking at the USB set, wrote that “it smells like everything in there needs to be picked up for the current stable qemu series, not only the patches which were Cc'ed qemu-stable@ explicitly,” and proceeded to queue the lot. For the UI and virtio-gpu batch he was more selective, holding some patches for follow-up fixes and skipping others, which is its own signal: the volume of security-tinged device fixes is high enough that stable triage is now part of the weekly rhythm.

Two readings of this cluster sit side by side without a clean synthesis. On one reading, the process is working as designed. Derived migration state is being recomputed. Post-load hooks are growing teeth. Guest-triggerable asserts are becoming error returns. Void validation results from helper libraries are being mirrored with real checks in QEMU. CVE assignment, GitLab work items, qemu-stable Ccs, and regression tests (including an xHCI unplug finalize test) show a mature incident response loop. Philippe Mathieu-Daudé’s reviews and Peter Maydell’s help on xHCI interrupter semantics and a VNC rebase mistake fit that incremental craft: fix the concrete hole, tighten the invariant, ship stable.

On the other reading, the surface area is the story. USB controllers, redirect peers, QXL primary surfaces, virtio-gpu command submission, e1000e receive descriptor machinery, and UEFI variable policy are all long-lived C models with intricate internal caches. Live migration serializes enough of that internal state that the stream becomes a second guest, one that bypasses the usual device register path and lands directly in post_load. Every “recalculate this field” or “fail if used_storage > max_storage” patch is a local win and also an admission that the default posture was too trusting. For operators running multi-tenant clouds, the practical implication is not that QEMU is uniquely careless, but that the guest/host boundary still includes a large, evolving C TCB whose migration path must be treated as hostile even when the wire format is QEMU’s own.

Nothing in the current threads proposes replacing device models wholesale, moving more of them into rust-vmm style crates, or shrinking the migratable surface by policy. The work on the table is the next round of clamps, recomputations, and post_load failures. What remains unresolved is whether that pace matches the attacker economics of shared hosts, and whether “we fixed the derived field” is enough of an answer when the next overflow is one forgotten cache line away in the same model.