Heap overflow in QEMU PCNET controller, allowing guest->host escape

The QEMU security team has predisclosed the following advisory:

pcnet_transmit 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
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
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.

lack of check (outsize)



4096 is the maximum length per TMD and it is also currently the size of the relay buffer pcnet driver uses for sending the packet data to QEMU for further processing. With packet spanning multiple TMDs it can happen that the overall packet size will be bigger than sizeof(buffer), which results in memory corruption.

Fix this by only allowing to queue maximum sizeof(buffer) bytes.

diff --git a/hw/pcnet.c b/hw/pcnet.c
index bdfd38f..6d32e4c 100644
--- a/hw/pcnet.c
+++ b/hw/pcnet.c
@@ -1241,6 +1241,14 @@ static void pcnet_transmit(PCNetState *s)

         bcnt = 4096 - GET_FIELD(tmd.length, TMDL, BCNT);
+        /* if multi-tmd packet outsizes s->buffer then skip it silently.
+           Note: this is not what real hw does */
+        if (s->xmit_pos + bcnt > sizeof(s->buffer)) {
+           s->xmit_pos = -1;
+           goto txdone;
+        }
         s->phys_mem_read(s->dma_opaque, PHYSADDR(s, tmd.tbadr),
                          s->buffer + s->xmit_pos, bcnt, CSR_BSWP(s));
         s->xmit_pos += bcnt;


A guest which has access to an emulated PCNET network device (e.g. with model=pcnet in their VIF configuration) can exploit this vulnerability to take over the qemu process elevating its privilege to that of the qemu process.

privilege escalation