f7153f0
------------------------------------------------------------------------
f7153f0
*From*: 	Laszlo Ersek
f7153f0
*Subject*: 	[Qemu-devel] [PATCH] e1000: eliminate infinite loops on
f7153f0
out-of-bounds transfer start
f7153f0
*Date*: 	Tue, 19 Jan 2016 14:17:20 +0100
f7153f0
f7153f0
------------------------------------------------------------------------
f7153f0
f7153f0
The start_xmit() and e1000_receive_iov() functions implement DMA transfers
f7153f0
iterating over a set of descriptors that the guest's e1000 driver
f7153f0
prepares:
f7153f0
f7153f0
- the TDLEN and RDLEN registers store the total size of the descriptor
f7153f0
  area,
f7153f0
f7153f0
- while the TDH and RDH registers store the offset (in whole tx / rx
f7153f0
  descriptors) into the area where the transfer is supposed to start.
f7153f0
f7153f0
Each time a descriptor is processed, the TDH and RDH register is bumped
f7153f0
(as appropriate for the transfer direction).
f7153f0
f7153f0
QEMU already contains logic to deal with bogus transfers submitted by the
f7153f0
guest:
f7153f0
f7153f0
- Normally, the transmit case wants to increase TDH from its initial value
f7153f0
  to TDT. (TDT is allowed to be numerically smaller than the initial TDH
f7153f0
  value; wrapping at or above TDLEN bytes to zero is normal.) The failsafe
f7153f0
  that QEMU currently has here is a check against reaching the original
f7153f0
  TDH value again -- a complete wraparound, which should never happen.
f7153f0
f7153f0
- In the receive case RDH is increased from its initial value until
f7153f0
  "total_size" bytes have been received; preferably in a single step, or
f7153f0
  in "s->rxbuf_size" byte steps, if the latter is smaller. However, null
f7153f0
  RX descriptors are skipped without receiving data, while RDH is
f7153f0
  incremented just the same. QEMU tries to prevent an infinite loop
f7153f0
  (processing only null RX descriptors) by detecting whether RDH assumes
f7153f0
  its original value during the loop. (Again, wrapping from RDLEN to 0 is
f7153f0
  normal.)
f7153f0
f7153f0
What both directions miss is that the guest could program TDLEN and RDLEN
f7153f0
so low, and the initial TDH and RDH so high, that these registers will
f7153f0
immediately be truncated to zero, and then never reassume their initial
f7153f0
values in the loop -- a full wraparound will never occur.
f7153f0
f7153f0
The condition that expresses this is:
f7153f0
f7153f0
  xdh_start >= s->mac_reg[XDLEN] / sizeof(desc)
f7153f0
f7153f0
i.e., TDH or RDH start out after the last whole rx or tx descriptor that
f7153f0
fits into the TDLEN or RDLEN sized area.
f7153f0
f7153f0
This condition could be checked before we enter the loops, but
f7153f0
pci_dma_read() / pci_dma_write() knows how to fill in buffers safely for
f7153f0
bogus DMA addresses, so we just extend the existing failsafes with the
f7153f0
above condition.
f7153f0
f7153f0
Cc: "Michael S. Tsirkin" <address@hidden>
f7153f0
Cc: Petr Matousek <address@hidden>
f7153f0
Cc: Stefano Stabellini <address@hidden>
f7153f0
Cc: Prasad Pandit <address@hidden>
f7153f0
Cc: Michael Roth <address@hidden>
f7153f0
Cc: Jason Wang <address@hidden>
f7153f0
RHBZ: https://bugzilla.redhat.com/show_bug.cgi?id=1296044
f7153f0
Signed-off-by: Laszlo Ersek <address@hidden>
f7153f0
Reviewed-by: Jason Wang <address@hidden>
f7153f0
---
f7153f0
f7153f0
Notes:
f7153f0
    Regarding the public posting: we made an honest effort to vet this
f7153f0
    vulnerability, and the impact seems low -- no host side reads/writes,
f7153f0
    "just" a DoS (infinite loop). We decided the patch could be posted
f7153f0
    publicly, for the usual review process. Jason and Prasad checked the
f7153f0
    patch in the internal discussion already, but comments, improvements
f7153f0
    etc. are clearly welcome. The CVE request is underway. Thanks.
f7153f0
f7153f0
 hw/net/e1000.c | 6 ++++--
f7153f0
 1 file changed, 4 insertions(+), 2 deletions(-)
f7153f0
f7153f0
diff --git a/hw/net/e1000.c b/hw/net/e1000.c
f7153f0
index bec06e9..34d0823 100644
f7153f0
--- a/tools/qemu-xen/hw/net/e1000.c
f7153f0
+++ b/tools/qemu-xen/hw/net/e1000.c
f7153f0
@@ -908,7 +908,8 @@ start_xmit(E1000State *s)
f7153f0
          * bogus values to TDT/TDLEN.
f7153f0
          * there's nothing too intelligent we could do about this.
f7153f0
          */
f7153f0
-        if (s->mac_reg[TDH] == tdh_start) {
f7153f0
+        if (s->mac_reg[TDH] == tdh_start ||
f7153f0
+            tdh_start >= s->mac_reg[TDLEN] / sizeof(desc)) {
f7153f0
             DBGOUT(TXERR, "TDH wraparound @%x, TDT %x, TDLEN %x\n",
f7153f0
                    tdh_start, s->mac_reg[TDT], s->mac_reg[TDLEN]);
f7153f0
             break;
f7153f0
@@ -1165,7 +1166,8 @@ e1000_receive_iov(NetClientState *nc, const struct iovec *iov, int iovcnt)
f7153f0
         if (++s->mac_reg[RDH] * sizeof(desc) >= s->mac_reg[RDLEN])
f7153f0
             s->mac_reg[RDH] = 0;
f7153f0
         /* see comment in start_xmit; same here */
f7153f0
-        if (s->mac_reg[RDH] == rdh_start) {
f7153f0
+        if (s->mac_reg[RDH] == rdh_start ||
f7153f0
+            rdh_start >= s->mac_reg[RDLEN] / sizeof(desc)) {
f7153f0
             DBGOUT(RXERR, "RDH wraparound @%x, RDT %x, RDLEN %x\n",
f7153f0
                    rdh_start, s->mac_reg[RDT], s->mac_reg[RDLEN]);
f7153f0
             set_ics(s, 0, E1000_ICS_RXO);
f7153f0
-- 
f7153f0
1.8.3.1