On Thu, Sep 10, 2026 at 12:52 PM Michal TOMA <[email protected]> wrote: > > drm_gem_prime_import_dev() attaches to a foreign dma-buf, takes a > reference with get_dma_buf(), maps the attachment and, once > vmw_prime_import_sg_table() has created the TTM buffer object, stores > the attachment in obj->import_attach. Drivers that import this way have > to undo it by calling drm_prime_gem_destroy() when the object is freed. > > vmwgfx never does. The TTM destroy callback, vmw_bo_free(), only calls > drm_gem_object_release() and kfree(). For every imported dma-buf, the > sg mapping, the attachment and the dma-buf reference are leaked when > the last GEM reference goes away, and the exporter's backing pages stay > pinned until reboot. > > Any process that can open the vmwgfx render node can hit this by > importing a dma-buf from another exporter, for example with > DRM_IOCTL_PRIME_FD_TO_HANDLE. It showed up as a memory drain on a > VirtualBox VMSVGA guest running a Plasma 6.7 Wayland session with the > host's 3D acceleration off. KWin 6.7 wraps wl_shm client buffers in > udmabufs and imports them through EGL on llvmpipe, and it keeps many of > those imports referenced while it runs, which is a separate userspace > problem. Restarting the compositor did not give the memory back, > though: with about 1.5 GiB of client buffers imported, killing KWin > left 211 udmabufs in /sys/kernel/debug/dma_buf/bufinfo with a refcount > of 1, still attached to the vmwgfx device, after every userspace > reference was gone. Their pages are no longer mapped or in any page > cache, so reclaim and swap cannot free them. > > Call drm_prime_gem_destroy() for imported objects before > drm_gem_object_release(), as amdgpu, radeon and nouveau do in their TTM > destroy callbacks, and include <drm/drm_prime.h> for it. The check is > safe on the import error path: drm_gem_prime_import_dev() only sets > obj->import_attach after gem_prime_import_sg_table() succeeded, so a > buffer object destroyed before that point is not unmapped, detached or > put a second time. ttm_bo_release() tears down the TTM backing and > drops the reservation lock before calling the destroy callback, which > fits the _unlocked unmap done by drm_prime_gem_destroy(). > > The leak was found by walking /proc/kpageflags, which attributed the > missing memory to orphaned shmem pages. Those matched the udmabuf > objects in dma_buf/bufinfo, and the remaining reference was traced to > the missing PRIME teardown. It was confirmed without a compositor using > a small reproducer: create a memfd, wrap it with UDMABUF_CREATE, import > it with DRM_IOCTL_PRIME_FD_TO_HANDLE on the vmwgfx render node, close > the GEM handle and every file descriptor, then check bufinfo. > > Build tested on drm-misc-fixes (4600b4d1a9ee) with the openSUSE 7.2.3 > config and W=1: drivers/gpu/drm/vmwgfx/ builds without warnings before > and after the change, and checkpatch.pl --strict is clean. Runtime > tested on a VirtualBox 7.2 VMSVGA guest with kernel 7.2.3, whose vmwgfx > sources for the files involved are identical to drm-misc-fixes, using a > vmwgfx.ko built from them with this change. With the reproducer, all 8 > imported udmabufs stayed pinned without the change and none with it, > with 3D acceleration both on and off. With KWin on llvmpipe, killing > the compositor now released 84 of 92 udmabufs (610 MiB) within 10 > seconds, where the same test on the unpatched driver released none. > > Fixes: b32233acceff ("drm/vmwgfx: Fix prime import/export") > Cc: [email protected] # v6.6+ > Assisted-by: LLM > Signed-off-by: Michal TOMA <[email protected]> > --- > Reproducer (results and environment are in the cover letter). Compare > the 4 MiB udmabuf entries in /sys/kernel/debug/dma_buf/bufinfo before > and after: without this patch all 8 remain, with count 1 and still > attached to the vmwgfx device. > > #!/usr/bin/env python3 > import fcntl, os, struct > N, SIZE = 8, 4 * 1024 * 1024 > UDMABUF_CREATE = 0x40187542 # _IOW('u', 0x42, struct udmabuf_create) > PRIME_FD_TO_HANDLE = 0xC00C642E # DRM_IOWR(0x2e, struct drm_prime_handle) > GEM_CLOSE = 0x40086409 # DRM_IOW(0x09, struct drm_gem_close) > render = os.open('/dev/dri/renderD128', os.O_RDWR | os.O_CLOEXEC) > udm = os.open('/dev/udmabuf', os.O_RDWR | os.O_CLOEXEC) > for i in range(N): > mfd = os.memfd_create(f'prime-leak-{i}', os.MFD_ALLOW_SEALING) > os.ftruncate(mfd, SIZE) > fcntl.fcntl(mfd, fcntl.F_ADD_SEALS, fcntl.F_SEAL_SHRINK) > create = bytearray(struct.pack('IIQQ', mfd, 1, 0, SIZE)) > dfd = fcntl.ioctl(udm, UDMABUF_CREATE, create) > ph = bytearray(struct.pack('IIi', 0, 0, dfd)) > fcntl.ioctl(render, PRIME_FD_TO_HANDLE, ph) > handle = struct.unpack('IIi', ph)[0] > fcntl.ioctl(render, GEM_CLOSE, bytearray(struct.pack('II', handle, 0))) > os.close(dfd) > os.close(mfd) > os.close(udm) > os.close(render) > > The same loop without the PRIME_FD_TO_HANDLE/GEM_CLOSE step frees > every udmabuf, so the leak is specific to the vmwgfx import. > > drivers/gpu/drm/vmwgfx/vmwgfx_bo.c | 3 +++ > 1 file changed, 3 insertions(+) > > diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_bo.c > b/drivers/gpu/drm/vmwgfx/vmwgfx_bo.c > index 9c7a73c0b..56bc94edc 100644 > --- a/drivers/gpu/drm/vmwgfx/vmwgfx_bo.c > +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_bo.c > @@ -30,6 +30,7 @@ > #include "vmwgfx_drv.h" > #include "vmwgfx_resource_priv.h" > > +#include <drm/drm_prime.h> > #include <drm/ttm/ttm_placement.h> > > /** > @@ -69,6 +70,8 @@ static void vmw_bo_free(struct ttm_buffer_object *bo) > vmw_surface_unreference(&vbo->dumb_surface); > } > WARN_ON(!RB_EMPTY_ROOT(&vbo->res_tree)); > + if (drm_gem_is_imported(&vbo->tbo.base)) > + drm_prime_gem_destroy(&vbo->tbo.base, vbo->tbo.sg); > drm_gem_object_release(&vbo->tbo.base); > WARN_ON(vbo->dirty); > kfree(vbo); > -- > 2.55.0 >
This looks right to me, but it must not be backported on its own: without patch 1 and the unbind fix above, releasing the import turns the leak into a crash. Please spell that dependency out here, not just in patch 1. z
smime.p7s
Description: S/MIME Cryptographic Signature
