All of lore.kernel.org
 help / color / mirror / Atom feed
* Re: [PATCH v2 1/2] intel_iommu: widen impl.min_access_size to 8 to fix MMIO abort
  2026-04-24 20:18 ` [PATCH v2 1/2] intel_iommu: widen impl.min_access_size to 8 to fix MMIO abort Junjie Cao
@ 2026-04-24 13:58   ` Philippe Mathieu-Daudé
  2026-04-27  1:24     ` Junjie Cao
  2026-04-27  5:23   ` Duan, Zhenzhong
  1 sibling, 1 reply; 15+ messages in thread
From: Philippe Mathieu-Daudé @ 2026-04-24 13:58 UTC (permalink / raw)
  To: Junjie Cao, qemu-devel
  Cc: mst, jasowang, yi.l.liu, clement.mathieu--drif, zhenzhong.duan

On 24/4/26 22:18, Junjie Cao wrote:
> Raise .impl.min_access_size from 4 to 8 in vtd_mem_ops so the memory
> subsystem always widens guest accesses to 8 bytes before calling the
> handler.  This eliminates all 25 assert(size == 4) sites that crashed
> QEMU on an 8-byte access to a 32-bit-only register.
> 
> With size always 8, the if/else branches for 64-bit register pairs
> collapse.  A zero-extended 4-byte write to the low half is safe:
> wmask protects read-only upper bits, and trigger functions re-read
> the register file and guard on their action bits.
> 
> The entry bounds check is relaxed to `addr >= DMAR_REG_SIZE` since
> the widened size no longer reflects the guest access width; the
> framework guarantees addr stays within the MemoryRegion.  Default
> branches fall back to vtd_get/set_long() when addr + 8 would exceed
> the register file.
> 
> Suggested-by: Philippe Mathieu-Daudé <philmd@linaro.org>
> Signed-off-by: Junjie Cao <junjie.cao@intel.com>
> ---
>   hw/i386/intel_iommu.c | 121 ++++++++----------------------------------
>   1 file changed, 23 insertions(+), 98 deletions(-)
> 
> diff --git a/hw/i386/intel_iommu.c b/hw/i386/intel_iommu.c
> index f395fa248c..4b25907778 100644
> --- a/hw/i386/intel_iommu.c
> +++ b/hw/i386/intel_iommu.c
> @@ -3697,7 +3697,7 @@ static uint64_t vtd_mem_read(void *opaque, hwaddr addr, unsigned size)
>   
>       trace_vtd_reg_read(addr, size);
>   
> -    if (addr + size > DMAR_REG_SIZE) {
> +    if (addr >= DMAR_REG_SIZE) {
>           error_report_once("%s: MMIO over range: addr=0x%" PRIx64
>                             " size=0x%x", __func__, addr, size);
>           return (uint64_t)-1;
> @@ -3707,13 +3707,9 @@ static uint64_t vtd_mem_read(void *opaque, hwaddr addr, unsigned size)
>       /* Root Table Address Register, 64-bit */
>       case DMAR_RTADDR_REG:
>           val = vtd_get_quad_raw(s, DMAR_RTADDR_REG);
> -        if (size == 4) {
> -            val = val & ((1ULL << 32) - 1);
> -        }
>           break;
>   
>       case DMAR_RTADDR_REG_HI:
> -        assert(size == 4);
>           val = vtd_get_quad_raw(s, DMAR_RTADDR_REG) >> 32;
>           break;
>   
> @@ -3722,26 +3718,21 @@ static uint64_t vtd_mem_read(void *opaque, hwaddr addr, unsigned size)
>           val = s->iq |
>                 (vtd_get_quad(s, DMAR_IQA_REG) &
>                 (VTD_IQA_QS | VTD_IQA_DW_MASK));
> -        if (size == 4) {
> -            val = val & ((1ULL << 32) - 1);
> -        }
>           break;
>   
>       case DMAR_IQA_REG_HI:
> -        assert(size == 4);
>           val = s->iq >> 32;
>           break;
>   
>       case DMAR_PEUADDR_REG:
> -        assert(size == 4);

Does this device support unaligned accesses? (I doubt). Otherwise
aren't all these assert(size == 4) now g_assert_not_reached()?


> @@ -4184,7 +4109,7 @@ static const MemoryRegionOps vtd_mem_ops = {
>       .write = vtd_mem_write,
>       .endianness = DEVICE_LITTLE_ENDIAN,
>       .impl = {
> -        .min_access_size = 4,
> +        .min_access_size = 8,
>           .max_access_size = 8,
>       },
>       .valid = {



^ permalink raw reply	[flat|nested] 15+ messages in thread

* [PATCH v2 0/2] intel_iommu: fix guest-triggerable assert in MMIO handlers
@ 2026-04-24 20:18 Junjie Cao
  2026-04-24 20:18 ` [PATCH v2 1/2] intel_iommu: widen impl.min_access_size to 8 to fix MMIO abort Junjie Cao
  2026-04-24 20:18 ` [PATCH v2 " Junjie Cao
  0 siblings, 2 replies; 15+ messages in thread
From: Junjie Cao @ 2026-04-24 20:18 UTC (permalink / raw)
  To: qemu-devel
  Cc: junjie.cao, mst, jasowang, yi.l.liu, clement.mathieu--drif,
	philmd, zhenzhong.duan

An 8-byte guest access to any 32-bit-only VT-d register hits
assert(size == 4) and aborts QEMU.  Found by fuzzing with
generic-fuzz; 24 distinct crash inputs all share the same root cause.

v1: https://lore.kernel.org/all/20260420170523.17908-1-junjie.cao@intel.com/
v2: Per Philippe's suggestion, widen .impl.min_access_size to 8
instead of replacing asserts with guest-error checks.  This lets the
memory subsystem always pass size == 8 to the handler, eliminating
all 25 asserts and every size-based branch.

Junjie Cao (2):
  intel_iommu: widen impl.min_access_size to 8 to fix MMIO abort
  tests/qtest: add 8-byte MMIO access sweep for intel-iommu

 hw/i386/intel_iommu.c          | 121 +++++++--------------------------
 tests/qtest/intel-iommu-test.c |  30 ++++++++
 2 files changed, 53 insertions(+), 98 deletions(-)

-- 
2.43.0



^ permalink raw reply	[flat|nested] 15+ messages in thread

* [PATCH v2 1/2] intel_iommu: widen impl.min_access_size to 8 to fix MMIO abort
  2026-04-24 20:18 [PATCH v2 0/2] intel_iommu: fix guest-triggerable assert in MMIO handlers Junjie Cao
@ 2026-04-24 20:18 ` Junjie Cao
  2026-04-24 13:58   ` Philippe Mathieu-Daudé
  2026-04-27  5:23   ` Duan, Zhenzhong
  2026-04-24 20:18 ` [PATCH v2 " Junjie Cao
  1 sibling, 2 replies; 15+ messages in thread
From: Junjie Cao @ 2026-04-24 20:18 UTC (permalink / raw)
  To: qemu-devel
  Cc: junjie.cao, mst, jasowang, yi.l.liu, clement.mathieu--drif,
	philmd, zhenzhong.duan

Raise .impl.min_access_size from 4 to 8 in vtd_mem_ops so the memory
subsystem always widens guest accesses to 8 bytes before calling the
handler.  This eliminates all 25 assert(size == 4) sites that crashed
QEMU on an 8-byte access to a 32-bit-only register.

With size always 8, the if/else branches for 64-bit register pairs
collapse.  A zero-extended 4-byte write to the low half is safe:
wmask protects read-only upper bits, and trigger functions re-read
the register file and guard on their action bits.

The entry bounds check is relaxed to `addr >= DMAR_REG_SIZE` since
the widened size no longer reflects the guest access width; the
framework guarantees addr stays within the MemoryRegion.  Default
branches fall back to vtd_get/set_long() when addr + 8 would exceed
the register file.

Suggested-by: Philippe Mathieu-Daudé <philmd@linaro.org>
Signed-off-by: Junjie Cao <junjie.cao@intel.com>
---
 hw/i386/intel_iommu.c | 121 ++++++++----------------------------------
 1 file changed, 23 insertions(+), 98 deletions(-)

diff --git a/hw/i386/intel_iommu.c b/hw/i386/intel_iommu.c
index f395fa248c..4b25907778 100644
--- a/hw/i386/intel_iommu.c
+++ b/hw/i386/intel_iommu.c
@@ -3697,7 +3697,7 @@ static uint64_t vtd_mem_read(void *opaque, hwaddr addr, unsigned size)
 
     trace_vtd_reg_read(addr, size);
 
-    if (addr + size > DMAR_REG_SIZE) {
+    if (addr >= DMAR_REG_SIZE) {
         error_report_once("%s: MMIO over range: addr=0x%" PRIx64
                           " size=0x%x", __func__, addr, size);
         return (uint64_t)-1;
@@ -3707,13 +3707,9 @@ static uint64_t vtd_mem_read(void *opaque, hwaddr addr, unsigned size)
     /* Root Table Address Register, 64-bit */
     case DMAR_RTADDR_REG:
         val = vtd_get_quad_raw(s, DMAR_RTADDR_REG);
-        if (size == 4) {
-            val = val & ((1ULL << 32) - 1);
-        }
         break;
 
     case DMAR_RTADDR_REG_HI:
-        assert(size == 4);
         val = vtd_get_quad_raw(s, DMAR_RTADDR_REG) >> 32;
         break;
 
@@ -3722,26 +3718,21 @@ static uint64_t vtd_mem_read(void *opaque, hwaddr addr, unsigned size)
         val = s->iq |
               (vtd_get_quad(s, DMAR_IQA_REG) &
               (VTD_IQA_QS | VTD_IQA_DW_MASK));
-        if (size == 4) {
-            val = val & ((1ULL << 32) - 1);
-        }
         break;
 
     case DMAR_IQA_REG_HI:
-        assert(size == 4);
         val = s->iq >> 32;
         break;
 
     case DMAR_PEUADDR_REG:
-        assert(size == 4);
         val = vtd_get_long_raw(s, DMAR_PEUADDR_REG);
         break;
 
     default:
-        if (size == 4) {
-            val = vtd_get_long(s, addr);
-        } else {
+        if (addr + 8 <= DMAR_REG_SIZE) {
             val = vtd_get_quad(s, addr);
+        } else {
+            val = vtd_get_long(s, addr);
         }
     }
 
@@ -3755,7 +3746,7 @@ static void vtd_mem_write(void *opaque, hwaddr addr,
 
     trace_vtd_reg_write(addr, size, val);
 
-    if (addr + size > DMAR_REG_SIZE) {
+    if (addr >= DMAR_REG_SIZE) {
         error_report_once("%s: MMIO over range: addr=0x%" PRIx64
                           " size=0x%x", __func__, addr, size);
         return;
@@ -3770,238 +3761,172 @@ static void vtd_mem_write(void *opaque, hwaddr addr,
 
     /* Context Command Register, 64-bit */
     case DMAR_CCMD_REG:
-        if (size == 4) {
-            vtd_set_long(s, addr, val);
-        } else {
-            vtd_set_quad(s, addr, val);
-            vtd_handle_ccmd_write(s);
-        }
+        vtd_set_quad(s, addr, val);
+        vtd_handle_ccmd_write(s);
         break;
 
     case DMAR_CCMD_REG_HI:
-        assert(size == 4);
         vtd_set_long(s, addr, val);
         vtd_handle_ccmd_write(s);
         break;
 
     /* IOTLB Invalidation Register, 64-bit */
     case DMAR_IOTLB_REG:
-        if (size == 4) {
-            vtd_set_long(s, addr, val);
-        } else {
-            vtd_set_quad(s, addr, val);
-            vtd_handle_iotlb_write(s);
-        }
+        vtd_set_quad(s, addr, val);
+        vtd_handle_iotlb_write(s);
         break;
 
     case DMAR_IOTLB_REG_HI:
-        assert(size == 4);
         vtd_set_long(s, addr, val);
         vtd_handle_iotlb_write(s);
         break;
 
     case DMAR_PEUADDR_REG:
-        assert(size == 4);
         vtd_set_long(s, addr, val);
         break;
 
     /* Invalidate Address Register, 64-bit */
     case DMAR_IVA_REG:
-        if (size == 4) {
-            vtd_set_long(s, addr, val);
-        } else {
-            vtd_set_quad(s, addr, val);
-        }
+        vtd_set_quad(s, addr, val);
         break;
 
     case DMAR_IVA_REG_HI:
-        assert(size == 4);
         vtd_set_long(s, addr, val);
         break;
 
     /* Fault Status Register, 32-bit */
     case DMAR_FSTS_REG:
-        assert(size == 4);
         vtd_set_long(s, addr, val);
         vtd_handle_fsts_write(s);
         break;
 
     /* Fault Event Control Register, 32-bit */
     case DMAR_FECTL_REG:
-        assert(size == 4);
         vtd_set_long(s, addr, val);
         vtd_handle_fectl_write(s);
         break;
 
     /* Fault Event Data Register, 32-bit */
     case DMAR_FEDATA_REG:
-        assert(size == 4);
         vtd_set_long(s, addr, val);
         break;
 
     /* Fault Event Address Register, 32-bit */
     case DMAR_FEADDR_REG:
-        if (size == 4) {
-            vtd_set_long(s, addr, val);
-        } else {
-            /*
-             * While the register is 32-bit only, some guests (Xen...) write to
-             * it with 64-bit.
-             */
-            vtd_set_quad(s, addr, val);
-        }
+        vtd_set_quad(s, addr, val);
         break;
 
     /* Fault Event Upper Address Register, 32-bit */
     case DMAR_FEUADDR_REG:
-        assert(size == 4);
         vtd_set_long(s, addr, val);
         break;
 
     /* Protected Memory Enable Register, 32-bit */
     case DMAR_PMEN_REG:
-        assert(size == 4);
         vtd_set_long(s, addr, val);
         break;
 
     /* Root Table Address Register, 64-bit */
     case DMAR_RTADDR_REG:
-        if (size == 4) {
-            vtd_set_long(s, addr, val);
-        } else {
-            vtd_set_quad(s, addr, val);
-        }
+        vtd_set_quad(s, addr, val);
         break;
 
     case DMAR_RTADDR_REG_HI:
-        assert(size == 4);
         vtd_set_long(s, addr, val);
         break;
 
     /* Invalidation Queue Tail Register, 64-bit */
     case DMAR_IQT_REG:
-        if (size == 4) {
-            vtd_set_long(s, addr, val);
-        } else {
-            vtd_set_quad(s, addr, val);
-        }
+        vtd_set_quad(s, addr, val);
         vtd_handle_iqt_write(s);
         break;
 
     case DMAR_IQT_REG_HI:
-        assert(size == 4);
         vtd_set_long(s, addr, val);
         /* 19:63 of IQT_REG is RsvdZ, do nothing here */
         break;
 
     /* Invalidation Queue Address Register, 64-bit */
     case DMAR_IQA_REG:
-        if (size == 4) {
-            vtd_set_long(s, addr, val);
-        } else {
-            vtd_set_quad(s, addr, val);
-        }
+        vtd_set_quad(s, addr, val);
         vtd_update_iq_dw(s);
         break;
 
     case DMAR_IQA_REG_HI:
-        assert(size == 4);
         vtd_set_long(s, addr, val);
         break;
 
     /* Invalidation Completion Status Register, 32-bit */
     case DMAR_ICS_REG:
-        assert(size == 4);
         vtd_set_long(s, addr, val);
         vtd_handle_ics_write(s);
         break;
 
     /* Invalidation Event Control Register, 32-bit */
     case DMAR_IECTL_REG:
-        assert(size == 4);
         vtd_set_long(s, addr, val);
         vtd_handle_iectl_write(s);
         break;
 
     /* Invalidation Event Data Register, 32-bit */
     case DMAR_IEDATA_REG:
-        assert(size == 4);
         vtd_set_long(s, addr, val);
         break;
 
     /* Invalidation Event Address Register, 32-bit */
     case DMAR_IEADDR_REG:
-        assert(size == 4);
         vtd_set_long(s, addr, val);
         break;
 
     /* Invalidation Event Upper Address Register, 32-bit */
     case DMAR_IEUADDR_REG:
-        assert(size == 4);
         vtd_set_long(s, addr, val);
         break;
 
     /* Fault Recording Registers, 128-bit */
     case DMAR_FRCD_REG_0_0:
-        if (size == 4) {
-            vtd_set_long(s, addr, val);
-        } else {
-            vtd_set_quad(s, addr, val);
-        }
+        vtd_set_quad(s, addr, val);
         break;
 
     case DMAR_FRCD_REG_0_1:
-        assert(size == 4);
         vtd_set_long(s, addr, val);
         break;
 
     case DMAR_FRCD_REG_0_2:
-        if (size == 4) {
-            vtd_set_long(s, addr, val);
-        } else {
-            vtd_set_quad(s, addr, val);
-            /* May clear bit 127 (Fault), update PPF */
-            vtd_update_fsts_ppf(s);
-        }
+        vtd_set_quad(s, addr, val);
+        /* May clear bit 127 (Fault), update PPF */
+        vtd_update_fsts_ppf(s);
         break;
 
     case DMAR_FRCD_REG_0_3:
-        assert(size == 4);
         vtd_set_long(s, addr, val);
         /* May clear bit 127 (Fault), update PPF */
         vtd_update_fsts_ppf(s);
         break;
 
     case DMAR_IRTA_REG:
-        if (size == 4) {
-            vtd_set_long(s, addr, val);
-        } else {
-            vtd_set_quad(s, addr, val);
-        }
+        vtd_set_quad(s, addr, val);
         break;
 
     case DMAR_IRTA_REG_HI:
-        assert(size == 4);
         vtd_set_long(s, addr, val);
         break;
 
     case DMAR_PRS_REG:
-        assert(size == 4);
         vtd_set_long(s, addr, val);
         vtd_handle_prs_write(s);
         break;
 
     case DMAR_PECTL_REG:
-        assert(size == 4);
         vtd_set_long(s, addr, val);
         vtd_handle_pectl_write(s);
         break;
 
     default:
-        if (size == 4) {
-            vtd_set_long(s, addr, val);
-        } else {
+        if (addr + 8 <= DMAR_REG_SIZE) {
             vtd_set_quad(s, addr, val);
+        } else {
+            vtd_set_long(s, addr, val);
         }
     }
 }
@@ -4184,7 +4109,7 @@ static const MemoryRegionOps vtd_mem_ops = {
     .write = vtd_mem_write,
     .endianness = DEVICE_LITTLE_ENDIAN,
     .impl = {
-        .min_access_size = 4,
+        .min_access_size = 8,
         .max_access_size = 8,
     },
     .valid = {
-- 
2.43.0



^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v2 2/2] tests/qtest: add 8-byte MMIO access sweep for intel-iommu
  2026-04-24 20:18 [PATCH v2 0/2] intel_iommu: fix guest-triggerable assert in MMIO handlers Junjie Cao
  2026-04-24 20:18 ` [PATCH v2 1/2] intel_iommu: widen impl.min_access_size to 8 to fix MMIO abort Junjie Cao
@ 2026-04-24 20:18 ` Junjie Cao
  1 sibling, 0 replies; 15+ messages in thread
From: Junjie Cao @ 2026-04-24 20:18 UTC (permalink / raw)
  To: qemu-devel
  Cc: junjie.cao, mst, jasowang, yi.l.liu, clement.mathieu--drif,
	philmd, zhenzhong.duan

Sweep every 4-byte-aligned offset in the VT-d MMIO register space
with 8-byte reads and writes to verify that no register handler
aborts on an oversized access.

Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
Signed-off-by: Junjie Cao <junjie.cao@intel.com>
---
 tests/qtest/intel-iommu-test.c | 30 ++++++++++++++++++++++++++++++
 1 file changed, 30 insertions(+)

diff --git a/tests/qtest/intel-iommu-test.c b/tests/qtest/intel-iommu-test.c
index e5cc6acaf0..b1763ed294 100644
--- a/tests/qtest/intel-iommu-test.c
+++ b/tests/qtest/intel-iommu-test.c
@@ -17,11 +17,39 @@
 #define ECAP_STAGE_1_FIXED1   (VTD_ECAP_QI |  VTD_ECAP_IR | VTD_ECAP_IRO | \
                               VTD_ECAP_MHMV | VTD_ECAP_SMTS | VTD_ECAP_FSTS)
 
+static inline uint32_t vtd_reg_readl(QTestState *s, uint64_t offset)
+{
+    return qtest_readl(s, Q35_HOST_BRIDGE_IOMMU_ADDR + offset);
+}
+
 static inline uint64_t vtd_reg_readq(QTestState *s, uint64_t offset)
 {
     return qtest_readq(s, Q35_HOST_BRIDGE_IOMMU_ADDR + offset);
 }
 
+static inline void vtd_reg_writeq(QTestState *s, uint64_t offset,
+                                  uint64_t value)
+{
+    qtest_writeq(s, Q35_HOST_BRIDGE_IOMMU_ADDR + offset, value);
+}
+
+static void test_intel_iommu_8byte_access(void)
+{
+    QTestState *s;
+    uint64_t off;
+
+    s = qtest_init("-M q35 -device intel-iommu");
+
+    for (off = 0; off < DMAR_REG_SIZE; off += 4) {
+        vtd_reg_readq(s, off);
+        vtd_reg_writeq(s, off, 0);
+    }
+
+    g_assert_cmpuint(vtd_reg_readl(s, DMAR_VER_REG), !=, 0);
+
+    qtest_quit(s);
+}
+
 static void test_intel_iommu_stage_1(void)
 {
     uint8_t init_csr[DMAR_REG_SIZE];     /* register values */
@@ -58,6 +86,8 @@ static void test_intel_iommu_stage_1(void)
 int main(int argc, char **argv)
 {
     g_test_init(&argc, &argv, NULL);
+    qtest_add_func("/q35/intel-iommu/8byte-access",
+                   test_intel_iommu_8byte_access);
     qtest_add_func("/q35/intel-iommu/stage-1", test_intel_iommu_stage_1);
 
     return g_test_run();
-- 
2.43.0



^ permalink raw reply related	[flat|nested] 15+ messages in thread

* Re: [PATCH v2 1/2] intel_iommu: widen impl.min_access_size to 8 to fix MMIO abort
  2026-04-24 13:58   ` Philippe Mathieu-Daudé
@ 2026-04-27  1:24     ` Junjie Cao
  0 siblings, 0 replies; 15+ messages in thread
From: Junjie Cao @ 2026-04-27  1:24 UTC (permalink / raw)
  To: Philippe Mathieu-Daudé, qemu-devel
  Cc: junjie.cao, mst, jasowang, yi.l.liu, clement.mathieu--drif,
	zhenzhong.duan

Hi Philippe,

Thanks for the review and the good question.

Looking at access_with_adjusted_size(), the _HI offsets (0x24, 0x2c, ...)
and standalone 32-bit registers like DMAR_PEUADDR_REG (0xec) are
4-byte-aligned but not 8-byte-aligned.  An 8-byte guest access to them
is indeed rejected by memory_region_access_valid (0xec & 7 != 0).  But a
4-byte access passes (0xec & 3 == 0), and the framework then widens size
to 8 while keeping the original addr -- so the handler is still reached
at these offsets.

For example, a guest 4-byte write to DMAR_PEUADDR_REG (0xec):

  1. memory_region_access_valid: size=4, 0xec & 3 == 0 -> pass
  2. access_with_adjusted_size: access_size = MAX(MIN(4,8),8) = 8
  3. write_accessor calls vtd_mem_write(opaque, 0xec, val_zext, 8)
  4. switch(addr) -> case DMAR_PEUADDR_REG -> vtd_set_long(s, addr, val)

So I believe these branches are still needed.  Please let me know if
I've missed something, or if you'd like any changes for v3.

Many thanks,
Junjie


^ permalink raw reply	[flat|nested] 15+ messages in thread

* RE: [PATCH v2 1/2] intel_iommu: widen impl.min_access_size to 8 to fix MMIO abort
  2026-04-24 20:18 ` [PATCH v2 1/2] intel_iommu: widen impl.min_access_size to 8 to fix MMIO abort Junjie Cao
  2026-04-24 13:58   ` Philippe Mathieu-Daudé
@ 2026-04-27  5:23   ` Duan, Zhenzhong
  2026-04-30  0:16     ` Junjie Cao
  1 sibling, 1 reply; 15+ messages in thread
From: Duan, Zhenzhong @ 2026-04-27  5:23 UTC (permalink / raw)
  To: Cao, Junjie, qemu-devel@nongnu.org
  Cc: mst@redhat.com, jasowang@redhat.com, Liu, Yi L,
	clement.mathieu--drif@bull.com, philmd@linaro.org



>-----Original Message-----
>From: Cao, Junjie <junjie.cao@intel.com>
>Subject: [PATCH v2 1/2] intel_iommu: widen impl.min_access_size to 8 to fix
>MMIO abort
>
>Raise .impl.min_access_size from 4 to 8 in vtd_mem_ops so the memory
>subsystem always widens guest accesses to 8 bytes before calling the
>handler.  This eliminates all 25 assert(size == 4) sites that crashed
>QEMU on an 8-byte access to a 32-bit-only register.
>
>With size always 8, the if/else branches for 64-bit register pairs
>collapse.  A zero-extended 4-byte write to the low half is safe:
>wmask protects read-only upper bits, and trigger functions re-read
>the register file and guard on their action bits.
>
>The entry bounds check is relaxed to `addr >= DMAR_REG_SIZE` since
>the widened size no longer reflects the guest access width; the
>framework guarantees addr stays within the MemoryRegion.  Default
>branches fall back to vtd_get/set_long() when addr + 8 would exceed
>the register file.
>
>Suggested-by: Philippe Mathieu-Daudé <philmd@linaro.org>
>Signed-off-by: Junjie Cao <junjie.cao@intel.com>
>---
> hw/i386/intel_iommu.c | 121 ++++++++----------------------------------
> 1 file changed, 23 insertions(+), 98 deletions(-)
>
>diff --git a/hw/i386/intel_iommu.c b/hw/i386/intel_iommu.c
>index f395fa248c..4b25907778 100644
>--- a/hw/i386/intel_iommu.c
>+++ b/hw/i386/intel_iommu.c
>@@ -3697,7 +3697,7 @@ static uint64_t vtd_mem_read(void *opaque, hwaddr
>addr, unsigned size)
>
>     trace_vtd_reg_read(addr, size);
>
>-    if (addr + size > DMAR_REG_SIZE) {
>+    if (addr >= DMAR_REG_SIZE) {
>         error_report_once("%s: MMIO over range: addr=0x%" PRIx64
>                           " size=0x%x", __func__, addr, size);
>         return (uint64_t)-1;
>@@ -3707,13 +3707,9 @@ static uint64_t vtd_mem_read(void *opaque, hwaddr
>addr, unsigned size)
>     /* Root Table Address Register, 64-bit */
>     case DMAR_RTADDR_REG:
>         val = vtd_get_quad_raw(s, DMAR_RTADDR_REG);
>-        if (size == 4) {
>-            val = val & ((1ULL << 32) - 1);
>-        }
>         break;
>
>     case DMAR_RTADDR_REG_HI:
>-        assert(size == 4);
>         val = vtd_get_quad_raw(s, DMAR_RTADDR_REG) >> 32;
>         break;
>
>@@ -3722,26 +3718,21 @@ static uint64_t vtd_mem_read(void *opaque, hwaddr
>addr, unsigned size)
>         val = s->iq |
>               (vtd_get_quad(s, DMAR_IQA_REG) &
>               (VTD_IQA_QS | VTD_IQA_DW_MASK));
>-        if (size == 4) {
>-            val = val & ((1ULL << 32) - 1);
>-        }
>         break;
>
>     case DMAR_IQA_REG_HI:
>-        assert(size == 4);
>         val = s->iq >> 32;
>         break;
>
>     case DMAR_PEUADDR_REG:
>-        assert(size == 4);
>         val = vtd_get_long_raw(s, DMAR_PEUADDR_REG);
>         break;
>
>     default:
>-        if (size == 4) {
>-            val = vtd_get_long(s, addr);
>-        } else {
>+        if (addr + 8 <= DMAR_REG_SIZE) {
>             val = vtd_get_quad(s, addr);
>+        } else {
>+            val = vtd_get_long(s, addr);
>         }
>     }
>
>@@ -3755,7 +3746,7 @@ static void vtd_mem_write(void *opaque, hwaddr addr,
>
>     trace_vtd_reg_write(addr, size, val);
>
>-    if (addr + size > DMAR_REG_SIZE) {
>+    if (addr >= DMAR_REG_SIZE) {
>         error_report_once("%s: MMIO over range: addr=0x%" PRIx64
>                           " size=0x%x", __func__, addr, size);
>         return;
>@@ -3770,238 +3761,172 @@ static void vtd_mem_write(void *opaque, hwaddr
>addr,
>
>     /* Context Command Register, 64-bit */
>     case DMAR_CCMD_REG:
>-        if (size == 4) {
>-            vtd_set_long(s, addr, val);
>-        } else {
>-            vtd_set_quad(s, addr, val);
>-            vtd_handle_ccmd_write(s);
>-        }
>+        vtd_set_quad(s, addr, val);
>+        vtd_handle_ccmd_write(s);

Will this cause wrong emulation when writing DMAR_CCMD_REG with 4 bytes size?

>         break;
>
>     case DMAR_CCMD_REG_HI:
>-        assert(size == 4);
>         vtd_set_long(s, addr, val);
>         vtd_handle_ccmd_write(s);
>         break;
>
>     /* IOTLB Invalidation Register, 64-bit */
>     case DMAR_IOTLB_REG:
>-        if (size == 4) {
>-            vtd_set_long(s, addr, val);
>-        } else {
>-            vtd_set_quad(s, addr, val);
>-            vtd_handle_iotlb_write(s);
>-        }
>+        vtd_set_quad(s, addr, val);
>+        vtd_handle_iotlb_write(s);
>         break;
>
>     case DMAR_IOTLB_REG_HI:
>-        assert(size == 4);
>         vtd_set_long(s, addr, val);
>         vtd_handle_iotlb_write(s);
>         break;
>
>     case DMAR_PEUADDR_REG:
>-        assert(size == 4);
>         vtd_set_long(s, addr, val);
>         break;
>
>     /* Invalidate Address Register, 64-bit */
>     case DMAR_IVA_REG:
>-        if (size == 4) {
>-            vtd_set_long(s, addr, val);
>-        } else {
>-            vtd_set_quad(s, addr, val);
>-        }
>+        vtd_set_quad(s, addr, val);
>         break;
>
>     case DMAR_IVA_REG_HI:
>-        assert(size == 4);
>         vtd_set_long(s, addr, val);
>         break;
>
>     /* Fault Status Register, 32-bit */
>     case DMAR_FSTS_REG:
>-        assert(size == 4);
>         vtd_set_long(s, addr, val);
>         vtd_handle_fsts_write(s);
>         break;
>
>     /* Fault Event Control Register, 32-bit */
>     case DMAR_FECTL_REG:
>-        assert(size == 4);
>         vtd_set_long(s, addr, val);
>         vtd_handle_fectl_write(s);
>         break;
>
>     /* Fault Event Data Register, 32-bit */
>     case DMAR_FEDATA_REG:
>-        assert(size == 4);
>         vtd_set_long(s, addr, val);
>         break;
>
>     /* Fault Event Address Register, 32-bit */
>     case DMAR_FEADDR_REG:
>-        if (size == 4) {
>-            vtd_set_long(s, addr, val);
>-        } else {
>-            /*
>-             * While the register is 32-bit only, some guests (Xen...) write to
>-             * it with 64-bit.
>-             */
>-            vtd_set_quad(s, addr, val);
>-        }
>+        vtd_set_quad(s, addr, val);
>         break;
>
>     /* Fault Event Upper Address Register, 32-bit */
>     case DMAR_FEUADDR_REG:
>-        assert(size == 4);
>         vtd_set_long(s, addr, val);
>         break;
>
>     /* Protected Memory Enable Register, 32-bit */
>     case DMAR_PMEN_REG:
>-        assert(size == 4);
>         vtd_set_long(s, addr, val);
>         break;
>
>     /* Root Table Address Register, 64-bit */
>     case DMAR_RTADDR_REG:
>-        if (size == 4) {
>-            vtd_set_long(s, addr, val);
>-        } else {
>-            vtd_set_quad(s, addr, val);
>-        }
>+        vtd_set_quad(s, addr, val);

Same here, maybe also others, I didn't check all registers.

Thanks
Zhenzhong

>         break;
>
>     case DMAR_RTADDR_REG_HI:
>-        assert(size == 4);
>         vtd_set_long(s, addr, val);
>         break;
>
>     /* Invalidation Queue Tail Register, 64-bit */
>     case DMAR_IQT_REG:
>-        if (size == 4) {
>-            vtd_set_long(s, addr, val);
>-        } else {
>-            vtd_set_quad(s, addr, val);
>-        }
>+        vtd_set_quad(s, addr, val);
>         vtd_handle_iqt_write(s);
>         break;
>
>     case DMAR_IQT_REG_HI:
>-        assert(size == 4);
>         vtd_set_long(s, addr, val);
>         /* 19:63 of IQT_REG is RsvdZ, do nothing here */
>         break;
>
>     /* Invalidation Queue Address Register, 64-bit */
>     case DMAR_IQA_REG:
>-        if (size == 4) {
>-            vtd_set_long(s, addr, val);
>-        } else {
>-            vtd_set_quad(s, addr, val);
>-        }
>+        vtd_set_quad(s, addr, val);
>         vtd_update_iq_dw(s);
>         break;
>
>     case DMAR_IQA_REG_HI:
>-        assert(size == 4);
>         vtd_set_long(s, addr, val);
>         break;
>
>     /* Invalidation Completion Status Register, 32-bit */
>     case DMAR_ICS_REG:
>-        assert(size == 4);
>         vtd_set_long(s, addr, val);
>         vtd_handle_ics_write(s);
>         break;
>
>     /* Invalidation Event Control Register, 32-bit */
>     case DMAR_IECTL_REG:
>-        assert(size == 4);
>         vtd_set_long(s, addr, val);
>         vtd_handle_iectl_write(s);
>         break;
>
>     /* Invalidation Event Data Register, 32-bit */
>     case DMAR_IEDATA_REG:
>-        assert(size == 4);
>         vtd_set_long(s, addr, val);
>         break;
>
>     /* Invalidation Event Address Register, 32-bit */
>     case DMAR_IEADDR_REG:
>-        assert(size == 4);
>         vtd_set_long(s, addr, val);
>         break;
>
>     /* Invalidation Event Upper Address Register, 32-bit */
>     case DMAR_IEUADDR_REG:
>-        assert(size == 4);
>         vtd_set_long(s, addr, val);
>         break;
>
>     /* Fault Recording Registers, 128-bit */
>     case DMAR_FRCD_REG_0_0:
>-        if (size == 4) {
>-            vtd_set_long(s, addr, val);
>-        } else {
>-            vtd_set_quad(s, addr, val);
>-        }
>+        vtd_set_quad(s, addr, val);
>         break;
>
>     case DMAR_FRCD_REG_0_1:
>-        assert(size == 4);
>         vtd_set_long(s, addr, val);
>         break;
>
>     case DMAR_FRCD_REG_0_2:
>-        if (size == 4) {
>-            vtd_set_long(s, addr, val);
>-        } else {
>-            vtd_set_quad(s, addr, val);
>-            /* May clear bit 127 (Fault), update PPF */
>-            vtd_update_fsts_ppf(s);
>-        }
>+        vtd_set_quad(s, addr, val);
>+        /* May clear bit 127 (Fault), update PPF */
>+        vtd_update_fsts_ppf(s);
>         break;
>
>     case DMAR_FRCD_REG_0_3:
>-        assert(size == 4);
>         vtd_set_long(s, addr, val);
>         /* May clear bit 127 (Fault), update PPF */
>         vtd_update_fsts_ppf(s);
>         break;
>
>     case DMAR_IRTA_REG:
>-        if (size == 4) {
>-            vtd_set_long(s, addr, val);
>-        } else {
>-            vtd_set_quad(s, addr, val);
>-        }
>+        vtd_set_quad(s, addr, val);
>         break;
>
>     case DMAR_IRTA_REG_HI:
>-        assert(size == 4);
>         vtd_set_long(s, addr, val);
>         break;
>
>     case DMAR_PRS_REG:
>-        assert(size == 4);
>         vtd_set_long(s, addr, val);
>         vtd_handle_prs_write(s);
>         break;
>
>     case DMAR_PECTL_REG:
>-        assert(size == 4);
>         vtd_set_long(s, addr, val);
>         vtd_handle_pectl_write(s);
>         break;
>
>     default:
>-        if (size == 4) {
>-            vtd_set_long(s, addr, val);
>-        } else {
>+        if (addr + 8 <= DMAR_REG_SIZE) {
>             vtd_set_quad(s, addr, val);
>+        } else {
>+            vtd_set_long(s, addr, val);
>         }
>     }
> }
>@@ -4184,7 +4109,7 @@ static const MemoryRegionOps vtd_mem_ops = {
>     .write = vtd_mem_write,
>     .endianness = DEVICE_LITTLE_ENDIAN,
>     .impl = {
>-        .min_access_size = 4,
>+        .min_access_size = 8,
>         .max_access_size = 8,
>     },
>     .valid = {
>--
>2.43.0


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH v2 1/2] intel_iommu: widen impl.min_access_size to 8 to fix MMIO abort
  2026-04-27  5:23   ` Duan, Zhenzhong
@ 2026-04-30  0:16     ` Junjie Cao
  2026-04-30  8:31       ` Duan, Zhenzhong
  0 siblings, 1 reply; 15+ messages in thread
From: Junjie Cao @ 2026-04-30  0:16 UTC (permalink / raw)
  To: zhenzhong.duan
  Cc: qemu-devel, mst, jasowang, yi.l.liu, clement.mathieu--drif,
	philmd, Junjie Cao

Hi Zhenzhong,

Thanks for the careful review.

You are right that the code path has changed.  With min_access_size=8
the handler always receives size=8, so a guest 4-byte write to a base
offset calls vtd_set_quad() with a zero-extended value instead of
vtd_set_long().  Two effects:


1. Triggers called unconditionally

CCMD_REG, IOTLB_REG, and FRCD_REG_0_2 originally guarded the
trigger on size==8.  In v2 all three are still no-ops:
vtd_handle_ccmd_write() and vtd_handle_iotlb_write() gate on
ICC/IVT (bit 63), which the zero-extended write just cleared
through the wmask.  For FRCD_REG_0_2, wmask is zero and the w1c
bit 63 does not fire (val bit 63 = 0), so the register is
unchanged.


2. Upper writable bits cleared by zero-extended vtd_set_quad()

Where the upper 32-bit wmask is non-zero, zero-extended val clears
those bits:

  Register       Offset  Upper wmask   Bits cleared
  CCMD_REG       0x28    0xe0000003    ICC, CIRG, 33:32
  IOTLB_REG      0xf8    0xb003ffff    IVT, IIRG, DID
  RTADDR_REG     0x20    0xffffffff    address 63:32
  IQA_REG        0x90    0xffffffff    address 63:32
  IVA_REG        0xf0    0xffffffff    address 63:32
  IRTA_REG       0xb8    0xffffffff    address 63:32

  (IQT_REG, FRCD_0_0, and FRCD_0_2 have upper wmask = 0 -- safe.)

This is safe because a guest must program the full register before
the value is consumed: CCMD/IOTLB fields are set before the action
bit (ICC/IVT) in the high-half write; RTADDR, IQA, and IRTA are
latched only when a GCMD enable bit (SRTP/QIE/SIRTP) fires; IVA is
read when IOTLB invalidation triggers via IVT.

However, a read-back between the low-half and high-half writes
would observe zeroed upper bits -- a real behavioral difference.


Two possible paths

(a) Keep v2 as-is, add comments documenting the safety argument
    above.

(b) Stay with min_access_size=4.  Simply remove all 25 asserts.
    21 sit at non-8-aligned offsets -- unreachable by any 8-byte
    guest access (alignment check rejects them).  The remaining 4
    at 8-aligned offsets (FECTL 0x38, IECTL 0xa0, IEADDR 0xa8,
    PECTL 0xe0) fall through to vtd_set_long() which implicitly
    truncates the value -- safe and no semantic change.  All
    existing size branches in 64-bit register pairs are preserved.

As I see it, both approaches are correct; (a) is simpler but
changes observable semantics in an edge case, (b) is conservative
with zero semantic change.  That said, I am happy to hear any other
suggestions.

Thanks,
Junjie


^ permalink raw reply	[flat|nested] 15+ messages in thread

* RE: [PATCH v2 1/2] intel_iommu: widen impl.min_access_size to 8 to fix MMIO abort
  2026-04-30  0:16     ` Junjie Cao
@ 2026-04-30  8:31       ` Duan, Zhenzhong
  2026-05-06  3:19         ` [PATCH v3 0/2] intel_iommu: fix guest-triggerable assert in MMIO handlers Junjie Cao
                           ` (2 more replies)
  0 siblings, 3 replies; 15+ messages in thread
From: Duan, Zhenzhong @ 2026-04-30  8:31 UTC (permalink / raw)
  To: Cao, Junjie
  Cc: qemu-devel@nongnu.org, mst@redhat.com, jasowang@redhat.com,
	Liu, Yi L, clement.mathieu--drif@bull.com, philmd@linaro.org



>-----Original Message-----
>From: Cao, Junjie <junjie.cao@intel.com>
>Subject: Re: [PATCH v2 1/2] intel_iommu: widen impl.min_access_size to 8 to fix
>MMIO abort
>
>Hi Zhenzhong,
>
>Thanks for the careful review.
>
>You are right that the code path has changed.  With min_access_size=8
>the handler always receives size=8, so a guest 4-byte write to a base
>offset calls vtd_set_quad() with a zero-extended value instead of
>vtd_set_long().  Two effects:
>
>
>1. Triggers called unconditionally
>
>CCMD_REG, IOTLB_REG, and FRCD_REG_0_2 originally guarded the
>trigger on size==8.  In v2 all three are still no-ops:
>vtd_handle_ccmd_write() and vtd_handle_iotlb_write() gate on
>ICC/IVT (bit 63), which the zero-extended write just cleared
>through the wmask.  For FRCD_REG_0_2, wmask is zero and the w1c
>bit 63 does not fire (val bit 63 = 0), so the register is
>unchanged.
>
>
>2. Upper writable bits cleared by zero-extended vtd_set_quad()
>
>Where the upper 32-bit wmask is non-zero, zero-extended val clears
>those bits:
>
>  Register       Offset  Upper wmask   Bits cleared
>  CCMD_REG       0x28    0xe0000003    ICC, CIRG, 33:32
>  IOTLB_REG      0xf8    0xb003ffff    IVT, IIRG, DID
>  RTADDR_REG     0x20    0xffffffff    address 63:32
>  IQA_REG        0x90    0xffffffff    address 63:32
>  IVA_REG        0xf0    0xffffffff    address 63:32
>  IRTA_REG       0xb8    0xffffffff    address 63:32
>
>  (IQT_REG, FRCD_0_0, and FRCD_0_2 have upper wmask = 0 -- safe.)
>
>This is safe because a guest must program the full register before
>the value is consumed: CCMD/IOTLB fields are set before the action
>bit (ICC/IVT) in the high-half write; RTADDR, IQA, and IRTA are
>latched only when a GCMD enable bit (SRTP/QIE/SIRTP) fires; IVA is
>read when IOTLB invalidation triggers via IVT.
>
>However, a read-back between the low-half and high-half writes
>would observe zeroed upper bits -- a real behavioral difference.
>
>
>Two possible paths
>
>(a) Keep v2 as-is, add comments documenting the safety argument
>    above.

This is risky, there is no guarantee that trigger bit is in upper register.

>
>(b) Stay with min_access_size=4.  Simply remove all 25 asserts.
>    21 sit at non-8-aligned offsets -- unreachable by any 8-byte
>    guest access (alignment check rejects them).  The remaining 4
>    at 8-aligned offsets (FECTL 0x38, IECTL 0xa0, IEADDR 0xa8,
>    PECTL 0xe0) fall through to vtd_set_long() which implicitly
>    truncates the value -- safe and no semantic change.  All
>    existing size branches in 64-bit register pairs are preserved.

This makes sense. I didn't find what happen about 8bytes access on
4bytes register in VTD spec, but I think at least we can print a
warning for the remaining 4 registers, instead of silently truncate.

Thanks
Zhenzhong

>
>As I see it, both approaches are correct; (a) is simpler but
>changes observable semantics in an edge case, (b) is conservative
>with zero semantic change.  That said, I am happy to hear any other
>suggestions.
>
>Thanks,
>Junjie


^ permalink raw reply	[flat|nested] 15+ messages in thread

* [PATCH v3 0/2] intel_iommu: fix guest-triggerable assert in MMIO handlers
  2026-04-30  8:31       ` Duan, Zhenzhong
@ 2026-05-06  3:19         ` Junjie Cao
  2026-05-06  3:19         ` [PATCH v3 1/2] intel_iommu: fix guest-triggerable abort on oversized MMIO access Junjie Cao
  2026-05-06  3:19         ` [PATCH v3 2/2] tests/qtest: add 8-byte MMIO access sweep for intel-iommu Junjie Cao
  2 siblings, 0 replies; 15+ messages in thread
From: Junjie Cao @ 2026-05-06  3:19 UTC (permalink / raw)
  To: qemu-devel
  Cc: junjie.cao, zhenzhong.duan, philmd, mst, jasowang, yi.l.liu,
	clement.mathieu--drif, marcel.apfelbaum, pbonzini,
	richard.henderson, farosas, lvivier

An 8-byte guest access to a 32-bit-only VT-d register hits
assert(size == 4) and aborts QEMU.  Found by generic-fuzz.

v1: https://lore.kernel.org/all/20260420170523.17908-1-junjie.cao@intel.com/
v2: https://lore.kernel.org/all/20260424201842.176953-1-junjie.cao@intel.com/

Changes in v3:
  - Drop v2's min_access_size=8 approach: per Zhenzhong, it
    silently zero-extends 4-byte guest writes, wiping upper
    wmask bits of 64-bit registers and firing triggers gated
    on size==8.
  - Keep min_access_size=4.  Remove the 25 assert(size == 4)
    sites: 21 are unreachable (non-8-aligned), the 4 reachable
    (FECTL 0x38, IECTL 0xa0, IEADDR 0xa8, PECTL 0xe0) fall
    through to vtd_set_long() and log a guest error.

Junjie Cao (2):
  intel_iommu: fix guest-triggerable abort on oversized MMIO access
  tests/qtest: add 8-byte MMIO access sweep for intel-iommu

 hw/i386/intel_iommu.c          | 41 +++++++++++++---------------------
 tests/qtest/intel-iommu-test.c | 30 +++++++++++++++++++++++++
 2 files changed, 46 insertions(+), 25 deletions(-)


base-commit: da6c4fe60fee30dd77267764d55b38af9cb89d4b
-- 
2.43.0



^ permalink raw reply	[flat|nested] 15+ messages in thread

* [PATCH v3 1/2] intel_iommu: fix guest-triggerable abort on oversized MMIO access
  2026-04-30  8:31       ` Duan, Zhenzhong
  2026-05-06  3:19         ` [PATCH v3 0/2] intel_iommu: fix guest-triggerable assert in MMIO handlers Junjie Cao
@ 2026-05-06  3:19         ` Junjie Cao
  2026-05-08  9:36           ` Yi Liu
  2026-05-06  3:19         ` [PATCH v3 2/2] tests/qtest: add 8-byte MMIO access sweep for intel-iommu Junjie Cao
  2 siblings, 1 reply; 15+ messages in thread
From: Junjie Cao @ 2026-05-06  3:19 UTC (permalink / raw)
  To: qemu-devel
  Cc: junjie.cao, zhenzhong.duan, philmd, mst, jasowang, yi.l.liu,
	clement.mathieu--drif, marcel.apfelbaum, pbonzini,
	richard.henderson, farosas, lvivier

An 8-byte guest access to a 32-bit-only VT-d register hit
assert(size == 4) and aborted QEMU.  Remove all 25 asserts.

21 sit at non-8-byte-aligned offsets and are rejected by
memory_region_access_valid() before reaching the handler -- dead
code, simply deleted.

The remaining 4 guard 8-byte-aligned 32-bit registers reachable
by a well-formed 8-byte access (FECTL 0x38, IECTL 0xa0, IEADDR
0xa8, PECTL 0xe0).  Writes fall through to vtd_set_long(), which
takes uint32_t and implicitly truncates, and log a guest error.
Reads fall through to the default vtd_get_quad() -- equivalent
to two 4-byte reads and therefore harmless, no warn needed.
min_access_size stays 4, so all size-based branches on 64-bit
register pairs are preserved.

Found by generic-fuzz (24 distinct crash seeds, all fixed).

Suggested-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
Signed-off-by: Junjie Cao <junjie.cao@intel.com>
---
 hw/i386/intel_iommu.c | 41 ++++++++++++++++-------------------------
 1 file changed, 16 insertions(+), 25 deletions(-)

diff --git a/hw/i386/intel_iommu.c b/hw/i386/intel_iommu.c
index f395fa248c..0fb89332f9 100644
--- a/hw/i386/intel_iommu.c
+++ b/hw/i386/intel_iommu.c
@@ -3713,7 +3713,6 @@ static uint64_t vtd_mem_read(void *opaque, hwaddr addr, unsigned size)
         break;
 
     case DMAR_RTADDR_REG_HI:
-        assert(size == 4);
         val = vtd_get_quad_raw(s, DMAR_RTADDR_REG) >> 32;
         break;
 
@@ -3728,12 +3727,10 @@ static uint64_t vtd_mem_read(void *opaque, hwaddr addr, unsigned size)
         break;
 
     case DMAR_IQA_REG_HI:
-        assert(size == 4);
         val = s->iq >> 32;
         break;
 
     case DMAR_PEUADDR_REG:
-        assert(size == 4);
         val = vtd_get_long_raw(s, DMAR_PEUADDR_REG);
         break;
 
@@ -3779,7 +3776,6 @@ static void vtd_mem_write(void *opaque, hwaddr addr,
         break;
 
     case DMAR_CCMD_REG_HI:
-        assert(size == 4);
         vtd_set_long(s, addr, val);
         vtd_handle_ccmd_write(s);
         break;
@@ -3795,13 +3791,11 @@ static void vtd_mem_write(void *opaque, hwaddr addr,
         break;
 
     case DMAR_IOTLB_REG_HI:
-        assert(size == 4);
         vtd_set_long(s, addr, val);
         vtd_handle_iotlb_write(s);
         break;
 
     case DMAR_PEUADDR_REG:
-        assert(size == 4);
         vtd_set_long(s, addr, val);
         break;
 
@@ -3815,27 +3809,27 @@ static void vtd_mem_write(void *opaque, hwaddr addr,
         break;
 
     case DMAR_IVA_REG_HI:
-        assert(size == 4);
         vtd_set_long(s, addr, val);
         break;
 
     /* Fault Status Register, 32-bit */
     case DMAR_FSTS_REG:
-        assert(size == 4);
         vtd_set_long(s, addr, val);
         vtd_handle_fsts_write(s);
         break;
 
     /* Fault Event Control Register, 32-bit */
     case DMAR_FECTL_REG:
-        assert(size == 4);
+        if (size != 4) {
+            error_report_once("%s: invalid %u-byte access to 32-bit reg "
+                              "addr=0x%" PRIx64, __func__, size, addr);
+        }
         vtd_set_long(s, addr, val);
         vtd_handle_fectl_write(s);
         break;
 
     /* Fault Event Data Register, 32-bit */
     case DMAR_FEDATA_REG:
-        assert(size == 4);
         vtd_set_long(s, addr, val);
         break;
 
@@ -3854,13 +3848,11 @@ static void vtd_mem_write(void *opaque, hwaddr addr,
 
     /* Fault Event Upper Address Register, 32-bit */
     case DMAR_FEUADDR_REG:
-        assert(size == 4);
         vtd_set_long(s, addr, val);
         break;
 
     /* Protected Memory Enable Register, 32-bit */
     case DMAR_PMEN_REG:
-        assert(size == 4);
         vtd_set_long(s, addr, val);
         break;
 
@@ -3874,7 +3866,6 @@ static void vtd_mem_write(void *opaque, hwaddr addr,
         break;
 
     case DMAR_RTADDR_REG_HI:
-        assert(size == 4);
         vtd_set_long(s, addr, val);
         break;
 
@@ -3889,7 +3880,6 @@ static void vtd_mem_write(void *opaque, hwaddr addr,
         break;
 
     case DMAR_IQT_REG_HI:
-        assert(size == 4);
         vtd_set_long(s, addr, val);
         /* 19:63 of IQT_REG is RsvdZ, do nothing here */
         break;
@@ -3905,39 +3895,41 @@ static void vtd_mem_write(void *opaque, hwaddr addr,
         break;
 
     case DMAR_IQA_REG_HI:
-        assert(size == 4);
         vtd_set_long(s, addr, val);
         break;
 
     /* Invalidation Completion Status Register, 32-bit */
     case DMAR_ICS_REG:
-        assert(size == 4);
         vtd_set_long(s, addr, val);
         vtd_handle_ics_write(s);
         break;
 
     /* Invalidation Event Control Register, 32-bit */
     case DMAR_IECTL_REG:
-        assert(size == 4);
+        if (size != 4) {
+            error_report_once("%s: invalid %u-byte access to 32-bit reg "
+                              "addr=0x%" PRIx64, __func__, size, addr);
+        }
         vtd_set_long(s, addr, val);
         vtd_handle_iectl_write(s);
         break;
 
     /* Invalidation Event Data Register, 32-bit */
     case DMAR_IEDATA_REG:
-        assert(size == 4);
         vtd_set_long(s, addr, val);
         break;
 
     /* Invalidation Event Address Register, 32-bit */
     case DMAR_IEADDR_REG:
-        assert(size == 4);
+        if (size != 4) {
+            error_report_once("%s: invalid %u-byte access to 32-bit reg "
+                              "addr=0x%" PRIx64, __func__, size, addr);
+        }
         vtd_set_long(s, addr, val);
         break;
 
     /* Invalidation Event Upper Address Register, 32-bit */
     case DMAR_IEUADDR_REG:
-        assert(size == 4);
         vtd_set_long(s, addr, val);
         break;
 
@@ -3951,7 +3943,6 @@ static void vtd_mem_write(void *opaque, hwaddr addr,
         break;
 
     case DMAR_FRCD_REG_0_1:
-        assert(size == 4);
         vtd_set_long(s, addr, val);
         break;
 
@@ -3966,7 +3957,6 @@ static void vtd_mem_write(void *opaque, hwaddr addr,
         break;
 
     case DMAR_FRCD_REG_0_3:
-        assert(size == 4);
         vtd_set_long(s, addr, val);
         /* May clear bit 127 (Fault), update PPF */
         vtd_update_fsts_ppf(s);
@@ -3981,18 +3971,19 @@ static void vtd_mem_write(void *opaque, hwaddr addr,
         break;
 
     case DMAR_IRTA_REG_HI:
-        assert(size == 4);
         vtd_set_long(s, addr, val);
         break;
 
     case DMAR_PRS_REG:
-        assert(size == 4);
         vtd_set_long(s, addr, val);
         vtd_handle_prs_write(s);
         break;
 
     case DMAR_PECTL_REG:
-        assert(size == 4);
+        if (size != 4) {
+            error_report_once("%s: invalid %u-byte access to 32-bit reg "
+                              "addr=0x%" PRIx64, __func__, size, addr);
+        }
         vtd_set_long(s, addr, val);
         vtd_handle_pectl_write(s);
         break;
-- 
2.43.0



^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v3 2/2] tests/qtest: add 8-byte MMIO access sweep for intel-iommu
  2026-04-30  8:31       ` Duan, Zhenzhong
  2026-05-06  3:19         ` [PATCH v3 0/2] intel_iommu: fix guest-triggerable assert in MMIO handlers Junjie Cao
  2026-05-06  3:19         ` [PATCH v3 1/2] intel_iommu: fix guest-triggerable abort on oversized MMIO access Junjie Cao
@ 2026-05-06  3:19         ` Junjie Cao
  2 siblings, 0 replies; 15+ messages in thread
From: Junjie Cao @ 2026-05-06  3:19 UTC (permalink / raw)
  To: qemu-devel
  Cc: junjie.cao, zhenzhong.duan, philmd, mst, jasowang, yi.l.liu,
	clement.mathieu--drif, marcel.apfelbaum, pbonzini,
	richard.henderson, farosas, lvivier

Sweep every 4-byte-aligned offset in the VT-d MMIO register space
with 8-byte reads and writes to verify that no register handler
aborts on an oversized access.

Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
Signed-off-by: Junjie Cao <junjie.cao@intel.com>
---
 tests/qtest/intel-iommu-test.c | 30 ++++++++++++++++++++++++++++++
 1 file changed, 30 insertions(+)

diff --git a/tests/qtest/intel-iommu-test.c b/tests/qtest/intel-iommu-test.c
index e5cc6acaf0..b1763ed294 100644
--- a/tests/qtest/intel-iommu-test.c
+++ b/tests/qtest/intel-iommu-test.c
@@ -17,11 +17,39 @@
 #define ECAP_STAGE_1_FIXED1   (VTD_ECAP_QI |  VTD_ECAP_IR | VTD_ECAP_IRO | \
                               VTD_ECAP_MHMV | VTD_ECAP_SMTS | VTD_ECAP_FSTS)
 
+static inline uint32_t vtd_reg_readl(QTestState *s, uint64_t offset)
+{
+    return qtest_readl(s, Q35_HOST_BRIDGE_IOMMU_ADDR + offset);
+}
+
 static inline uint64_t vtd_reg_readq(QTestState *s, uint64_t offset)
 {
     return qtest_readq(s, Q35_HOST_BRIDGE_IOMMU_ADDR + offset);
 }
 
+static inline void vtd_reg_writeq(QTestState *s, uint64_t offset,
+                                  uint64_t value)
+{
+    qtest_writeq(s, Q35_HOST_BRIDGE_IOMMU_ADDR + offset, value);
+}
+
+static void test_intel_iommu_8byte_access(void)
+{
+    QTestState *s;
+    uint64_t off;
+
+    s = qtest_init("-M q35 -device intel-iommu");
+
+    for (off = 0; off < DMAR_REG_SIZE; off += 4) {
+        vtd_reg_readq(s, off);
+        vtd_reg_writeq(s, off, 0);
+    }
+
+    g_assert_cmpuint(vtd_reg_readl(s, DMAR_VER_REG), !=, 0);
+
+    qtest_quit(s);
+}
+
 static void test_intel_iommu_stage_1(void)
 {
     uint8_t init_csr[DMAR_REG_SIZE];     /* register values */
@@ -58,6 +86,8 @@ static void test_intel_iommu_stage_1(void)
 int main(int argc, char **argv)
 {
     g_test_init(&argc, &argv, NULL);
+    qtest_add_func("/q35/intel-iommu/8byte-access",
+                   test_intel_iommu_8byte_access);
     qtest_add_func("/q35/intel-iommu/stage-1", test_intel_iommu_stage_1);
 
     return g_test_run();
-- 
2.43.0



^ permalink raw reply related	[flat|nested] 15+ messages in thread

* Re: [PATCH v3 1/2] intel_iommu: fix guest-triggerable abort on oversized MMIO access
  2026-05-06  3:19         ` [PATCH v3 1/2] intel_iommu: fix guest-triggerable abort on oversized MMIO access Junjie Cao
@ 2026-05-08  9:36           ` Yi Liu
  2026-05-11  5:41             ` Duan, Zhenzhong
  0 siblings, 1 reply; 15+ messages in thread
From: Yi Liu @ 2026-05-08  9:36 UTC (permalink / raw)
  To: Junjie Cao, qemu-devel
  Cc: zhenzhong.duan, philmd, mst, jasowang, clement.mathieu--drif,
	marcel.apfelbaum, pbonzini, richard.henderson, farosas, lvivier

On 5/6/26 11:19, Junjie Cao wrote:
> An 8-byte guest access to a 32-bit-only VT-d register hit
> assert(size == 4) and aborted QEMU.  Remove all 25 asserts.
> 
> 21 sit at non-8-byte-aligned offsets and are rejected by
> memory_region_access_valid() before reaching the handler -- dead
> code, simply deleted.
> 
> The remaining 4 guard 8-byte-aligned 32-bit registers reachable
> by a well-formed 8-byte access (FECTL 0x38, IECTL 0xa0, IEADDR
> 0xa8, PECTL 0xe0). 

could you share how you identify the 4 registers among all the
registers. The offset of such registers are multiple of 8? is it? Just
curious why only 4 registers.

> Writes fall through to vtd_set_long(), which
> takes uint32_t and implicitly truncates, and log a guest error.
> Reads fall through to the default vtd_get_quad() -- equivalent
> to two 4-byte reads and therefore harmless, no warn needed.
> min_access_size stays 4, so all size-based branches on 64-bit
> register pairs are preserved.
> 
> Found by generic-fuzz (24 distinct crash seeds, all fixed).
> 
> Suggested-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
> Signed-off-by: Junjie Cao <junjie.cao@intel.com>
> ---
>   hw/i386/intel_iommu.c | 41 ++++++++++++++++-------------------------
>   1 file changed, 16 insertions(+), 25 deletions(-)
> 
> diff --git a/hw/i386/intel_iommu.c b/hw/i386/intel_iommu.c
> index f395fa248c..0fb89332f9 100644
> --- a/hw/i386/intel_iommu.c
> +++ b/hw/i386/intel_iommu.c
> @@ -3713,7 +3713,6 @@ static uint64_t vtd_mem_read(void *opaque, hwaddr addr, unsigned size)
>           break;
>   
>       case DMAR_RTADDR_REG_HI:
> -        assert(size == 4);
>           val = vtd_get_quad_raw(s, DMAR_RTADDR_REG) >> 32;
>           break;
>   
> @@ -3728,12 +3727,10 @@ static uint64_t vtd_mem_read(void *opaque, hwaddr addr, unsigned size)
>           break;
>   
>       case DMAR_IQA_REG_HI:
> -        assert(size == 4);
>           val = s->iq >> 32;
>           break;
>   
>       case DMAR_PEUADDR_REG:
> -        assert(size == 4);
>           val = vtd_get_long_raw(s, DMAR_PEUADDR_REG);
>           break;
>   
> @@ -3779,7 +3776,6 @@ static void vtd_mem_write(void *opaque, hwaddr addr,
>           break;
>   
>       case DMAR_CCMD_REG_HI:
> -        assert(size == 4);
>           vtd_set_long(s, addr, val);
>           vtd_handle_ccmd_write(s);
>           break;
> @@ -3795,13 +3791,11 @@ static void vtd_mem_write(void *opaque, hwaddr addr,
>           break;
>   
>       case DMAR_IOTLB_REG_HI:
> -        assert(size == 4);
>           vtd_set_long(s, addr, val);
>           vtd_handle_iotlb_write(s);
>           break;
>   
>       case DMAR_PEUADDR_REG:
> -        assert(size == 4);
>           vtd_set_long(s, addr, val);
>           break;
>   
> @@ -3815,27 +3809,27 @@ static void vtd_mem_write(void *opaque, hwaddr addr,
>           break;
>   
>       case DMAR_IVA_REG_HI:
> -        assert(size == 4);
>           vtd_set_long(s, addr, val);
>           break;
>   
>       /* Fault Status Register, 32-bit */
>       case DMAR_FSTS_REG:
> -        assert(size == 4);
>           vtd_set_long(s, addr, val);
>           vtd_handle_fsts_write(s);
>           break;
>   
>       /* Fault Event Control Register, 32-bit */
>       case DMAR_FECTL_REG:
> -        assert(size == 4);
> +        if (size != 4) {
> +            error_report_once("%s: invalid %u-byte access to 32-bit reg "
> +                              "addr=0x%" PRIx64, __func__, size, addr);
> +        }

Since such writes are harmless, I don't think it is really helpful to
have this error log. Instead, we should have a comment here to avoid
somebody break it in future.

>           vtd_set_long(s, addr, val);
>           vtd_handle_fectl_write(s);
>           break;
>   
>       /* Fault Event Data Register, 32-bit */
>       case DMAR_FEDATA_REG:
> -        assert(size == 4);
>           vtd_set_long(s, addr, val);
>           break;
>   
> @@ -3854,13 +3848,11 @@ static void vtd_mem_write(void *opaque, hwaddr addr,
>   
>       /* Fault Event Upper Address Register, 32-bit */
>       case DMAR_FEUADDR_REG:
> -        assert(size == 4);
>           vtd_set_long(s, addr, val);
>           break;
>   
>       /* Protected Memory Enable Register, 32-bit */
>       case DMAR_PMEN_REG:
> -        assert(size == 4);
>           vtd_set_long(s, addr, val);
>           break;
>   
> @@ -3874,7 +3866,6 @@ static void vtd_mem_write(void *opaque, hwaddr addr,
>           break;
>   
>       case DMAR_RTADDR_REG_HI:
> -        assert(size == 4);
>           vtd_set_long(s, addr, val);
>           break;
>   
> @@ -3889,7 +3880,6 @@ static void vtd_mem_write(void *opaque, hwaddr addr,
>           break;
>   
>       case DMAR_IQT_REG_HI:
> -        assert(size == 4);
>           vtd_set_long(s, addr, val);
>           /* 19:63 of IQT_REG is RsvdZ, do nothing here */
>           break;
> @@ -3905,39 +3895,41 @@ static void vtd_mem_write(void *opaque, hwaddr addr,
>           break;
>   
>       case DMAR_IQA_REG_HI:
> -        assert(size == 4);
>           vtd_set_long(s, addr, val);
>           break;
>   
>       /* Invalidation Completion Status Register, 32-bit */
>       case DMAR_ICS_REG:
> -        assert(size == 4);
>           vtd_set_long(s, addr, val);
>           vtd_handle_ics_write(s);
>           break;
>   
>       /* Invalidation Event Control Register, 32-bit */
>       case DMAR_IECTL_REG:
> -        assert(size == 4);
> +        if (size != 4) {
> +            error_report_once("%s: invalid %u-byte access to 32-bit reg "
> +                              "addr=0x%" PRIx64, __func__, size, addr);
> +        }
>           vtd_set_long(s, addr, val);
>           vtd_handle_iectl_write(s);
>           break;
>   
>       /* Invalidation Event Data Register, 32-bit */
>       case DMAR_IEDATA_REG:
> -        assert(size == 4);
>           vtd_set_long(s, addr, val);
>           break;
>   
>       /* Invalidation Event Address Register, 32-bit */
>       case DMAR_IEADDR_REG:
> -        assert(size == 4);
> +        if (size != 4) {
> +            error_report_once("%s: invalid %u-byte access to 32-bit reg "
> +                              "addr=0x%" PRIx64, __func__, size, addr);
> +        }
>           vtd_set_long(s, addr, val);
>           break;
>   
>       /* Invalidation Event Upper Address Register, 32-bit */
>       case DMAR_IEUADDR_REG:
> -        assert(size == 4);
>           vtd_set_long(s, addr, val);
>           break;
>   
> @@ -3951,7 +3943,6 @@ static void vtd_mem_write(void *opaque, hwaddr addr,
>           break;
>   
>       case DMAR_FRCD_REG_0_1:
> -        assert(size == 4);
>           vtd_set_long(s, addr, val);
>           break;
>   
> @@ -3966,7 +3957,6 @@ static void vtd_mem_write(void *opaque, hwaddr addr,
>           break;
>   
>       case DMAR_FRCD_REG_0_3:
> -        assert(size == 4);
>           vtd_set_long(s, addr, val);
>           /* May clear bit 127 (Fault), update PPF */
>           vtd_update_fsts_ppf(s);
> @@ -3981,18 +3971,19 @@ static void vtd_mem_write(void *opaque, hwaddr addr,
>           break;
>   
>       case DMAR_IRTA_REG_HI:
> -        assert(size == 4);
>           vtd_set_long(s, addr, val);
>           break;
>   
>       case DMAR_PRS_REG:
> -        assert(size == 4);
>           vtd_set_long(s, addr, val);
>           vtd_handle_prs_write(s);
>           break;
>   
>       case DMAR_PECTL_REG:
> -        assert(size == 4);
> +        if (size != 4) {
> +            error_report_once("%s: invalid %u-byte access to 32-bit reg "
> +                              "addr=0x%" PRIx64, __func__, size, addr);
> +        }
>           vtd_set_long(s, addr, val);
>           vtd_handle_pectl_write(s);
>           break;



^ permalink raw reply	[flat|nested] 15+ messages in thread

* RE: [PATCH v3 1/2] intel_iommu: fix guest-triggerable abort on oversized MMIO access
  2026-05-08  9:36           ` Yi Liu
@ 2026-05-11  5:41             ` Duan, Zhenzhong
  2026-05-14 13:42               ` Junjie Cao
  0 siblings, 1 reply; 15+ messages in thread
From: Duan, Zhenzhong @ 2026-05-11  5:41 UTC (permalink / raw)
  To: Liu, Yi L, Cao, Junjie, qemu-devel@nongnu.org
  Cc: philmd@linaro.org, mst@redhat.com, jasowang@redhat.com,
	clement.mathieu--drif@bull.com, marcel.apfelbaum@gmail.com,
	pbonzini@redhat.com, richard.henderson@linaro.org,
	farosas@suse.de, lvivier@redhat.com



>-----Original Message-----
>From: Liu, Yi L <yi.l.liu@intel.com>
>Subject: Re: [PATCH v3 1/2] intel_iommu: fix guest-triggerable abort on oversized
>MMIO access
>
>On 5/6/26 11:19, Junjie Cao wrote:
>> An 8-byte guest access to a 32-bit-only VT-d register hit
>> assert(size == 4) and aborted QEMU.  Remove all 25 asserts.
>>
>> 21 sit at non-8-byte-aligned offsets and are rejected by
>> memory_region_access_valid() before reaching the handler -- dead
>> code, simply deleted.
>>
>> The remaining 4 guard 8-byte-aligned 32-bit registers reachable
>> by a well-formed 8-byte access (FECTL 0x38, IECTL 0xa0, IEADDR
>> 0xa8, PECTL 0xe0).
>
>could you share how you identify the 4 registers among all the
>registers. The offset of such registers are multiple of 8? is it? Just
>curious why only 4 registers.
>
>> Writes fall through to vtd_set_long(), which
>> takes uint32_t and implicitly truncates, and log a guest error.
>> Reads fall through to the default vtd_get_quad() -- equivalent
>> to two 4-byte reads and therefore harmless, no warn needed.
>> min_access_size stays 4, so all size-based branches on 64-bit
>> register pairs are preserved.
>>
>> Found by generic-fuzz (24 distinct crash seeds, all fixed).
>>
>> Suggested-by: Zhenzhong Duan <zhenzhong.duan@intel.com>
>> Signed-off-by: Junjie Cao <junjie.cao@intel.com>
>> ---
>>   hw/i386/intel_iommu.c | 41 ++++++++++++++++-------------------------
>>   1 file changed, 16 insertions(+), 25 deletions(-)
>>
>> diff --git a/hw/i386/intel_iommu.c b/hw/i386/intel_iommu.c
>> index f395fa248c..0fb89332f9 100644
>> --- a/hw/i386/intel_iommu.c
>> +++ b/hw/i386/intel_iommu.c
>> @@ -3713,7 +3713,6 @@ static uint64_t vtd_mem_read(void *opaque, hwaddr
>addr, unsigned size)
>>           break;
>>
>>       case DMAR_RTADDR_REG_HI:
>> -        assert(size == 4);
>>           val = vtd_get_quad_raw(s, DMAR_RTADDR_REG) >> 32;
>>           break;
>>
>> @@ -3728,12 +3727,10 @@ static uint64_t vtd_mem_read(void *opaque,
>hwaddr addr, unsigned size)
>>           break;
>>
>>       case DMAR_IQA_REG_HI:
>> -        assert(size == 4);
>>           val = s->iq >> 32;
>>           break;
>>
>>       case DMAR_PEUADDR_REG:
>> -        assert(size == 4);
>>           val = vtd_get_long_raw(s, DMAR_PEUADDR_REG);
>>           break;
>>
>> @@ -3779,7 +3776,6 @@ static void vtd_mem_write(void *opaque, hwaddr
>addr,
>>           break;
>>
>>       case DMAR_CCMD_REG_HI:
>> -        assert(size == 4);
>>           vtd_set_long(s, addr, val);
>>           vtd_handle_ccmd_write(s);
>>           break;
>> @@ -3795,13 +3791,11 @@ static void vtd_mem_write(void *opaque, hwaddr
>addr,
>>           break;
>>
>>       case DMAR_IOTLB_REG_HI:
>> -        assert(size == 4);
>>           vtd_set_long(s, addr, val);
>>           vtd_handle_iotlb_write(s);
>>           break;
>>
>>       case DMAR_PEUADDR_REG:
>> -        assert(size == 4);
>>           vtd_set_long(s, addr, val);
>>           break;
>>
>> @@ -3815,27 +3809,27 @@ static void vtd_mem_write(void *opaque, hwaddr
>addr,
>>           break;
>>
>>       case DMAR_IVA_REG_HI:
>> -        assert(size == 4);
>>           vtd_set_long(s, addr, val);
>>           break;
>>
>>       /* Fault Status Register, 32-bit */
>>       case DMAR_FSTS_REG:
>> -        assert(size == 4);
>>           vtd_set_long(s, addr, val);
>>           vtd_handle_fsts_write(s);
>>           break;
>>
>>       /* Fault Event Control Register, 32-bit */
>>       case DMAR_FECTL_REG:
>> -        assert(size == 4);
>> +        if (size != 4) {
>> +            error_report_once("%s: invalid %u-byte access to 32-bit reg "
>> +                              "addr=0x%" PRIx64, __func__, size, addr);
>> +        }
>
>Since such writes are harmless, I don't think it is really helpful to
>have this error log. Instead, we should have a comment here to avoid
>somebody break it in future.

Maybe to take it as guest error and log it:

qemu_log_mask(LOG_GUEST_ERROR,...),

then people could catch it and fix guest code?

Thanks
Zhenzhong


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH v3 1/2] intel_iommu: fix guest-triggerable abort on oversized MMIO access
  2026-05-14 13:42               ` Junjie Cao
@ 2026-05-14  6:59                 ` Yi Liu
  0 siblings, 0 replies; 15+ messages in thread
From: Yi Liu @ 2026-05-14  6:59 UTC (permalink / raw)
  To: Junjie Cao, zhenzhong.duan
  Cc: qemu-devel, philmd, mst, jasowang, clement.mathieu--drif,
	marcel.apfelbaum, pbonzini, richard.henderson, farosas, lvivier

On 5/14/26 21:42, Junjie Cao wrote:
> Hi Yi and Zhenzhong,
> 
> Thanks both.
> 
> On Yi's question -- I enumerated the 25 v2 assert sites and checked
> offset & 7.  21 (3 reads + 18 writes) sit at non-8-aligned offsets
> and are unreachable (memory_region_access_valid() rejects 8-byte
> access there) -- just deleted.  The remaining 4, all writes at
> 8-aligned offsets, are reachable: FECTL 0x38, IECTL 0xa0,
> IEADDR 0xa8, PECTL 0xe0.
> 
> (FEADDR 0x40 is 8-aligned too but already takes 8-byte writes via
> vtd_set_quad() for Xen; GCMD 0x18 and VER 0x0 never asserted on
> size and their default/long paths already truncate.)
> 
> On the log -- Yi, fair point that the truncation is harmless from
> QEMU's side.  The one thing I'd gently float: the VT-d spec is
> silent on oversized accesses to 32-bit registers, so the guest is
> in undefined territory, and Zhenzhong's LOG_GUEST_ERROR suggestion
> is free unless -d guest_errors is passed.  If that reasoning works
> for you, I'd combine both -- keep the log (with Zhenzhong's API)
> and add the comment you asked for, so future maintainers don't
> delete the block as "harmless":
> 
>      /*
>       * 32-bit register at an 8-byte-aligned offset: a well-formed
>       * 8-byte guest access reaches this handler.  vtd_set_long()
>       * takes uint32_t and truncates the high half -- undefined
>       * per the VT-d spec but harmless here.  Flag it so
>       * -d guest_errors surfaces the guest-side bug.
>       */
>      if (size != 4) {
>          qemu_log_mask(LOG_GUEST_ERROR,
>                        "%s: invalid %u-byte access to 32-bit reg "
>                        "addr=0x%" PRIx64 "\n", __func__, size, addr);
>      }
>      vtd_set_long(s, addr, val);
>      vtd_handle_*_write(s);
> 
> Happy either way -- if you'd still rather drop the log, I'll do
> that in v4.

LGTM. Please go ahead. :)

Regards,
Yi Liu


^ permalink raw reply	[flat|nested] 15+ messages in thread

* Re: [PATCH v3 1/2] intel_iommu: fix guest-triggerable abort on oversized MMIO access
  2026-05-11  5:41             ` Duan, Zhenzhong
@ 2026-05-14 13:42               ` Junjie Cao
  2026-05-14  6:59                 ` Yi Liu
  0 siblings, 1 reply; 15+ messages in thread
From: Junjie Cao @ 2026-05-14 13:42 UTC (permalink / raw)
  To: yi.l.liu, zhenzhong.duan
  Cc: qemu-devel, philmd, mst, jasowang, clement.mathieu--drif,
	marcel.apfelbaum, pbonzini, richard.henderson, farosas, lvivier,
	Junjie Cao

Hi Yi and Zhenzhong,

Thanks both.

On Yi's question -- I enumerated the 25 v2 assert sites and checked
offset & 7.  21 (3 reads + 18 writes) sit at non-8-aligned offsets
and are unreachable (memory_region_access_valid() rejects 8-byte
access there) -- just deleted.  The remaining 4, all writes at
8-aligned offsets, are reachable: FECTL 0x38, IECTL 0xa0,
IEADDR 0xa8, PECTL 0xe0.

(FEADDR 0x40 is 8-aligned too but already takes 8-byte writes via
vtd_set_quad() for Xen; GCMD 0x18 and VER 0x0 never asserted on
size and their default/long paths already truncate.)

On the log -- Yi, fair point that the truncation is harmless from
QEMU's side.  The one thing I'd gently float: the VT-d spec is
silent on oversized accesses to 32-bit registers, so the guest is
in undefined territory, and Zhenzhong's LOG_GUEST_ERROR suggestion
is free unless -d guest_errors is passed.  If that reasoning works
for you, I'd combine both -- keep the log (with Zhenzhong's API)
and add the comment you asked for, so future maintainers don't
delete the block as "harmless":

    /*
     * 32-bit register at an 8-byte-aligned offset: a well-formed
     * 8-byte guest access reaches this handler.  vtd_set_long()
     * takes uint32_t and truncates the high half -- undefined
     * per the VT-d spec but harmless here.  Flag it so
     * -d guest_errors surfaces the guest-side bug.
     */
    if (size != 4) {
        qemu_log_mask(LOG_GUEST_ERROR,
                      "%s: invalid %u-byte access to 32-bit reg "
                      "addr=0x%" PRIx64 "\n", __func__, size, addr);
    }
    vtd_set_long(s, addr, val);
    vtd_handle_*_write(s);

Happy either way -- if you'd still rather drop the log, I'll do
that in v4.

Thanks,
Junjie


^ permalink raw reply	[flat|nested] 15+ messages in thread

end of thread, other threads:[~2026-05-14  6:51 UTC | newest]

Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-24 20:18 [PATCH v2 0/2] intel_iommu: fix guest-triggerable assert in MMIO handlers Junjie Cao
2026-04-24 20:18 ` [PATCH v2 1/2] intel_iommu: widen impl.min_access_size to 8 to fix MMIO abort Junjie Cao
2026-04-24 13:58   ` Philippe Mathieu-Daudé
2026-04-27  1:24     ` Junjie Cao
2026-04-27  5:23   ` Duan, Zhenzhong
2026-04-30  0:16     ` Junjie Cao
2026-04-30  8:31       ` Duan, Zhenzhong
2026-05-06  3:19         ` [PATCH v3 0/2] intel_iommu: fix guest-triggerable assert in MMIO handlers Junjie Cao
2026-05-06  3:19         ` [PATCH v3 1/2] intel_iommu: fix guest-triggerable abort on oversized MMIO access Junjie Cao
2026-05-08  9:36           ` Yi Liu
2026-05-11  5:41             ` Duan, Zhenzhong
2026-05-14 13:42               ` Junjie Cao
2026-05-14  6:59                 ` Yi Liu
2026-05-06  3:19         ` [PATCH v3 2/2] tests/qtest: add 8-byte MMIO access sweep for intel-iommu Junjie Cao
2026-04-24 20:18 ` [PATCH v2 " Junjie Cao

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.