diff mbox series

[PULL,12/24] hcd-ohci: Drop ohci_service_iso_td() if ed->head & OHCI_DPTR_MASK is zero

Message ID 20220927081912.180983-13-kraxel@redhat.com (mailing list archive)
State New, archived
Headers show
Series [PULL,01/24] ui/console: Get tab completion working again in the SDL monitor vc | expand

Commit Message

Gerd Hoffmann Sept. 27, 2022, 8:19 a.m. UTC
From: Qiang Liu <cyruscyliu@gmail.com>

An abort happens in ohci_frame_boundary() when ohci->done is 0 [1].

``` c
static void ohci_frame_boundary(void *opaque)
{
    // ...
    if (ohci->done_count == 0 && !(ohci->intr_status & OHCI_INTR_WD)) {
        if (!ohci->done)
            abort(); <----------------------------------------- [1]
```

This was reported in https://bugs.launchpad.net/qemu/+bug/1911216/,
https://lists.gnu.org/archive/html/qemu-devel/2021-06/msg03613.html, and
https://gitlab.com/qemu-project/qemu/-/issues/545. I can still reproduce it with
the latest QEMU.

This happends due to crafted ED with putting ISO_TD at physical address 0.

Suppose ed->head & OHCI_DPTR_MASK is 0 [2], and we memset 0 to the phyiscal
memory from 0 to sizeof(ohci_iso_td). Then, starting_frame [3] and frame_count
[4] are both 0. As we can control the value of ohci->frame_number (0 to 0x1f,
suppose 1), we then control the value of relative_frame_number to be 1 [6]. The
control flow goes to [7] where ohci->done is 0. Have returned from
ohci_service_iso_td(), ohci_frame_boundary() will abort() [1].

``` c
static int ohci_service_iso_td(OHCIState *ohci, struct ohci_ed *ed)
{
    // ...
    addr = ed->head & OHCI_DPTR_MASK; // <--------------------- [2]

    if (ohci_read_iso_td(ohci, addr, &iso_td)) {   // <-------- [3]
        // ...

    starting_frame = OHCI_BM(iso_td.flags, TD_SF); // <-------- [4]
    frame_count = OHCI_BM(iso_td.flags, TD_FC);    // <-------- [5]
    relative_frame_number = USUB(ohci->frame_number, starting_frame);
                                                   // <-------- [6]
    if (relative_frame_number < 0) {
        return 1;
    } else if (relative_frame_number > frame_count) {
        // ...
        ohci->done = addr;                         // <-------- [7]
        // ...
    }
```

As only (afaik) a guest root user can manipulate ED, TD and the physical memory,
this assertion failure is not a security bug.

The idea to fix this issue is to drop ohci_service_iso_td() if ed->head &
OHCI_DPTR_MASK is 0, which is similar to the drop operation for
ohci_service_ed_list() when head is 0. Probably, a similar issue is in
ohci_service_td(). I drop ohci_service_td() if ed->head & OHCI_DPTR_MASK is 0.

Fixes: 7bfe577702 ("OHCI USB isochronous transfers support (Arnon Gilboa)")
Reported-by: Gaoning Pan <pgn@zju.edu.cn>
Reported-by: Alexander Bulekov <alxndr@bu.edu>
Reported-by: Qiang Liu <cyruscyliu@gmail.com>
Resolves: https://gitlab.com/qemu-project/qemu/-/issues/545
Buglink: https://lists.gnu.org/archive/html/qemu-devel/2021-06/msg03613.html
Buglink: https://bugs.launchpad.net/qemu/+bug/1911216
Signed-off-by: Qiang Liu <cyruscyliu@gmail.com>
Message-Id: <20220826051557.119570-1-cyruscyliu@gmail.com>
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
---
 hw/usb/hcd-ohci.c | 10 ++++++++++
 1 file changed, 10 insertions(+)
diff mbox series

Patch

diff --git a/hw/usb/hcd-ohci.c b/hw/usb/hcd-ohci.c
index 895b29fb8657..72bdde92617c 100644
--- a/hw/usb/hcd-ohci.c
+++ b/hw/usb/hcd-ohci.c
@@ -571,6 +571,11 @@  static int ohci_service_iso_td(OHCIState *ohci, struct ohci_ed *ed)
 
     addr = ed->head & OHCI_DPTR_MASK;
 
+    if (addr == 0) {
+        ohci_die(ohci);
+        return 1;
+    }
+
     if (ohci_read_iso_td(ohci, addr, &iso_td)) {
         trace_usb_ohci_iso_td_read_failed(addr);
         ohci_die(ohci);
@@ -858,6 +863,11 @@  static int ohci_service_td(OHCIState *ohci, struct ohci_ed *ed)
     int completion;
 
     addr = ed->head & OHCI_DPTR_MASK;
+    if (addr == 0) {
+        ohci_die(ohci);
+        return 1;
+    }
+
     /* See if this TD has already been submitted to the device.  */
     completion = (addr == ohci->async_td);
     if (completion && !ohci->async_complete) {