|
|
9ae3a8 |
From 4fef3479339001ef3ea529fb0552533fae422240 Mon Sep 17 00:00:00 2001
|
|
|
9ae3a8 |
From: Laszlo Ersek <lersek@redhat.com>
|
|
|
9ae3a8 |
Date: Fri, 5 Feb 2016 14:26:18 +0100
|
|
|
9ae3a8 |
Subject: [PATCH 1/5] e1000: eliminate infinite loops on out-of-bounds transfer
|
|
|
9ae3a8 |
start
|
|
|
9ae3a8 |
|
|
|
9ae3a8 |
RH-Author: Laszlo Ersek <lersek@redhat.com>
|
|
|
9ae3a8 |
Message-id: <1454682378-29144-2-git-send-email-lersek@redhat.com>
|
|
|
9ae3a8 |
Patchwork-id: 69116
|
|
|
9ae3a8 |
O-Subject: [RHEL-7.3 qemu-kvm PATCH 1/1] e1000: eliminate infinite loops on out-of-bounds transfer start
|
|
|
9ae3a8 |
Bugzilla: 1296044
|
|
|
9ae3a8 |
RH-Acked-by: Xiao Wang <jasowang@redhat.com>
|
|
|
9ae3a8 |
RH-Acked-by: P J P <ppandit@redhat.com>
|
|
|
9ae3a8 |
RH-Acked-by: Stefan Hajnoczi <stefanha@redhat.com>
|
|
|
9ae3a8 |
|
|
|
9ae3a8 |
The start_xmit() and e1000_receive_iov() functions implement DMA transfers
|
|
|
9ae3a8 |
iterating over a set of descriptors that the guest's e1000 driver
|
|
|
9ae3a8 |
prepares:
|
|
|
9ae3a8 |
|
|
|
9ae3a8 |
- the TDLEN and RDLEN registers store the total size of the descriptor
|
|
|
9ae3a8 |
area,
|
|
|
9ae3a8 |
|
|
|
9ae3a8 |
- while the TDH and RDH registers store the offset (in whole tx / rx
|
|
|
9ae3a8 |
descriptors) into the area where the transfer is supposed to start.
|
|
|
9ae3a8 |
|
|
|
9ae3a8 |
Each time a descriptor is processed, the TDH and RDH register is bumped
|
|
|
9ae3a8 |
(as appropriate for the transfer direction).
|
|
|
9ae3a8 |
|
|
|
9ae3a8 |
QEMU already contains logic to deal with bogus transfers submitted by the
|
|
|
9ae3a8 |
guest:
|
|
|
9ae3a8 |
|
|
|
9ae3a8 |
- Normally, the transmit case wants to increase TDH from its initial value
|
|
|
9ae3a8 |
to TDT. (TDT is allowed to be numerically smaller than the initial TDH
|
|
|
9ae3a8 |
value; wrapping at or above TDLEN bytes to zero is normal.) The failsafe
|
|
|
9ae3a8 |
that QEMU currently has here is a check against reaching the original
|
|
|
9ae3a8 |
TDH value again -- a complete wraparound, which should never happen.
|
|
|
9ae3a8 |
|
|
|
9ae3a8 |
- In the receive case RDH is increased from its initial value until
|
|
|
9ae3a8 |
"total_size" bytes have been received; preferably in a single step, or
|
|
|
9ae3a8 |
in "s->rxbuf_size" byte steps, if the latter is smaller. However, null
|
|
|
9ae3a8 |
RX descriptors are skipped without receiving data, while RDH is
|
|
|
9ae3a8 |
incremented just the same. QEMU tries to prevent an infinite loop
|
|
|
9ae3a8 |
(processing only null RX descriptors) by detecting whether RDH assumes
|
|
|
9ae3a8 |
its original value during the loop. (Again, wrapping from RDLEN to 0 is
|
|
|
9ae3a8 |
normal.)
|
|
|
9ae3a8 |
|
|
|
9ae3a8 |
What both directions miss is that the guest could program TDLEN and RDLEN
|
|
|
9ae3a8 |
so low, and the initial TDH and RDH so high, that these registers will
|
|
|
9ae3a8 |
immediately be truncated to zero, and then never reassume their initial
|
|
|
9ae3a8 |
values in the loop -- a full wraparound will never occur.
|
|
|
9ae3a8 |
|
|
|
9ae3a8 |
The condition that expresses this is:
|
|
|
9ae3a8 |
|
|
|
9ae3a8 |
xdh_start >= s->mac_reg[XDLEN] / sizeof(desc)
|
|
|
9ae3a8 |
|
|
|
9ae3a8 |
i.e., TDH or RDH start out after the last whole rx or tx descriptor that
|
|
|
9ae3a8 |
fits into the TDLEN or RDLEN sized area.
|
|
|
9ae3a8 |
|
|
|
9ae3a8 |
This condition could be checked before we enter the loops, but
|
|
|
9ae3a8 |
pci_dma_read() / pci_dma_write() knows how to fill in buffers safely for
|
|
|
9ae3a8 |
bogus DMA addresses, so we just extend the existing failsafes with the
|
|
|
9ae3a8 |
above condition.
|
|
|
9ae3a8 |
|
|
|
9ae3a8 |
This is CVE-2016-1981.
|
|
|
9ae3a8 |
|
|
|
9ae3a8 |
Cc: "Michael S. Tsirkin" <mst@redhat.com>
|
|
|
9ae3a8 |
Cc: Petr Matousek <pmatouse@redhat.com>
|
|
|
9ae3a8 |
Cc: Stefano Stabellini <stefano.stabellini@eu.citrix.com>
|
|
|
9ae3a8 |
Cc: Prasad Pandit <ppandit@redhat.com>
|
|
|
9ae3a8 |
Cc: Michael Roth <mdroth@linux.vnet.ibm.com>
|
|
|
9ae3a8 |
Cc: Jason Wang <jasowang@redhat.com>
|
|
|
9ae3a8 |
Cc: qemu-stable@nongnu.org
|
|
|
9ae3a8 |
RHBZ: https://bugzilla.redhat.com/show_bug.cgi?id=1296044
|
|
|
9ae3a8 |
Signed-off-by: Laszlo Ersek <lersek@redhat.com>
|
|
|
9ae3a8 |
Reviewed-by: Jason Wang <jasowang@redhat.com>
|
|
|
9ae3a8 |
Signed-off-by: Jason Wang <jasowang@redhat.com>
|
|
|
9ae3a8 |
(cherry picked from commit dd793a74882477ca38d49e191110c17dfee51dcc)
|
|
|
9ae3a8 |
Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com>
|
|
|
9ae3a8 |
---
|
|
|
9ae3a8 |
hw/net/e1000.c | 6 ++++--
|
|
|
9ae3a8 |
1 file changed, 4 insertions(+), 2 deletions(-)
|
|
|
9ae3a8 |
|
|
|
9ae3a8 |
diff --git a/hw/net/e1000.c b/hw/net/e1000.c
|
|
|
9ae3a8 |
index 87a84a7..2cd38bc 100644
|
|
|
9ae3a8 |
--- a/hw/net/e1000.c
|
|
|
9ae3a8 |
+++ b/hw/net/e1000.c
|
|
|
9ae3a8 |
@@ -697,7 +697,8 @@ start_xmit(E1000State *s)
|
|
|
9ae3a8 |
* bogus values to TDT/TDLEN.
|
|
|
9ae3a8 |
* there's nothing too intelligent we could do about this.
|
|
|
9ae3a8 |
*/
|
|
|
9ae3a8 |
- if (s->mac_reg[TDH] == tdh_start) {
|
|
|
9ae3a8 |
+ if (s->mac_reg[TDH] == tdh_start ||
|
|
|
9ae3a8 |
+ tdh_start >= s->mac_reg[TDLEN] / sizeof(desc)) {
|
|
|
9ae3a8 |
DBGOUT(TXERR, "TDH wraparound @%x, TDT %x, TDLEN %x\n",
|
|
|
9ae3a8 |
tdh_start, s->mac_reg[TDT], s->mac_reg[TDLEN]);
|
|
|
9ae3a8 |
break;
|
|
|
9ae3a8 |
@@ -902,7 +903,8 @@ e1000_receive(NetClientState *nc, const uint8_t *buf, size_t size)
|
|
|
9ae3a8 |
if (++s->mac_reg[RDH] * sizeof(desc) >= s->mac_reg[RDLEN])
|
|
|
9ae3a8 |
s->mac_reg[RDH] = 0;
|
|
|
9ae3a8 |
/* see comment in start_xmit; same here */
|
|
|
9ae3a8 |
- if (s->mac_reg[RDH] == rdh_start) {
|
|
|
9ae3a8 |
+ if (s->mac_reg[RDH] == rdh_start ||
|
|
|
9ae3a8 |
+ rdh_start >= s->mac_reg[RDLEN] / sizeof(desc)) {
|
|
|
9ae3a8 |
DBGOUT(RXERR, "RDH wraparound @%x, RDT %x, RDLEN %x\n",
|
|
|
9ae3a8 |
rdh_start, s->mac_reg[RDT], s->mac_reg[RDLEN]);
|
|
|
9ae3a8 |
set_ics(s, 0, E1000_ICS_RXO);
|
|
|
9ae3a8 |
--
|
|
|
9ae3a8 |
1.8.3.1
|
|
|
9ae3a8 |
|