> -----Original Message-----
> From: Matt Ochs <[email protected]>
> Sent: 03 September 2026 19:47
> To: [email protected]
> Cc: [email protected]; Peter Maydell <[email protected]>; Eric
> Auger <[email protected]>; Nicolin Chen <[email protected]>;
> Shameer Kolothum Thodi <[email protected]>; qemu-
> [email protected]
> Subject: [PATCH] hw/arm/tegra241-cmdqv: Keep VINTF page0 region alive
> 
> With CMDQV enabled, resetting a guest after it enables VINTF invokes
> the VINTF page0 unmap path. The resulting crash is intermittent and
> has been observed on the RCU reclaim thread as:
> 
>   reboot: Restarting system
>   double free or corruption (!prev)
>   ...
>   #5 address_space_dispatch_free
>   #6 flatview_destroy
>   #7 call_rcu_thread
> 
> FlatViews retain raw MemoryRegion pointers and release their references
> asynchronously through RCU. The VINTF page0 unmap path removes the
> subregion and immediately unparents and frees it. An old FlatView can
> then access the freed region during teardown, resulting in a
> use-after-free and heap corruption.
> 
> Embed the VINTF page0 MemoryRegion in Tegra241CMDQV and add it only
> once. Use memory_region_set_enabled() as the guest enables and disables
> VINTF. New FlatViews omit the disabled region, while old views continue
> to reference valid storage.
> 
> Fixes: 5965b81ce283 ("hw/arm/tegra241-cmdqv: Use mmap'd host VINTF
> page0 for virtual VINTF page0")
> Signed-off-by: Matthew R. Ochs <[email protected]>
> ---
> Reproducer:
> 
> Start an Arm virt guest with one passed-through device behind an
> accelerated SMMUv3 configured with cmdqv=on. Add an HMP monitor
> socket:
> 
>   -monitor unix:/tmp/qmon.sock,server,nowait
> 
> The failure can be made reliable without an ASan build by starting QEMU
> with glibc freed-memory poisoning enabled:
> 
>   GLIBC_TUNABLES=glibc.malloc.tcache_count=0 \
>   MALLOC_PERTURB_=165 \
>   MALLOC_CHECK_=3 \
>   qemu-system-aarch64 <options>
> 
> Wait until "info mtree" shows the VINTF page0 region, then reset the
> guest through the monitor:
> 
>   printf 'system_reset\n' | timeout 5 nc -N -U /tmp/qmon.sock
> 
> With the unpatched binary, QEMU crashed on the first reset with SIGSEGV.
> The core showed object_unref() called from address_space_dispatch_free()
> with the object pointer set to 0xa5a5a5a5a5a5a5a5.
> 
> Testing:
> 
>   Unpatched, one CMDQV instance: SIGSEGV on first reset
>   Patched, one CMDQV instance:   100/100 resets completed successfully

Thanks for the test results. The patch looks good to me.
One nit below.

>  hw/arm/tegra241-cmdqv.c | 22 ++++++++++++----------
>  hw/arm/tegra241-cmdqv.h |  3 ++-
>  2 files changed, 14 insertions(+), 11 deletions(-)
> 
> diff --git a/hw/arm/tegra241-cmdqv.c b/hw/arm/tegra241-cmdqv.c
> index 273633e62937..29123f6267fc 100644
> --- a/hw/arm/tegra241-cmdqv.c
> +++ b/hw/arm/tegra241-cmdqv.c
> @@ -131,35 +131,37 @@ static void
> tegra241_cmdqv_reset_vcmdq_cache(Tegra241CMDQV *cmdqv, int index)
> 
>  static void tegra241_cmdqv_guest_unmap_vintf_page0(Tegra241CMDQV
> *cmdqv)
>  {
> -    if (!cmdqv->mr_vintf_page0) {
> +    if (!cmdqv->mr_vintf_page0_initialized) {
>          return;
>      }
> 
> -    memory_region_del_subregion(&cmdqv->mmio_cmdqv, cmdqv-
> >mr_vintf_page0);
> -    object_unparent(OBJECT(cmdqv->mr_vintf_page0));
> -    g_free(cmdqv->mr_vintf_page0);
> -    cmdqv->mr_vintf_page0 = NULL;
> +    /*
> +     * Keep the region parented: old FlatViews can retain a pointer to it
> +     * until their RCU callbacks have run.
> +     */
> +    memory_region_set_enabled(&cmdqv->mr_vintf_page0, false);
>  }
> 
>  static void tegra241_cmdqv_guest_map_vintf_page0(Tegra241CMDQV
> *cmdqv)
>  {
>      char *name;
> 
> -    if (cmdqv->mr_vintf_page0) {
> +    if (cmdqv->mr_vintf_page0_initialized) {
> +        memory_region_set_enabled(&cmdqv->mr_vintf_page0, true);
>          return;
>      }
> 
>      name = g_strdup_printf("%s vintf-page0",
>                             memory_region_name(&cmdqv->mmio_cmdqv));
> -    cmdqv->mr_vintf_page0 = g_malloc0(sizeof(*cmdqv->mr_vintf_page0));
> -    memory_region_init_ram_device_ptr(cmdqv->mr_vintf_page0,
> +    memory_region_init_ram_device_ptr(&cmdqv->mr_vintf_page0,
>                                        
> memory_region_owner(&cmdqv->mmio_cmdqv),
>                                        name, VINTF_PAGE_SIZE,
>                                        cmdqv->vintf_page0);
> -    memory_region_set_skip_iommu_map(cmdqv->mr_vintf_page0, true);
> +    memory_region_set_skip_iommu_map(&cmdqv->mr_vintf_page0, true);
>      memory_region_add_subregion_overlap(&cmdqv->mmio_cmdqv,
>                                          CMDQV_VINTF_PAGE0_BASE,
> -                                        cmdqv->mr_vintf_page0, 1);
> +                                        &cmdqv->mr_vintf_page0, 1);
> +    cmdqv->mr_vintf_page0_initialized = true;

Do we really need the bool mr_vintf_page0_initialized or can use
memory_region_is_mapped() instead?
Since the region stays a subregion while disabled, memory_region_is_mapped()
still returns true, so it should work for the "initialized" check, I think.

Either way,

Reviewed-by: Shameer Kolothum <[email protected]>

Thanks,
Shameer


Reply via email to