VENOM - Virtualized Environment Neglected Operations Manipulation

2015.05.15
Credit: Jason Geffner
Risk: High
Local: Yes
Remote: No
CWE: CWE-119


CVSS Base Score: 7.7/10
Impact Subscore: 10/10
Exploitability Subscore: 5.1/10
Exploit range: Adjacent network
Attack complexity: Low
Authentication: Single time
Confidentiality impact: Complete
Integrity impact: Complete
Availability impact: Complete

VENOM (CVE-2015-3456) Virtualized Environment Neglected Operations Manipulation Discovered by Jason Geffner jason@crowdstrike.com ABSTRACT -------- This email describes a vulnerability in QEMU's virtual Floppy Disk Controller (FDC), exploitation of which may allow malicious code inside a virtual machine guest to perform arbitrary code execution on the host machine (a "VM escape"). SCOPE ----- QEMU's virtualized hardware is used in numerous virtualization platforms, notably Xen, KVM, VirtualBox, and the native QEMU client. VULNERABILITY ------------- All code snippets in this section are from http://git.qemu.org/?p=qemu.git;f=hw/block/fdc.c;hb=24a5c62cfe3cbe3fb4722f79661b9900a2579316, which was the last commit before the VENOM vulnerability was patched on 2015-05-13. QEMU's FDC uses a 512-byte first-in first-out (FIFO) buffer for data storage: ________________________________________________________________________________ struct FDCtrl { ... /* Command FIFO */ uint8_t *fifo; int32_t fifo_size; uint32_t data_pos; uint32_t data_len; ... }; ... fdctrl->fifo = qemu_memalign(512, FD_SECTOR_LEN); fdctrl->fifo_size = 512; ________________________________________________________________________________ The FDC's data_pos and data_len fields above are initialized to 0 upon FDC reset. Code in the VM guest can write to the FIFO buffer by sending data to the FDC via its FD_REG_FIFO I/O port. Writes are handled by the function below, with each byte sent to the I/O port getting passed to this function as the value parameter: ________________________________________________________________________________ static void fdctrl_write_data(FDCtrl *fdctrl, uint32_t value) { FDrive *cur_drv; int pos; ... if (fdctrl->data_pos == 0) { /* Command */ pos = command_to_handler[value & 0xff]; FLOPPY_DPRINTF("%s command\n", handlers[pos].name); fdctrl->data_len = handlers[pos].parameters + 1; fdctrl->msr |= FD_MSR_CMDBUSY; } FLOPPY_DPRINTF("%s: %02x\n", __func__, value); fdctrl->fifo[fdctrl->data_pos++] = value; if (fdctrl->data_pos == fdctrl->data_len) { /* We now have all parameters * and will be able to treat the command */ if (fdctrl->data_state & FD_STATE_FORMAT) { fdctrl_format_sector(fdctrl); return; } pos = command_to_handler[fdctrl->fifo[0] & 0xff]; FLOPPY_DPRINTF("treat %s command\n", handlers[pos].name); (*handlers[pos].handler)(fdctrl, handlers[pos].direction); } } ________________________________________________________________________________ The function above uses the first received I/O byte as a command ID, with each ID mapping to a handler: ________________________________________________________________________________ static const struct { uint8_t value; uint8_t mask; const char* name; int parameters; void (*handler)(FDCtrl *fdctrl, int direction); int direction; } handlers[] = { { FD_CMD_READ, 0x1f, "READ", 8, fdctrl_start_transfer, FD_DIR_READ }, { FD_CMD_WRITE, 0x3f, "WRITE", 8, fdctrl_start_transfer, FD_DIR_WRITE }, { FD_CMD_SEEK, 0xff, "SEEK", 2, fdctrl_handle_seek }, ... }; ________________________________________________________________________________ Each handler has an associated parameter count (stored in the parameters variable) and the I/O bytes written to the FIFO buffer after the command ID byte are considered parameters for the command handler. When fdctrl_write_data() determines that all parameters have been supplied (by comparing the incrementing data_pos with data_len), the command handler's function is called to operate on the data in the FIFO buffer. The FDC supports 32 different FIFO-based commands, including a default handler for unrecognized command ID values. The code path for each command handler function resets the FDC's data_pos to 0 at the end of its processing, ensuring that the FIFO buffer can't be overflowed. See below for an example, where the handler function fdctrl_handle_partid() calls fdctrl_set_fifo() which resets data_pos to 0: ________________________________________________________________________________ { FD_CMD_PART_ID, 0xff, "PART ID", 0, fdctrl_handle_partid }, ________________________________________________________________________________ ________________________________________________________________________________ static void fdctrl_handle_partid(FDCtrl *fdctrl, int direction) { fdctrl->fifo[0] = 0x41; /* Stepping 1 */ fdctrl_set_fifo(fdctrl, 1); } ________________________________________________________________________________ ________________________________________________________________________________ static void fdctrl_set_fifo(FDCtrl *fdctrl, int fifo_len) { fdctrl->data_dir = FD_DIR_READ; fdctrl->data_len = fifo_len; fdctrl->data_pos = 0; fdctrl->msr |= FD_MSR_CMDBUSY | FD_MSR_RQM | FD_MSR_DIO; } ________________________________________________________________________________ For 30 of the command handler functions, this data_pos reset happens immediately at the completion of the command processing, similarly to the example above. However, for two of the command handler functions, the data_pos reset is delayed or can be circumvented. The code below shows the handler for the "READ ID" command: ________________________________________________________________________________ { FD_CMD_READ_ID, 0xbf, "READ ID", 1, fdctrl_handle_readid }, ________________________________________________________________________________ ________________________________________________________________________________ static void fdctrl_handle_readid(FDCtrl *fdctrl, int direction) { FDrive *cur_drv = get_cur_drv(fdctrl); cur_drv->head = (fdctrl->fifo[1] >> 2) & 1; timer_mod(fdctrl->result_timer, qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + (get_ticks_per_sec() / 50)); } ________________________________________________________________________________ The fdctrl_handle_readid() function above sets a 20 ms timer (result_timer is initialized to point to the function fdctrl_result_timer() during FDC initialization). After 20 ms, fdctrl_result_timer() gets executed, which calls fdctrl_stop_transfer(), which calls fdctrl_set_fifo(), which resets data_pos to 0. During that 20 ms time window though, code in the VM guest can continue writing to the FIFO buffer, and since fdctrl_write_data() will continue to increment data_pos for each I/O byte received, code in the VM guest can overflow the FIFO buffer with arbitrary data. The code below shows the handler for the "DRIVE SPECIFICATION COMMAND" command: ________________________________________________________________________________ { FD_CMD_DRIVE_SPECIFICATION_COMMAND, 0xff, "DRIVE SPECIFICATION COMMAND", 5, fdctrl_handle_drive_specification_command }, ________________________________________________________________________________ ________________________________________________________________________________ static void fdctrl_handle_drive_specification_command(FDCtrl *fdctrl, int direction) { FDrive *cur_drv = get_cur_drv(fdctrl); if (fdctrl->fifo[fdctrl->data_pos - 1] & 0x80) { /* Command parameters done */ if (fdctrl->fifo[fdctrl->data_pos - 1] & 0x40) { fdctrl->fifo[0] = fdctrl->fifo[1]; fdctrl->fifo[2] = 0; fdctrl->fifo[3] = 0; fdctrl_set_fifo(fdctrl, 4); } else { fdctrl_reset_fifo(fdctrl); } } else if (fdctrl->data_len > 7) { /* ERROR */ fdctrl->fifo[0] = 0x80 | (cur_drv->head << 2) | GET_CUR_DRV(fdctrl); fdctrl_set_fifo(fdctrl, 1); } } ________________________________________________________________________________ The fdctrl_handle_drive_specification_command() function above is called after the FDC receives the FD_CMD_DRIVE_SPECIFICATION_COMMAND command and its 5 parameter bytes. The if-condition in the handler will evaluate to false if the fifth parameter byte doesn't have its most-significant-bit (MSB) set. The else-if-condition will always evaluate to false since fdctrl->data_len will always be set to 6 by the fdctrl_write_data() function for the FD_CMD_DRIVE_SPECIFICATION_COMMAND command. Thus, after sending the FD_CMD_DRIVE_SPECIFICATION_COMMAND command and its 5 parameter bytes (with the fifth parameter byte's MSB set to 0), code in the VM guest can continue writing to the FIFO buffer, and since fdctrl_write_data() will continue to increment data_pos for each I/O byte received, code in the VM guest can overflow the FIFO buffer with arbitrary data.

References:

http://git.qemu.org/?p=qemu.git;f=hw/block/fdc.c;hb=24a5c62cfe3cbe3fb4722f79661b9900a2579316


Vote for this issue:
50%
50%


 

Thanks for you vote!


 

Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.


(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2024, cxsecurity.com

 

Back to Top