On Fri, 11 Sept 2026 at 20:04, Matthew R. Ochs <[email protected]> wrote:
>
> 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.
>
> Keeping the region initialized across VINTF disable and reset is safe
> because the vIOMMU association and its VINTF page0 mmap remain stable
> once the guest CMDQ has been initialized. QEMU blocks hot-unplug of the
> device that established the association, so later hot-adds reuse it
> instead of associating the initialized guest CMDQ with a different host
> SMMUv3. The CMDQV free_viommu path is therefore only invoked while
> unwinding initial allocation, before guest CMDQ initialization.
>
> Fixes: 5965b81ce283 ("hw/arm/tegra241-cmdqv: Use mmap'd host VINTF page0 for 
> virtual VINTF page0")
> Reviewed-by: Shameer Kolothum <[email protected]>
> Signed-off-by: Matthew R. Ochs <[email protected]>
> ---
> v2:
> - Use memory_region_is_mapped() instead of an explicit initialization
>   flag, as suggested by Shameer.
> - Link to v1: 
> https://lore.kernel.org/all/[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
>   Patched, four CMDQV instances: 100/100 resets completed successfully
>
>  hw/arm/tegra241-cmdqv.c | 21 +++++++++++----------
>  hw/arm/tegra241-cmdqv.h |  2 +-
>  2 files changed, 12 insertions(+), 11 deletions(-)


>  static void tegra241_cmdqv_guest_map_vintf_page0(Tegra241CMDQV *cmdqv)
>  {
>      char *name;
>
> -    if (cmdqv->mr_vintf_page0) {
> +    if (memory_region_is_mapped(&cmdqv->mr_vintf_page0)) {

Here we call memory_region_mapped() on mr_vintf_page0...

> +        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);

...but we don't actually initialize the MemoryRegion until here.

I don't think you should assume that memory_region_* functions
will do anything sensible on a zeroed-out lump of memory.

thanks
-- PMM

Reply via email to