A QEMU guest's disk is served by an emulated storage controller: legacy IDE, the AHCI SATA controller, or the paravirtual virtio-blk and virtio-scsi devices. Each takes guest-programmed command structures and DMA descriptors and reads them to perform I/O. AHCI uses a command list and physical region descriptor (PRD) tables in guest memory; SCSI paths parse command descriptor blocks and transfer lengths; virtio-blk/scsi use the virtqueue descriptor model. Parsing those guest-controlled structures in QEMU is the escape surface, with AHCI command-list/PRD handling and SCSI request parsing the recurring loci.
The surface#
// AHCI: the guest sets the command list base; each command header points at a
// command table with a PRD (physical region descriptor) table. QEMU walks the PRDs,
// each giving a guest address and byte count, to DMA data. Primitives:
// - a PRD count/length QEMU trusts for a transfer -> OOB read/write
// - a command-list/command-table offset the model uses without bounding
// SCSI (scsi-disk / virtio-scsi): a CDB and transfer length; a length used for a
// buffer operation beyond the allocation, or an unexpected command, -> OOB
// virtio-blk/scsi: request headers over the virtqueue (see virtio devices)
lspci -nn | grep -iE 'sata|ahci|scsi|virtio' # which storage controller
Exploitation notes#
- AHCI PRD handling is a classic target: the guest fully controls the PRD table (addresses and counts) that QEMU uses for DMA, so a trusted count or length is a direct out-of-bounds transfer primitive.
- SCSI request parsing (shared by scsi-disk and virtio-scsi backends) handles guest-chosen command blocks and transfer lengths, another place length assumptions break.
- virtio-blk/scsi route through the shared virtqueue layer; the storage-specific parsing is the request header, see virtio devices.
- The guest programs these structures directly from its storage driver; primitives land in QEMU, QEMU-version-specific.