superuser5 / google-security-research

Automatically exported from code.google.com/p/google-security-research
0 stars 0 forks source link

Heap overflow in QEMU Programmable Interrupt Timer controler #419

Closed GoogleCodeExporter closed 9 years ago

GoogleCodeExporter commented 9 years ago
The programmable interrupt timer (PIT) controller in QEMU does not correctly 
validate the channel number when performing IO writes to the device controller, 
allowing both an information disclosure and heap-overflow within the context of 
the host.

Depending on the layout of the data beyond the heap allocation, this 
vulnerability can set various bytes just beyond the heap allocation to 
non-attacker controlled values (mainly zero), as well as leaking various bytes 
from beyond the heap allocation back to the guest.

== Detail ==

The vulnerable function and relevant structures are given below:

typedef struct PITChannelState {
    int count; /* can be 65536 */
    uint16_t latched_count;
    uint8_t count_latched;
    uint8_t status_latched;
    uint8_t status;
    uint8_t read_state;
    uint8_t write_state;
    uint8_t write_latch;
    uint8_t rw_mode;
    uint8_t mode;
    uint8_t bcd; /* not supported */
    uint8_t gate; /* timer start */
    int64_t count_load_time;
    /* irq handling */
    int64_t next_transition_time;
    QEMUTimer *irq_timer;
    qemu_irq irq;
    uint32_t irq_disabled;
} PITChannelState;

typedef struct PITCommonState {
    ISADevice dev;
    MemoryRegion ioports;
    uint32_t iobase;
    PITChannelState channels[3];
} PITCommonState;

static uint64_t pit_ioport_read(void *opaque, hwaddr addr,
                                unsigned size)
{
    PITCommonState *pit = opaque;
    int ret, count;
    PITChannelState *s;

    addr &= 3;
    s = &pit->channels[addr];
    if (s->status_latched) {
        s->status_latched = 0;
        ret = s->status;
    } else if (s->count_latched) {
        switch(s->count_latched) {
        default:
        case RW_STATE_LSB:
            ret = s->latched_count & 0xff;
            s->count_latched = 0;
            break;
        case RW_STATE_MSB:
            ret = s->latched_count >> 8;
            s->count_latched = 0;
            break;
        case RW_STATE_WORD0:
            ret = s->latched_count & 0xff;
            s->count_latched = RW_STATE_MSB;
            break;
        }
    } else {
        switch(s->read_state) {
        default:
        case RW_STATE_LSB:
            count = pit_get_count(s);
            ret = count & 0xff;
            break;
        case RW_STATE_MSB:
            count = pit_get_count(s);
            ret = (count >> 8) & 0xff;
            break;
        case RW_STATE_WORD0:
            count = pit_get_count(s);
            ret = count & 0xff;
            s->read_state = RW_STATE_WORD1;
            break;
        case RW_STATE_WORD1:
            count = pit_get_count(s);
            ret = (count >> 8) & 0xff;
            s->read_state = RW_STATE_WORD0;
            break;
        }
    }
    return ret;
}

By specifying the value of addr to be IOPORT_PIT_CHANNEL0+3, the value of "addr 
& 3" will be set to 3. This is then used as a array index into s->channels, 
however since C array-indexes are zero-based (i.e. array[3] points to the 
fourth element of an array), and there are only three channels in the 
"PITCommonState.channels" field, this causes the "s" variable to point just 
beyond the bounds of the "PITChannelState" heap allocation.

What happens next is heavilly dependent on the bytes present beyond the heap 
allocation.

Firstly, the "s" variable - invalidly pointing beyond the heap allocation - 
dereferences the value "status_latched". If this value is non-zero, the host 
leaks the value held at "s->status" back to the guest, and triggers a relative 
write beyond bounds by setting a zero byte beyond the heap allocation at 
"s->status_latched".

If the value is zero - or if the vulnerability is triggered a second time - the 
value at "s->count_latched" is inspected. If it is non zero, the function can 
either leak the low, high, or both bytes of "s->latched_count" back to the 
guest, as well as causing "s->count_latched" to be set to zero.

If s->count_latched is also zero - or if the vulnerability is triggered a third 
time - the value at s->read_state is finally read. Depending its value, and the 
value of s->mode, this method can leak the low, high or both bytes of s->count 
back to the guest, and can cause the byte corresponding to s->read_state to be 
invalidly set to zero.

== PoC ==

Triggering this vulnerability from the context of a guest machine (running in 
Ring-0 in the guest VM) is simple:

#define IOPORT_PIT_CHANNEL0 0x40

void kmain()
{
  uint8_t hostleaked;
  size_t i;
  for(i = 0; i < 6; i++)
  {
    // trigger write-beyond-bounds and host leak:
    hostleaked = __inb(IOPORT_PIT_CHANNEL0 + 3);
  }
}

This bug is subject to a 90 day disclosure deadline. If 90 days elapse
without a broadly available patch, then the bug report will automatically
become visible to the public.

Original issue reported on code.google.com by mattt...@google.com on 1 Jun 2015 at 10:01

GoogleCodeExporter commented 9 years ago
[deleted comment]
GoogleCodeExporter commented 9 years ago
[deleted comment]
GoogleCodeExporter commented 9 years ago
[deleted comment]
GoogleCodeExporter commented 9 years ago
Fixed: 
https://github.com/qemu/qemu/commit/d4862a87e31a51de9eb260f25c9e99a75efe3235

Original comment by mattt...@google.com on 27 Aug 2015 at 2:01