@samitouri / QOSamiQemu / commits / a35c5755d9

intel_iommu: fix guest-triggerable abort on oversized MMIO access

An 8-byte guest access to a 32-bit-only VT-d register hit assert(size == 4) and aborted QEMU. Remove all 25 asserts. All 3 read-side and 18 of 22 write-side asserts are at non-8-aligned offsets (unreachable, rejected by memory_region_access_valid()) -- simply deleted. The remaining 4, all writes at 8-aligned offsets, are reachable: FECTL 0x38, IECTL 0xa0, IEADDR 0xa8, PECTL 0xe0. Truncating the high half via vtd_set_long() matches prior behavior; log under -d guest_errors since the VT-d spec is silent on oversized accesses to 32-bit registers, and add a comment so future maintainers don't delete the check as "harmless". 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> Reviewed-by: Michael S. Tsirkin <mst@redhat.com> Reviewed-by: Yi Liu <yi.l.liu@intel.com> Reviewed-by: Zhenzhong Duan <zhenzhong.duan@intel.com> Signed-off-by: Michael S. Tsirkin <mst@redhat.com> Message-Id: <20260514180703.85686-2-junjie.cao@intel.com>

Junjie Cao committed May 15, 2026 at 02:07 UTC a35c5755d9422f5f7b289ad7389efc8269ddfacd
1 file changed +49 -25
hw/i386/intel_iommu.c
+49 -25
@@ -21,6 +21,7 @@
21
22 #include "qemu/osdep.h"
23 #include "qemu/error-report.h"
24 +#include "qemu/log.h"
25 #include "qemu/main-loop.h"
26 #include "qapi/error.h"
27 #include "hw/core/sysbus.h"
@@ -3659,7 +3660,6 @@ static uint64_t vtd_mem_read(void *opaque, hwaddr addr, unsigned size)
3660 break;
3661
3662 case DMAR_RTADDR_REG_HI:
3662 - assert(size == 4);
3663 val = vtd_get_quad_raw(s, DMAR_RTADDR_REG) >> 32;
3664 break;
3665
@@ -3674,12 +3674,10 @@ static uint64_t vtd_mem_read(void *opaque, hwaddr addr, unsigned size)
3674 break;
3675
3676 case DMAR_IQA_REG_HI:
3677 - assert(size == 4);
3677 val = s->iq >> 32;
3678 break;
3679
3680 case DMAR_PEUADDR_REG:
3682 - assert(size == 4);
3681 val = vtd_get_long_raw(s, DMAR_PEUADDR_REG);
3682 break;
3683
@@ -3725,7 +3723,6 @@ static void vtd_mem_write(void *opaque, hwaddr addr,
3723 break;
3724
3725 case DMAR_CCMD_REG_HI:
3728 - assert(size == 4);
3726 vtd_set_long(s, addr, val);
3727 vtd_handle_ccmd_write(s);
3728 break;
@@ -3741,13 +3738,11 @@ static void vtd_mem_write(void *opaque, hwaddr addr,
3738 break;
3739
3740 case DMAR_IOTLB_REG_HI:
3744 - assert(size == 4);
3741 vtd_set_long(s, addr, val);
3742 vtd_handle_iotlb_write(s);
3743 break;
3744
3745 case DMAR_PEUADDR_REG:
3750 - assert(size == 4);
3746 vtd_set_long(s, addr, val);
3747 break;
3748
@@ -3761,27 +3756,35 @@ static void vtd_mem_write(void *opaque, hwaddr addr,
3756 break;
3757
3758 case DMAR_IVA_REG_HI:
3764 - assert(size == 4);
3759 vtd_set_long(s, addr, val);
3760 break;
3761
3762 /* Fault Status Register, 32-bit */
3763 case DMAR_FSTS_REG:
3770 - assert(size == 4);
3764 vtd_set_long(s, addr, val);
3765 vtd_handle_fsts_write(s);
3766 break;
3767
3768 /* Fault Event Control Register, 32-bit */
3769 case DMAR_FECTL_REG:
3777 - assert(size == 4);
3770 + /*
3771 + * 32-bit register at an 8-byte-aligned offset: a well-formed
3772 + * 8-byte guest access reaches this handler. vtd_set_long()
3773 + * takes uint32_t and truncates the high half -- undefined per
3774 + * the VT-d spec but harmless here. Flag it under
3775 + * -d guest_errors so the guest-side bug surfaces.
3776 + */
3777 + if (size != 4) {
3778 + qemu_log_mask(LOG_GUEST_ERROR,
3779 + "%s: invalid %u-byte access to 32-bit reg "
3780 + "addr=0x%" PRIx64 "\n", __func__, size, addr);
3781 + }
3782 vtd_set_long(s, addr, val);
3783 vtd_handle_fectl_write(s);
3784 break;
3785
3786 /* Fault Event Data Register, 32-bit */
3787 case DMAR_FEDATA_REG:
3784 - assert(size == 4);
3788 vtd_set_long(s, addr, val);
3789 break;
3790
@@ -3800,13 +3803,11 @@ static void vtd_mem_write(void *opaque, hwaddr addr,
3803
3804 /* Fault Event Upper Address Register, 32-bit */
3805 case DMAR_FEUADDR_REG:
3803 - assert(size == 4);
3806 vtd_set_long(s, addr, val);
3807 break;
3808
3809 /* Protected Memory Enable Register, 32-bit */
3810 case DMAR_PMEN_REG:
3809 - assert(size == 4);
3811 vtd_set_long(s, addr, val);
3812 break;
3813
@@ -3820,7 +3821,6 @@ static void vtd_mem_write(void *opaque, hwaddr addr,
3821 break;
3822
3823 case DMAR_RTADDR_REG_HI:
3823 - assert(size == 4);
3824 vtd_set_long(s, addr, val);
3825 break;
3826
@@ -3835,7 +3835,6 @@ static void vtd_mem_write(void *opaque, hwaddr addr,
3835 break;
3836
3837 case DMAR_IQT_REG_HI:
3838 - assert(size == 4);
3838 vtd_set_long(s, addr, val);
3839 /* 19:63 of IQT_REG is RsvdZ, do nothing here */
3840 break;
@@ -3851,39 +3850,57 @@ static void vtd_mem_write(void *opaque, hwaddr addr,
3850 break;
3851
3852 case DMAR_IQA_REG_HI:
3854 - assert(size == 4);
3853 vtd_set_long(s, addr, val);
3854 break;
3855
3856 /* Invalidation Completion Status Register, 32-bit */
3857 case DMAR_ICS_REG:
3860 - assert(size == 4);
3858 vtd_set_long(s, addr, val);
3859 vtd_handle_ics_write(s);
3860 break;
3861
3862 /* Invalidation Event Control Register, 32-bit */
3863 case DMAR_IECTL_REG:
3867 - assert(size == 4);
3864 + /*
3865 + * 32-bit register at an 8-byte-aligned offset: a well-formed
3866 + * 8-byte guest access reaches this handler. vtd_set_long()
3867 + * takes uint32_t and truncates the high half -- undefined per
3868 + * the VT-d spec but harmless here. Flag it under
3869 + * -d guest_errors so the guest-side bug surfaces.
3870 + */
3871 + if (size != 4) {
3872 + qemu_log_mask(LOG_GUEST_ERROR,
3873 + "%s: invalid %u-byte access to 32-bit reg "
3874 + "addr=0x%" PRIx64 "\n", __func__, size, addr);
3875 + }
3876 vtd_set_long(s, addr, val);
3877 vtd_handle_iectl_write(s);
3878 break;
3879
3880 /* Invalidation Event Data Register, 32-bit */
3881 case DMAR_IEDATA_REG:
3874 - assert(size == 4);
3882 vtd_set_long(s, addr, val);
3883 break;
3884
3885 /* Invalidation Event Address Register, 32-bit */
3886 case DMAR_IEADDR_REG:
3880 - assert(size == 4);
3887 + /*
3888 + * 32-bit register at an 8-byte-aligned offset: a well-formed
3889 + * 8-byte guest access reaches this handler. vtd_set_long()
3890 + * takes uint32_t and truncates the high half -- undefined per
3891 + * the VT-d spec but harmless here. Flag it under
3892 + * -d guest_errors so the guest-side bug surfaces.
3893 + */
3894 + if (size != 4) {
3895 + qemu_log_mask(LOG_GUEST_ERROR,
3896 + "%s: invalid %u-byte access to 32-bit reg "
3897 + "addr=0x%" PRIx64 "\n", __func__, size, addr);
3898 + }
3899 vtd_set_long(s, addr, val);
3900 break;
3901
3902 /* Invalidation Event Upper Address Register, 32-bit */
3903 case DMAR_IEUADDR_REG:
3886 - assert(size == 4);
3904 vtd_set_long(s, addr, val);
3905 break;
3906
@@ -3897,7 +3914,6 @@ static void vtd_mem_write(void *opaque, hwaddr addr,
3914 break;
3915
3916 case DMAR_FRCD_REG_0_1:
3900 - assert(size == 4);
3917 vtd_set_long(s, addr, val);
3918 break;
3919
@@ -3912,7 +3928,6 @@ static void vtd_mem_write(void *opaque, hwaddr addr,
3928 break;
3929
3930 case DMAR_FRCD_REG_0_3:
3915 - assert(size == 4);
3931 vtd_set_long(s, addr, val);
3932 /* May clear bit 127 (Fault), update PPF */
3933 vtd_update_fsts_ppf(s);
@@ -3927,18 +3942,27 @@ static void vtd_mem_write(void *opaque, hwaddr addr,
3942 break;
3943
3944 case DMAR_IRTA_REG_HI:
3930 - assert(size == 4);
3945 vtd_set_long(s, addr, val);
3946 break;
3947
3948 case DMAR_PRS_REG:
3935 - assert(size == 4);
3949 vtd_set_long(s, addr, val);
3950 vtd_handle_prs_write(s);
3951 break;
3952
3953 case DMAR_PECTL_REG:
3941 - assert(size == 4);
3954 + /*
3955 + * 32-bit register at an 8-byte-aligned offset: a well-formed
3956 + * 8-byte guest access reaches this handler. vtd_set_long()
3957 + * takes uint32_t and truncates the high half -- undefined per
3958 + * the VT-d spec but harmless here. Flag it under
3959 + * -d guest_errors so the guest-side bug surfaces.
3960 + */
3961 + if (size != 4) {
3962 + qemu_log_mask(LOG_GUEST_ERROR,
3963 + "%s: invalid %u-byte access to 32-bit reg "
3964 + "addr=0x%" PRIx64 "\n", __func__, size, addr);
3965 + }
3966 vtd_set_long(s, addr, val);
3967 vtd_handle_pectl_write(s);
3968 break;