* 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.