diff options
author | Hans de Goede <hdegoede@redhat.com> | 2012-08-29 10:37:37 +0200 |
---|---|---|
committer | Gerd Hoffmann <kraxel@redhat.com> | 2012-09-11 07:42:59 +0200 |
commit | dafe31fc2a8653b535d58f8c7b250c0827b14420 (patch) | |
tree | d0ae767d639da16be8e7de2c09c7f7d480b479a1 /hw | |
parent | 66f092d25697e11847b61d761c38ddebedaed8d1 (diff) |
ehci: Validate qh is not changed unexpectedly by the guest
-combine the qh check with the check for devaddr changes
-also ensure that p gets set to NULL when the queue gets cancelled on
devaddr change, which was not done properly before this patch
Signed-off-by: Hans de Goede <hdegoede@redhat.com>
Diffstat (limited to 'hw')
-rw-r--r-- | hw/usb/hcd-ehci.c | 39 |
1 files changed, 28 insertions, 11 deletions
diff --git a/hw/usb/hcd-ehci.c b/hw/usb/hcd-ehci.c index e7c36f4155..35eb441f1d 100644 --- a/hw/usb/hcd-ehci.c +++ b/hw/usb/hcd-ehci.c @@ -780,6 +780,14 @@ static void ehci_cancel_queue(EHCIQueue *q) } while ((p = QTAILQ_FIRST(&q->packets)) != NULL); } +static void ehci_reset_queue(EHCIQueue *q) +{ + trace_usb_ehci_queue_action(q, "reset"); + ehci_cancel_queue(q); + q->dev = NULL; + q->qtdaddr = 0; +} + static void ehci_free_queue(EHCIQueue *q) { EHCIQueueHead *head = q->async ? &q->ehci->aqueues : &q->ehci->pqueues; @@ -1755,8 +1763,9 @@ out: static EHCIQueue *ehci_state_fetchqh(EHCIState *ehci, int async) { EHCIPacket *p; - uint32_t entry, devaddr; + uint32_t entry, devaddr, endp; EHCIQueue *q; + EHCIqh qh; entry = ehci_get_fetch_addr(ehci, async); q = ehci_find_queue_by_qh(ehci, entry, async); @@ -1774,17 +1783,25 @@ static EHCIQueue *ehci_state_fetchqh(EHCIState *ehci, int async) } get_dwords(ehci, NLPTR_GET(q->qhaddr), - (uint32_t *) &q->qh, sizeof(EHCIqh) >> 2); - ehci_trace_qh(q, NLPTR_GET(q->qhaddr), &q->qh); + (uint32_t *) &qh, sizeof(EHCIqh) >> 2); + ehci_trace_qh(q, NLPTR_GET(q->qhaddr), &qh); + + /* + * The overlay area of the qh should never be changed by the guest, + * except when idle, in which case the reset is a nop. + */ + devaddr = get_field(qh.epchar, QH_EPCHAR_DEVADDR); + endp = get_field(qh.epchar, QH_EPCHAR_EP); + if ((devaddr != get_field(q->qh.epchar, QH_EPCHAR_DEVADDR)) || + (endp != get_field(q->qh.epchar, QH_EPCHAR_EP)) || + (memcmp(&qh.current_qtd, &q->qh.current_qtd, + 9 * sizeof(uint32_t)) != 0) || + (q->dev != NULL && q->dev->addr != devaddr)) { + ehci_reset_queue(q); + p = NULL; + } + q->qh = qh; - devaddr = get_field(q->qh.epchar, QH_EPCHAR_DEVADDR); - if (q->dev != NULL && q->dev->addr != devaddr) { - if (!QTAILQ_EMPTY(&q->packets)) { - /* should not happen (guest bug) */ - ehci_cancel_queue(q); - } - q->dev = NULL; - } if (q->dev == NULL) { q->dev = ehci_find_device(q->ehci, devaddr); } |