n0o.com - Personal archive of discovered vulns & writeups.

[NO CVE]QEMU: Use-after-free in scsi-bus.c


Reported to QEMU @ 22 April 2020 Fixed @ 7 July 2020 Reply from the vendor: But considering that the issue depends on an administrator hot plugging/unplugging a drive image, it is more of a non-security bug. Not a CVE issue. ====================================== Overview ====================================== The "opaque" object in scsi_dma_restart_bh can be used after free. The operation qemu_bh_delete(s->bh); will use the freed "opaque (s)" object directly. Freed "s (opaque)" can be occupied by other data, so the s->bh can point to arbitrary address and freed by qemu_bh_delete later: void qemu_bh_delete(QEMUBH *bh){ g_free(bh); } I believe this is not related to system version or hardware architecture. Here's the environment I used to test: Host: Ubuntu 16.04 x86_64 Guest: Ubuntu 18.04 x86_64 Qemu: 4.2.0 (I checked the commit between 4.2.0-5.0.0 and I believe 5.0.0 has the same problem) libvirt: 6.0.0 with KVM enabled The root cause of the vulnerability ======================================= 1. Whenever there's a SCSI device add/plugged into the guest, the callback scsi_dma_restart_cb will be added. 2. When there's a state change in guest, callback scsi_dma_restart_cb will be called and scheduled bottom half: scsi_dma_restart_bh with opaque=s if the guest is not in the shutdown process. 3. In the main IO thread, there's a loop of glib_pollfds_poll, when fd is ready, AIO operations will be called and then scsi_dma_restart_bh is called. (The 'USE' part) 4. Meanwhile, the attacker could write something to IOPORT to unplug the device, and in another thread, will trigger acpi_pcihp_eject_slot, then device_unparent and will free the related memory to the device. (The 'FREE' part) 5. Step (3) (4) could cause a race condition, if (4) is called before (3), there's a UAF. Related code: hw/scsi/scsi-bus.c:scsi_dma_restart_cb, scsi_dma_restart_bh hw/acpi/pcihp.c:acpi_pcihp_eject_slot Different Ways To Trigger the UAF ======================================= a. If the guest system could be suspended (paused), here's a simple way to test: 1. Know the slot number of the disk X being attached. (By finding the next available slot number from lspci) 2. Do not attach disk X now, start a program in the guest. That program will *infinitely* write (2 << slot) to the IOPORT of the bus, try to release disk X. 3. Pause the guest and attach disk X. 4. Resume the guest. Now bh callback and the IOPORT write should run at the same time, and cause UAF by chance. If it is not succeeded, repeat steps 3-4. b. If your guest system cannot be paused, you can try (I haven't tested this yet, I think there could be some chance to do this but I'm not very sure about that. Meant for reference only.): 1. Know the slot of the disk X being attached. 2. Build a custom system kernel, IOPORT writes in the last time before the machine is rebooted. 3. Attach disk X. 4. Reboot the guest machine. If it is not succeeded, repeat steps 3-4.