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