The datasheet doesn't explicitly say that TXFR_LEN has to be word
aligned but the fact there is a DMA_D_WIDTH flag to select between 32
bit and 128 bit strongly implies that is how it works. The downstream
rpi kernel also goes to efforts to not write sub-4 byte lengths so
lets:

  - fail when mis-programmed and report GUEST_ERROR
  - catch setting D_WIDTH for 128 bit and report UNIMP
  - add comments that the DEBUG register isn't a straight write

This includes the test case from the reported bug which is of unknown
provenience but isn't particularly novel.

Resolves: https://gitlab.com/qemu-project/qemu/-/issues/3201
Signed-off-by: Alex Bennée <[email protected]>
---
 hw/dma/bcm2835_dma.c           | 19 +++++++++++++++++++
 tests/qtest/bcm2835-dma-test.c | 22 ++++++++++++++++++++++
 2 files changed, 41 insertions(+)

diff --git a/hw/dma/bcm2835_dma.c b/hw/dma/bcm2835_dma.c
index a2771ddcb52..e03247796cf 100644
--- a/hw/dma/bcm2835_dma.c
+++ b/hw/dma/bcm2835_dma.c
@@ -86,6 +86,19 @@ static void bcm2835_dma_update(BCM2835DMAState *s, unsigned 
c)
         }
         xlen_td = xlen;
 
+        if (ch->ti & BCM2708_DMA_D_WIDTH) {
+            qemu_log_mask(LOG_UNIMP, "%s: 128bit transfers not yet supported", 
__func__);
+            ch->cs |= BCM2708_DMA_ERR;
+            break;
+        }
+
+        /* datasheet implies 32bit or 128bit transfers only */
+        if (xlen & 0x3) {
+            qemu_log_mask(LOG_GUEST_ERROR, "%s: bad transfer size\n", 
__func__);
+            ch->cs |= BCM2708_DMA_ERR;
+            break;
+        }
+
         while (ylen != 0) {
             /* Normal transfer mode */
             while (xlen != 0) {
@@ -229,6 +242,12 @@ static void bcm2835_dma_write(BCM2835DMAState *s, hwaddr 
offset,
         ch->conblk_ad = value;
         break;
     case BCM2708_DMA_DEBUG:
+        /* this needs masking
+         * 31:29 - WAZRI
+         * 28:04 - RO
+         *    03 - WAZRI
+         * 00:02 - W1CLR
+         */
         ch->debug = value;
         break;
     default:
diff --git a/tests/qtest/bcm2835-dma-test.c b/tests/qtest/bcm2835-dma-test.c
index 18901b76d21..4b100480f25 100644
--- a/tests/qtest/bcm2835-dma-test.c
+++ b/tests/qtest/bcm2835-dma-test.c
@@ -105,12 +105,34 @@ static void bcm2835_dma_test_interrupts(void)
     bcm2835_dma_test_interrupt(14, 11);
 }
 
+static void test_cve_underflow_txfr_len_1(void)
+{
+    uint64_t dma_base = RASPI3_DMA_BASE; // 0x3f007000
+    uint32_t cb_addr = 0x1000;
+    uint32_t src_addr = 0x2000;
+    uint32_t dst_addr = 0x3000;
+    /* Prepare DMA Control Block with VULNERABLE configuration */
+    writel(cb_addr + 0, BCM2708_DMA_S_INC | BCM2708_DMA_D_INC); /* TI */
+    writel(cb_addr + 4, src_addr); /* source address */
+    writel(cb_addr + 8, dst_addr); /* destination address */
+    writel(cb_addr + 12, 1); /* ⚠️ txfr_len = 1 (TRIGGER!) */
+    writel(cb_addr + 16, 0); /* stride */
+    writel(cb_addr + 20, 0); /* next CB = NULL */
+    /* Set control block address */
+    writel(dma_base + BCM2708_DMA_ADDR, cb_addr);
+    /* Trigger DMA - this will cause the vulnerability */
+    writel(dma_base + BCM2708_DMA_CS, BCM2708_DMA_ACTIVE);
+    /* Without the fix, QEMU process will hang at 100% CPU */
+}
+
 int main(int argc, char **argv)
 {
     int ret;
     g_test_init(&argc, &argv, NULL);
     qtest_add_func("/bcm2835/dma/test_interrupts",
                    bcm2835_dma_test_interrupts);
+    qtest_add_func("/bcm2835/dma/test_underflow_txfr",
+                   test_cve_underflow_txfr_len_1);
     qtest_start("-machine raspi3b");
     ret = g_test_run();
     qtest_end();
-- 
2.47.3


Reply via email to