amd/virtio: fix bo use-after-free race condition in amdvgpu_bo_free

In amdvgpu_bo_free(), when the reference count drops to 0, vdrm_flush()
is called before removing the bo from the handle_to_vbo hash table.

Since vdrm_flush() is a time-consuming operation and is executed outside
of the handle_to_vbo_mutex lock, another thread calling amdvgpu_bo_import()
can concurrently find this bo in the hash table, increment its refcount,
and attempt to use it. Once vdrm_flush() finishes, amdvgpu_bo_free()
proceeds to remove the bo and call free(), leaving the importing thread
with a dangling pointer, which leads to a use-after-free or double free
crash.

To fix this race condition, we must remove the bo from the hash table
under the lock first. After the bo is safely unlinked and the lock is
released, we can then perform the time-consuming vdrm_flush() and the
actual memory release.

Signed-off-by: zhaqian <zhaqian@amd.com>
Part-of: <https://gitlab.freedesktop.org/mesa/mesa/-/merge_requests/41146>
This commit is contained in:
xueyuli2 2025-08-07 09:27:50 +08:00 committed by Marge Bot
parent 9960637b26
commit da7ed1c576

View file

@ -1,5 +1,5 @@
/*
* Copyright 2024 Advanced Micro Devices, Inc.
* Copyright 2026 Advanced Micro Devices, Inc.
*
* SPDX-License-Identifier: MIT
*/
@ -125,27 +125,29 @@ int amdvgpu_bo_export(amdvgpu_device_handle dev, amdvgpu_bo_handle bo,
}
int amdvgpu_bo_free(amdvgpu_device_handle dev, struct amdvgpu_bo *bo) {
simple_mtx_lock(&dev->handle_to_vbo_mutex);
int refcnt = p_atomic_dec_return(&bo->refcount);
if (refcnt == 0) {
/* Flush pending ops. */
vdrm_flush(dev->vdev);
/* Remove it from the bo table. */
if (bo->host_blob->handle > 0) {
simple_mtx_lock(&dev->handle_to_vbo_mutex);
void *entry = _mesa_hash_table_u64_search(dev->handle_to_vbo, bo->host_blob->handle);
if (entry) {
/* entry can be NULL for the shmem buffer. */
_mesa_hash_table_u64_remove(dev->handle_to_vbo, bo->host_blob->handle);
}
simple_mtx_unlock(&dev->handle_to_vbo_mutex);
}
simple_mtx_unlock(&dev->handle_to_vbo_mutex);
/* Flush pending ops. */
vdrm_flush(dev->vdev);
if (bo->host_blob)
destroy_host_blob(dev, bo->host_blob);
free(bo);
} else {
simple_mtx_unlock(&dev->handle_to_vbo_mutex);
}
return 0;