From: Bin Guo <[email protected]>

The FIQ_CONTROL register accepts a 7-bit source selector, but only
sources 0..71 (64 GPU + 8 ARM IRQs) exist.  Values 96..127 cause
bcm2835_ic_update() to call extract32(arm_irq_level, start, 1) with
start >= 32, which trips the assertion in bitops.h and aborts QEMU.

Reject writes that select a non-existent source and log a guest error,
so that a malicious or buggy guest cannot kill the emulator.

Add a qtest that verifies both valid and out-of-range FIQ source
selections on raspi3b.

Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/4368
Signed-off-by: Bin Guo <[email protected]>
Reviewed-by: Philippe Mathieu-Daudé <[email protected]>
Message-id: [email protected]
Signed-off-by: Peter Maydell <[email protected]>
(cherry picked from commit 2a8b740e7acf4bcf54f0f186d9184d764cd06f01)
Signed-off-by: Michael Tokarev <[email protected]>

diff --git a/hw/intc/bcm2835_ic.c b/hw/intc/bcm2835_ic.c
index 4a42fcf60dd..7fb193f9264 100644
--- a/hw/intc/bcm2835_ic.c
+++ b/hw/intc/bcm2835_ic.c
@@ -139,10 +139,19 @@ static void bcm2835_ic_write(void *opaque, hwaddr offset, 
uint64_t val,
     BCM2835ICState *s = opaque;
 
     switch (offset) {
-    case FIQ_CONTROL:
-        s->fiq_select = extract32(val, 0, 7);
+    case FIQ_CONTROL: {
+        unsigned fiq_select = extract32(val, 0, 7);
+
+        if (fiq_select >= GPU_IRQS + ARM_IRQS) {
+            qemu_log_mask(LOG_GUEST_ERROR,
+                          "%s: FIQ select %u out of range\n",
+                          __func__, fiq_select);
+            return;
+        }
+        s->fiq_select = fiq_select;
         s->fiq_enable = extract32(val, 7, 1);
         break;
+    }
     case IRQ_ENABLE_1:
         s->gpu_irq_enable |= val;
         break;
diff --git a/tests/qtest/bcm2835-ic-test.c b/tests/qtest/bcm2835-ic-test.c
new file mode 100644
index 00000000000..1e171ee0924
--- /dev/null
+++ b/tests/qtest/bcm2835-ic-test.c
@@ -0,0 +1,66 @@
+/*
+ * QTest testcase for the BCM2835 Interrupt Controller
+ *
+ * Copyright (c) 2026 Bin Guo <[email protected]>
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+#include "qemu/osdep.h"
+#include "libqtest-single.h"
+
+#define IC_BASE     0x3f00b200
+#define FIQ_CONTROL (IC_BASE + 0x0c)
+
+static void test_fiq_select_out_of_range(void)
+{
+    uint32_t val;
+
+    /*
+     * Only FIQ sources 0..71 exist.  Source 96 used to trigger an assertion
+     * in bcm2835_ic_update() because extract32(arm_irq_level, 32, 1) was
+     * called with start >= 32.  Make sure the write is rejected and QEMU
+     * keeps running.
+     */
+    writel(FIQ_CONTROL, 0xe0); /* fiq_select = 96, fiq_enable = 1 */
+    val = readl(FIQ_CONTROL);
+    g_assert_cmpint(val, ==, 0);
+
+    /* The first source past the ARM IRQ range should also be rejected. */
+    writel(FIQ_CONTROL, 0xc8); /* fiq_select = 72, fiq_enable = 1 */
+    val = readl(FIQ_CONTROL);
+    g_assert_cmpint(val, ==, 0);
+}
+
+static void test_fiq_select_valid(void)
+{
+    uint32_t val;
+
+    /* Select the highest valid ARM IRQ source (64 + 7 = 71). */
+    writel(FIQ_CONTROL, 0xc7); /* fiq_select = 71, fiq_enable = 1 */
+    val = readl(FIQ_CONTROL);
+    g_assert_cmpint(val, ==, 0xc7);
+
+    /* Select the highest valid GPU IRQ source. */
+    writel(FIQ_CONTROL, 0x3f); /* fiq_select = 63, fiq_enable = 0 */
+    val = readl(FIQ_CONTROL);
+    g_assert_cmpint(val, ==, 0x3f);
+}
+
+int main(int argc, char **argv)
+{
+    int ret;
+
+    g_test_init(&argc, &argv, NULL);
+
+    qtest_add_func("/bcm2835/bcm2835-ic/fiq-select-out-of-range",
+                   test_fiq_select_out_of_range);
+    qtest_add_func("/bcm2835/bcm2835-ic/fiq-select-valid",
+                   test_fiq_select_valid);
+
+    qtest_start("-machine raspi3b");
+    ret = g_test_run();
+    qtest_end();
+
+    return ret;
+}
diff --git a/tests/qtest/meson.build b/tests/qtest/meson.build
index 6ea825e1d7d..73c9bd9fb65 100644
--- a/tests/qtest/meson.build
+++ b/tests/qtest/meson.build
@@ -254,7 +254,7 @@ qtests_aarch64 = \
     ['tpm-tis-device-test', 'tpm-tis-device-swtpm-test'] : []) +               
                          \
   (config_all_devices.has_key('CONFIG_XLNX_ZYNQMP_ARM') ? ['xlnx-can-test', 
'fuzz-xlnx-dp-test'] : []) + \
   (config_all_devices.has_key('CONFIG_XLNX_VERSAL') ? ['xlnx-canfd-test', 
'xlnx-versal-trng-test'] : []) + \
-  (config_all_devices.has_key('CONFIG_RASPI') ? ['bcm2835-dma-test', 
'bcm2835-i2c-test'] : []) +  \
+  (config_all_devices.has_key('CONFIG_RASPI') ? ['bcm2835-dma-test', 
'bcm2835-i2c-test', 'bcm2835-ic-test'] : []) +  \
   (config_all_accel.has_key('CONFIG_TCG') and                                  
          \
    config_all_devices.has_key('CONFIG_TPM_TIS_I2C') ? ['tpm-tis-i2c-test'] : 
[]) + \
   (config_all_devices.has_key('CONFIG_ASPEED_SOC') ? qtests_aspeed64 : []) + \
-- 
2.47.3


Reply via email to