mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
From: Aldo Ariel Panzardo <qwe.aldo@gmail.com>
To: maaz.mombasawala@broadcom.com, zack.rusin@broadcom.com
Cc: bcm-kernel-feedback-list@broadcom.com,
	dri-devel@lists.freedesktop.org, christian.koenig@amd.com,
	linux-kernel@vger.kernel.org, stable@vger.kernel.org
Subject: Re: [PATCH] drm/vmwgfx: do not hand the embedded sg_table to the PRIME core
Date: Wed,  7 Oct 2026 01:43:10 -0300	[thread overview]
Message-ID: <20261007044312.2152404-1-qwe.aldo@gmail.com> (raw)
In-Reply-To: <DLX6J39ZERYJ.377I7R20BGISX@broadcom.com>

Hi Maaz,

I reproduced the bug on VMware Workstation with a kernel based on
mainline (commit 6edd14dd67d7, v7.3-rc4), with the vgem test
modification described below. Below is the setup, the KASAN splat,
and notes on reproducing.

Regarding your October 3 question about drm-misc-fixes: as of
2026-10-06, I searched for "vmw_gem_object_get_sg_table drm-misc-fixes"
and found no indexed commit fixing this function's embedded sg_table
ownership issue. The published drm-misc-fixes-2026-10-01 pull request
lists vmwgfx input validation and blend-mode changes, not this fix [1].
I could not access the branch's live file history, so I cannot confirm
that no commit touching vmw_gem_object_get_sg_table exists at its current
tip. The vgem import-path change described below can prevent the test
from reaching the affected callback without fixing that callback.

[1] https://www.mail-archive.com/dri-devel%40lists.freedesktop.org/msg641567.html

Setup:
  - VMware Workstation 26.0.0 (build 25388281) on Ubuntu 24.04 host
  - Guest: Debian 12 (bookworm), CONFIG_KASAN=y
  - Kernel: commit 6edd14dd67d76d39a518ad3bf1a98363690a5a62 (v7.3-rc4)
  - vmwgfx loaded as module (out-of-tree rebuild from same tree)
  - Second DRM device: vgem (with gem_prime_import_sg_table enabled,
    see note below)
  - PoC runs as UID 65534 (nobody), no capabilities

KASAN splat (the v7.3-rc4 commit above, VMware Workstation):

The runtime release string in the unedited trace below is 7.3.0-rc4+.

  BUG: KASAN: invalid-free in dma_buf_unmap_attachment+0xaa/0x1d0
  Free of addr ffff888104489960 by task poc_sgtable/354

  CPU: 0 UID: 65534 PID: 354 Comm: poc_sgtable Tainted: G  OE  7.3.0-rc4+ #16
  Hardware name: VMware, Inc. VMware Virtual Platform/440BX Desktop Reference Platform, BIOS 6.00 02/17/2026
  Call Trace:
   <TASK>
   dump_stack_lvl+0x60/0x80
   print_report+0xd0/0x630
   kasan_report_invalid_free+0x9e/0xc0
   check_slab_allocation+0xf5/0x100
   kfree+0x166/0x420
   dma_buf_unmap_attachment+0xaa/0x1d0
   dma_buf_unmap_attachment_unlocked+0x80/0x100
   drm_prime_gem_destroy+0x42/0x90 [drm]
   drm_gem_shmem_release+0x71/0x800 [drm_shmem_helper]
   drm_gem_shmem_object_free+0x9/0x20 [drm_shmem_helper]
   drm_gem_object_release_handle+0xaf/0x220 [drm]
   drm_gem_handle_delete+0x59/0xa0 [drm]
   drm_ioctl_kernel+0x163/0x2d0 [drm]
   drm_ioctl+0x4ce/0xb10 [drm]
   __x64_sys_ioctl+0x135/0x1c0
   do_syscall_64+0xc1/0x560
   entry_SYSCALL_64_after_hwframe+0x76/0x7e

Steps to reproduce:
  1. DRM_VMW_ALLOC_DMABUF(262144) on vmwgfx renderD128
  2. DRM_IOCTL_PRIME_HANDLE_TO_FD
  3. DRM_VMW_GB_SURFACE_CREATE_EXT 64x64 (BO size 262144 bytes)
  4. EXECBUF BIND_GB_SURFACE(sid, mobid=handle)
     -> vmw_ttm_bind -> vmw_ttm_map_dma
     -> caches vsgt.sgt = &vmw_tt->sgt (embedded member)
  5. DRM_IOCTL_PRIME_FD_TO_HANDLE on a second DRM device (vgem)
     -> dma_buf_map_attachment -> drm_gem_map_dma_buf
     -> vmw_gem_object_get_sg_table returns &vmw_tt->sgt
  6. Close the importing GEM handle (DRM_IOCTL_GEM_CLOSE or fd close)
     -> drm_prime_gem_destroy -> dma_buf_unmap_attachment
     -> drm_gem_unmap_dma_buf: sg_free_table + kfree(&vmw_tt->sgt)
     (drm_gem_unmap_dma_buf elided in trace by tail-call optimization)
     => KASAN: invalid-free (interior pointer of vmw_ttm_tt object)

Note on the importing device:

The bug is in vmwgfx's vmw_gem_object_get_sg_table() which returns
an interior pointer (&vmw_tt->sgt) that the DRM core later kfree()s.
Any importing driver that calls dma_buf_map_attachment() triggers it,
provided the BO has been DMA-mapped (vsgt.sgt cached by a prior
surface bind), as demonstrated with vgem configured with
gem_prime_import_sg_table. This was tested with vgem; the statement
about other importing drivers is an inference from the shared PRIME
map/unmap path.

On mainline, vgem recently switched to drm_gem_shmem_prime_import_no_map
(commit 660cd44659a0, "drm/shmem-helper: Import dmabuf without mapping
its sg_table") which skips the map/unmap cycle entirely. This means
vgem no longer exercises the buggy path by default. For this reproduction,
I set .gem_prime_import_sg_table = drm_gem_shmem_prime_import_sg_table
in vgem_drv.c to use the mapping import path (one-line change, no
modification to vmwgfx).

The vgem change avoids the affected path for vgem specifically, but
the underlying vmwgfx defect is unresolved: vmw_gem_object_get_sg_table()
still returns an interior pointer when vsgt.sgt is cached on the
tested kernel. The invalid kfree was demonstrated with vgem configured
with gem_prime_import_sg_table; other importing drivers were not tested.

I also confirmed the invalid-free on kernel 6.12.0 running on
VMware Workstation, where vgem still uses the mapping import path
and no vgem modification is needed:

  BUG: KASAN: invalid-free in dma_buf_detach+0x147/0x4e0
  Free of addr ffff88811813e250 by task poc_sgtable/521

  CPU: 3 UID: 65534 PID: 521 Comm: poc_sgtable Tainted: G  OE  6.12.0 #3
  Hardware name: VMware, Inc. VMware Virtual Platform
  Call Trace:
   kasan_report_invalid_free+0x90/0xb0
   check_slab_allocation+0xf5/0x100
   kfree+0xd3/0x400
   dma_buf_detach+0x147/0x4e0
   drm_prime_gem_destroy+0x67/0x90 [drm]
   drm_gem_shmem_free+0x71/0x520 [drm_shmem_helper]
   drm_gem_handle_delete+0xd9/0x140 [drm]

Patch used for the comparison:

UNFIXED means vmwgfx built from commit
6edd14dd67d76d39a518ad3bf1a98363690a5a62 (v7.3-rc4). FIXED means the
same commit with the change below applied to vmw_gem_object_get_sg_table().

This patch contains two changes:
  (a) Add a NULL-check for bo->ttm before dereferencing it via
      container_of, returning -ENODEV if the TTM backend is absent.
  (b) Always return a freshly allocated sg_table via
      drm_prime_pages_to_sg() instead of returning the cached
      vsgt.sgt interior pointer, which is the root cause of the
      invalid kfree.

diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_gem.c b/drivers/gpu/drm/vmwgfx/vmwgfx_gem.c
index 39f8c46550c2..98c5e42cc762 100644
--- a/drivers/gpu/drm/vmwgfx/vmwgfx_gem.c
+++ b/drivers/gpu/drm/vmwgfx/vmwgfx_gem.c
@@ -70,13 +70,15 @@ static void vmw_gem_object_unpin(struct drm_gem_object *obj)
 static struct sg_table *vmw_gem_object_get_sg_table(struct drm_gem_object *obj)
 {
 	struct ttm_buffer_object *bo = drm_gem_ttm_of_gem(obj);
-	struct vmw_ttm_tt *vmw_tt =
-		container_of(bo->ttm, struct vmw_ttm_tt, dma_ttm);
+	struct vmw_ttm_tt *vmw_tt;

-	if (vmw_tt->vsgt.sgt)
-		return vmw_tt->vsgt.sgt;
+	if (!bo->ttm)
+		return ERR_PTR(-ENODEV);

-	return drm_prime_pages_to_sg(obj->dev, vmw_tt->dma_ttm.pages, vmw_tt->dma_ttm.num_pages);
+	vmw_tt = container_of(bo->ttm, struct vmw_ttm_tt, dma_ttm);
+
+	return drm_prime_pages_to_sg(obj->dev, vmw_tt->dma_ttm.pages,
+				     vmw_tt->dma_ttm.num_pages);
 }

 static int vmw_gem_vmap(struct drm_gem_object *obj, struct iosys_map *map)

IGT regression test (existing suite):

I ran the igt-gpu-tools vmwgfx test suite on the same kernel
(6edd14dd67d76d39a518ad3bf1a98363690a5a62, v7.3-rc4,
VMware Workstation, Debian 12), swapping vmwgfx.ko built without and
with the fix described above at runtime via rmmod/insmod. Results
are identical -- no additional failures:

  IGT version: 2.6-NO-GIT (source from commit 5e43e9d, built from tarball)
  Invocations:
    sudo /usr/local/igt/vmwgfx/vmw_execution_buffer
    sudo /usr/local/igt/vmwgfx/vmw_mob_stress
    sudo /usr/local/igt/vmwgfx/vmw_ref_count
    sudo /usr/local/igt/vmwgfx/vmw_surface_copy
    sudo /usr/local/igt/vmwgfx/vmw_tri
    sudo /usr/local/igt/vmwgfx/vmw_prime

                                                            UNFIXED    FIXED
  vmw_execution_buffer:
    mob-create-map:                                         SUCCESS    SUCCESS
    buffer-create:                                          SUCCESS    SUCCESS
    execution-buffer-submit-sync:                           SUCCESS    SUCCESS
  vmw_mob_stress:
    max_mob_mem_stress:                                     FAIL       FAIL       (pre-existing)
  vmw_ref_count:
    surface_prime_transfer_explicit_mob:                    SUCCESS    SUCCESS
    surface_prime_transfer_implicit_mob:                    SUCCESS    SUCCESS
    surface_prime_transfer_fd_dup:                          SUCCESS    SUCCESS
    surface_prime_transfer_two_surfaces:                    SUCCESS    SUCCESS
    surface_prime_transfer_single_surface_multiple_handle:  SUCCESS    SUCCESS
    mob_repeated_unref:                                     SUCCESS    SUCCESS
    surface_repeated_unref:                                 SUCCESS    SUCCESS
    surface_alloc_ref_unref:                                SUCCESS    SUCCESS
    surface_buffer_ref:                                     SUCCESS    SUCCESS
    surface_prime_refs:                                     SUCCESS    SUCCESS
    surface_buffer_prime_refs:                              SUCCESS    SUCCESS
  vmw_surface_copy:
    test_invalid_copies:                                    SUCCESS    SUCCESS
    test_invalid_copies_3d:                                 SUCCESS    SUCCESS
  vmw_tri:
    tri:                                                    FAIL       FAIL       (pre-existing)
    tri-no-sync-coherent:                                   FAIL       FAIL       (pre-existing)
    tri-2d:                                                 SUCCESS    SUCCESS
  vmw_prime:
    basic-vgem:                                             SUCCESS    SUCCESS
    tri-map-gem:                                            FAIL       FAIL       (pre-existing)
    tri-map-dmabuf:                                         FAIL       FAIL       (pre-existing)
    draw-dumb-buffer:                                       FAIL       FAIL       (pre-existing)
    buffer-surface-fb-sharing-sync-readback:                      FAIL       FAIL       (pre-existing)
    buffer-surface-fb-sharing-sync:                         FAIL       FAIL       (pre-existing)
    buffer-surface-fb-sharing:                              FAIL       FAIL       (pre-existing)
18 SUCCESS, 9 FAIL (all pre-existing, identical in both runs).
Every subtest listed individually. No additional failures from
the fix.

IGT regression test (new sgtable-invalid-free subtest):

I also wrote and compiled a dedicated IGT subtest
(vmw_prime_sgtable) that exercises the same PRIME
export/import/close flow from the standalone reproducer. It was
compiled against the igt-gpu-tools tree and executed on the same
kernel (v7.3-rc4, VMware Workstation, CONFIG_KASAN=y), with the
same vgem mapping-import configuration described above. Results:

  - UNFIXED vmwgfx: subtest sgtable-invalid-free SUCCESS.
    Immediately afterward, a TTM cleanup worker Oopsed in
    dma_direct_unmap_sg, with vmw_ttm_unmap_dma in the call
    trace. This is consistent with corruption of the sg_table
    used during cleanup.
  - FIXED vmwgfx: subtest sgtable-invalid-free SUCCESS.
    dmesg after the test shows no Oops, no KASAN reports,
    no BUG. A separate WARNING from vmw_cmdbuf_ctx_process
    (command buffer error during EXECBUF) appears in dmesg;
    it is distinct from the sg_table invalid-free reported
    here.

Full IGT logs follow in two replies to this message (unfixed and
fixed runs).

Standalone reproducer results:

The standalone reproducer (vmw_sgtable_test.c) was compiled and
executed on VMware Workstation 26.0.0, kernel commit
6edd14dd67d76d39a518ad3bf1a98363690a5a62 (v7.3-rc4), without and
with the fix described above:
  - UNFIXED vmwgfx: BUG: KASAN: invalid-free in dma_buf_unmap_attachment
  - FIXED vmwgfx:   clean (no KASAN)

Both the standalone reproducer and the IGT testcase were compiled and
executed as described above. The standalone reproducer
(vmw_sgtable_test.c) and IGT testcase source
(vmw_prime_sgtable.c) are included below.
Full IGT logs follow in two replies to this message (unfixed
and fixed runs).

Requires vgem with gem_prime_import_sg_table (one-line override in
vgem_drv.c, see note above) to exercise the mapping import path.

Let me know if you can reproduce with this setup, or if you need
anything else from my side. I am happy to send v2 patches whenever
you are ready.

thanks,
Aldo

---8<--- vmw_sgtable_test.c ---8<---

/*
 * vmw_prime_sgtable_test: Reproduce embedded sg_table invalid-free in vmwgfx.
 * Without fix: KASAN reports invalid-free. With fix: clean.
 */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <errno.h>
#include <stdint.h>

/* DRM core */
#define DRM_COMMAND_BASE 0x40
#define DRM_IOCTL_BASE 'd'
#define DRM_IOWR(nr, type) _IOWR(DRM_IOCTL_BASE, nr, type)
#define DRM_IOW(nr, type)  _IOW(DRM_IOCTL_BASE, nr, type)

struct drm_prime_handle { uint32_t handle; uint32_t flags; int32_t fd; };
struct drm_gem_close { uint32_t handle; uint32_t pad; };
#define DRM_IOCTL_PRIME_HANDLE_TO_FD _IOWR(DRM_IOCTL_BASE, 0x2d, struct drm_prime_handle)
#define DRM_IOCTL_PRIME_FD_TO_HANDLE _IOWR(DRM_IOCTL_BASE, 0x2e, struct drm_prime_handle)
#define DRM_IOCTL_GEM_CLOSE _IOW(DRM_IOCTL_BASE, 0x09, struct drm_gem_close)

/* vmwgfx */
#define DRM_VMW_ALLOC_DMABUF 1
#define DRM_VMW_GB_SURFACE_CREATE 23
#define DRM_VMW_GB_SURFACE_CREATE_EXT 27
#define DRM_VMW_EXECBUF 12
#define DRM_VMW_EXECBUF_VERSION 2
#define SVGA_3D_CMD_BIND_GB_SURFACE 1099

struct drm_vmw_alloc_bo_req { uint32_t size, pad64; };
struct drm_vmw_bo_rep { uint64_t map_handle; uint32_t handle, cur_gmr_id, cur_gmr_offset, pad64; };
union drm_vmw_alloc_bo_arg { struct drm_vmw_alloc_bo_req req; struct drm_vmw_bo_rep rep; };

struct drm_vmw_size { uint32_t width, height, depth, pad64; };
struct drm_vmw_gb_surface_create_req {
    uint32_t svga3d_flags; uint32_t format; uint32_t mip_levels;
    uint32_t drm_surface_flags; uint32_t multisample_count;
    uint32_t autogen_filter; uint32_t array_size;
    uint32_t buffer_handle; struct drm_vmw_size base_size;
};
struct drm_vmw_gb_surface_create_rep {
    uint32_t handle; uint32_t backup_size; uint32_t buffer_handle;
    uint32_t buffer_size; uint64_t buffer_map_handle;
};
union drm_vmw_gb_surface_create_arg {
    struct drm_vmw_gb_surface_create_rep rep;
    struct drm_vmw_gb_surface_create_req req;
};
struct drm_vmw_gb_surface_create_ext_req {
    struct drm_vmw_gb_surface_create_req base;
    uint32_t version;
    uint32_t svga3d_flags_upper_32_bits;
    uint32_t multisample_pattern;
    uint32_t quality_level;
    uint32_t buffer_byte_stride;
    uint32_t must_be_zero;
};
union drm_vmw_gb_surface_create_ext_arg {
    struct drm_vmw_gb_surface_create_ext_req req;
    struct drm_vmw_gb_surface_create_rep rep;
};

struct drm_vmw_execbuf_arg {
    uint64_t commands; uint32_t command_size; uint32_t throttle_us;
    uint64_t fence_rep; uint32_t version; uint32_t flags;
    uint32_t context_handle; int32_t imported_fence_fd;
};

#pragma pack(push, 1)
typedef struct { uint32_t id; uint32_t size; } SVGA3dCmdHeader;
typedef struct { uint32_t sid; uint32_t mobid; } SVGA3dCmdBindGBSurface;
#pragma pack(pop)

#define IOCTL_ALLOC DRM_IOWR(DRM_COMMAND_BASE + DRM_VMW_ALLOC_DMABUF, union drm_vmw_alloc_bo_arg)
#define IOCTL_SURFACE_EXT DRM_IOWR(DRM_COMMAND_BASE + DRM_VMW_GB_SURFACE_CREATE_EXT, union drm_vmw_gb_surface_create_ext_arg)
#define IOCTL_EXECBUF DRM_IOW(DRM_COMMAND_BASE + DRM_VMW_EXECBUF, struct drm_vmw_execbuf_arg)

int main(void) {
    int vmw_fd, vgem_fd, prime_fd, ret;
    union drm_vmw_alloc_bo_arg alloc;
    union drm_vmw_gb_surface_create_ext_arg surf;
    struct drm_vmw_execbuf_arg exec;
    struct { SVGA3dCmdHeader hdr; SVGA3dCmdBindGBSurface body; } cmd;
    struct drm_prime_handle ph, ph2;
    struct drm_gem_close cl;
    void *map;

    vmw_fd = open("/dev/dri/renderD128", O_RDWR);
    vgem_fd = open("/dev/dri/renderD129", O_RDWR);
    if (vmw_fd < 0 || vgem_fd < 0) { perror("open"); return 77; }

    /* 1. Alloc BO */
    memset(&alloc, 0, sizeof(alloc));
    alloc.req.size = 256 * 256 * 4;
    ret = ioctl(vmw_fd, IOCTL_ALLOC, &alloc);
    if (ret < 0) { perror("alloc"); return 1; }
    printf("[+] BO handle=%u\n", alloc.rep.handle);

    /* 2. Populate */
    map = mmap(NULL, 256*256*4, PROT_READ|PROT_WRITE, MAP_SHARED, vmw_fd, alloc.rep.map_handle);
    if (map != MAP_FAILED) { memset(map, 0x41, 256*256*4); munmap(map, 256*256*4); }

    /* 3. PRIME export the MOB handle */
    memset(&ph, 0, sizeof(ph));
    ph.handle = alloc.rep.handle;
    ph.flags = O_CLOEXEC | O_RDWR;
    ret = ioctl(vmw_fd, DRM_IOCTL_PRIME_HANDLE_TO_FD, &ph);
    if (ret < 0) { perror("export"); return 1; }
    prime_fd = ph.fd;
    printf("[+] PRIME exported fd=%d\n", prime_fd);

    /* 4. Create GB surface + bind (triggers vmw_ttm_map_dma -> vsgt.sgt cache) */
    memset(&surf, 0, sizeof(surf));
    surf.req.base.svga3d_flags = (1 << 6); /* SVGA3D_SURFACE_HINT_RENDERTARGET */
    surf.req.base.format = 37;             /* SVGA3D_BUFFER */
    surf.req.base.mip_levels = 1;
    surf.req.base.autogen_filter = 1;
    surf.req.base.array_size = 1;
    surf.req.base.drm_surface_flags = 1; /* drm_vmw_surface_flag_shareable */
    surf.req.base.buffer_handle = alloc.rep.handle; /* backup = our MOB */
    surf.req.base.base_size.width = 64;
    surf.req.base.base_size.height = 64;
    surf.req.base.base_size.depth = 1;
    surf.req.version = 1; /* drm_vmw_surface_version_v1 */
    ret = ioctl(vmw_fd, IOCTL_SURFACE_EXT, &surf);
    if (ret < 0) { printf("[-] surface create: %s (non-fatal)\n", strerror(errno)); }
    else {
        printf("[+] surface sid=%u backup_size=%u\n", surf.rep.handle, surf.rep.backup_size);
        /* EXECBUF bind */
        cmd.hdr.id = SVGA_3D_CMD_BIND_GB_SURFACE;
        cmd.hdr.size = sizeof(cmd.body);
        cmd.body.sid = surf.rep.handle;
        cmd.body.mobid = alloc.rep.handle;
        memset(&exec, 0, sizeof(exec));
        exec.commands = (uint64_t)(uintptr_t)&cmd;
        exec.command_size = sizeof(cmd);
        exec.version = DRM_VMW_EXECBUF_VERSION;
        exec.context_handle = 0xFFFFFFFF; /* SVGA3D_INVALID_ID = no DX context */
        ret = ioctl(vmw_fd, IOCTL_EXECBUF, &exec);
        if (ret < 0) printf("[-] execbuf bind: %s\n", strerror(errno));
        else printf("[+] BIND_GB_SURFACE OK\n");
    }

    /* 5. Cross-device PRIME import */
    memset(&ph2, 0, sizeof(ph2));
    ph2.fd = prime_fd;
    ret = ioctl(vgem_fd, DRM_IOCTL_PRIME_FD_TO_HANDLE, &ph2);
    if (ret < 0) {
        printf("[-] PRIME import failed: errno=%d\n", errno);
        close(prime_fd); close(vmw_fd); close(vgem_fd);
        return 1;
    }
    printf("[+] PRIME imported handle=%u\n", ph2.handle);

    /* 6. Close import -> kfree(sgt) */
    memset(&cl, 0, sizeof(cl));
    cl.handle = ph2.handle;
    ioctl(vgem_fd, DRM_IOCTL_GEM_CLOSE, &cl);
    close(prime_fd);

    printf("[+] DONE. Check: sudo dmesg | grep KASAN\n");
    close(vmw_fd); close(vgem_fd);
    return 0;
}

---8<--- vmw_prime_sgtable.c (IGT testcase) ---8<---

// SPDX-License-Identifier: GPL-2.0 OR MIT
/*
 * Test: vmw_prime_sgtable
 *
 * Validates that vmwgfx correctly handles embedded sg_table lifetime
 * during cross-device PRIME import/close sequences.  On unfixed kernels,
 * closing the imported GEM handle triggers kfree() on the embedded
 * (non-heap-allocated) sg_table inside struct vmw_ttm_tt, producing a
 * KASAN invalid-free splat.
 */

#include "igt_kms.h"
#include "igt_vmwgfx.h"

#include <fcntl.h>
#include <string.h>
#include <sys/ioctl.h>

IGT_TEST_DESCRIPTION("Test sg_table lifetime in vmwgfx PRIME export/import paths.");

/*
 * Full ioctl number for DRM_VMW_EXECBUF — vmwgfx_drm.h provides the
 * command offset but not the composed ioctl macro.
 */
#define IOCTL_VMW_EXECBUF \
	DRM_IOW(DRM_COMMAND_BASE + DRM_VMW_EXECBUF, struct drm_vmw_execbuf_arg)

static void test_sgtable_invalid_free(int vmw_fd, int import_fd)
{
	struct vmw_mob *mob;
	struct vmw_surface *surf;
	int prime_fd, ret;
	void *map;
	const uint32_t bo_size = 64 * 64 * 4;
	SVGA3dSize surf_size = { .width = 64, .height = 64, .depth = 1 };

	/* 1. Create and populate a MOB */
	mob = vmw_ioctl_mob_create(vmw_fd, bo_size);
	igt_require(mob);
	igt_require(mob->handle != 0);

	map = vmw_ioctl_mob_map(vmw_fd, mob);
	igt_require(map);
	memset(map, 0x41, bo_size);
	vmw_ioctl_mob_unmap(mob);

	/* 2. PRIME export the MOB handle */
	prime_fd = prime_handle_to_fd(vmw_fd, mob->handle);
	igt_assert(prime_fd >= 0);

	/* 3. Create a GB surface backed by this MOB */
	surf = vmw_ioctl_create_surface_full(vmw_fd,
		SVGA3D_SURFACE_HINT_RENDERTARGET,   /* flags */
		SVGA3D_BUFFER,                       /* format */
		0,                                   /* multisample_count */
		SVGA3D_MS_PATTERN_NONE,
		SVGA3D_MS_QUALITY_NONE,
		SVGA3D_TEX_FILTER_NEAREST,           /* autogen_filter */
		1,                                   /* num_mip_levels */
		1,                                   /* array_size */
		surf_size,
		mob->handle,                         /* buffer_handle (backup) */
		drm_vmw_surface_flag_shareable);
	/* Surface creation + bind trigger DMA mapping of the BO. */
	if (surf) {
		/* 4. Bind surface to MOB via raw EXECBUF */
		struct {
			SVGA3dCmdHeader hdr;
			SVGA3dCmdBindGBSurface body;
		} cmd;
		struct drm_vmw_execbuf_arg exec;

		cmd.hdr.id = SVGA_3D_CMD_BIND_GB_SURFACE;
		cmd.hdr.size = sizeof(cmd.body);
		cmd.body.sid = surf->base.handle;
		cmd.body.mobid = mob->handle;

		memset(&exec, 0, sizeof(exec));
		exec.commands = (uint64_t)(uintptr_t)&cmd;
		exec.command_size = sizeof(cmd);
		exec.version = DRM_VMW_EXECBUF_VERSION;
		exec.context_handle = 0xFFFFFFFF;

		ret = ioctl(vmw_fd, IOCTL_VMW_EXECBUF, &exec);
		if (ret < 0)
			igt_debug("execbuf bind: %s (non-fatal)\n",
				  strerror(errno));
		else
			igt_debug("BIND_GB_SURFACE OK (sid=%u, mobid=%u)\n",
				  surf->base.handle, mob->handle);
	}

	/*
	 * 5. Cross-device PRIME import using raw ioctl.
	 *
	 * Do NOT use prime_fd_to_handle() — it returns ENOSYS on
	 * some vmwgfx setups.  Raw DRM_IOCTL_PRIME_FD_TO_HANDLE works.
	 */
	{
		struct drm_prime_handle import_args;
		struct drm_gem_close close_args;

		memset(&import_args, 0, sizeof(import_args));
		import_args.fd = prime_fd;
		ret = ioctl(import_fd, DRM_IOCTL_PRIME_FD_TO_HANDLE,
			    &import_args);
		igt_assert_eq(ret, 0);
		igt_assert(import_args.handle != 0);
		igt_debug("PRIME imported handle=%u\n", import_args.handle);

		/*
		 * 6. Close the imported handle.
		 *
		 * On unfixed kernels this triggers kfree() on the
		 * embedded sg_table that was never separately allocated,
		 * causing KASAN invalid-free.  On fixed kernels this
		 * completes cleanly.
		 */
		memset(&close_args, 0, sizeof(close_args));
		close_args.handle = import_args.handle;
		ret = ioctl(import_fd, DRM_IOCTL_GEM_CLOSE, &close_args);
		igt_assert_eq(ret, 0);
	}

	/* 7. Cleanup */
	close(prime_fd);
	if (surf)
		vmw_ioctl_surface_unref(vmw_fd, surf);
	vmw_ioctl_mob_close_handle(vmw_fd, mob);
}

int igt_main()
{
	int vmw_fd = -1;
	int import_fd = -1;

	igt_fixture() {
		vmw_fd = open("/dev/dri/renderD128", O_RDWR);
		igt_require(vmw_fd >= 0);

		/*
		 * Need a second DRM device for cross-device PRIME import.
		 * Try renderD129 (typically VGEM or another GPU node).
		 */
		import_fd = open("/dev/dri/renderD129", O_RDWR);
		if (import_fd < 0)
			import_fd = open("/dev/dri/card1", O_RDWR);
		igt_require_f(import_fd >= 0,
			      "Need a second DRM device for PRIME import\n");
	}

	igt_describe("Validates sg_table lifetime during PRIME"
		     " export/import/close.  On unfixed kernels, closing the"
		     " imported handle triggers an invalid kfree of the"
		     " embedded sg_table.");
	igt_subtest("sgtable-invalid-free") {
		test_sgtable_invalid_free(vmw_fd, import_fd);
	}

	igt_fixture() {
		if (import_fd >= 0)
			close(import_fd);
		if (vmw_fd >= 0)
			close(vmw_fd);
	}
}

  reply	other threads:[~2026-10-07  4:43 UTC|newest]

Thread overview: 8+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-09-23 12:26 Aldo Ariel Panzardo
2026-09-23 14:37 ` Zack Rusin
     [not found]   ` <CAP48HfviFZXacVY9Lj4Hv7+brObhmxWLYAM8MNiTUkJNchnp1Q@mail.gmail.com>
2026-10-03  0:34     ` Maaz Mombasawala <maaz.mombasawala@broadcom.com>
2026-10-03 16:38       ` Aldo Ariel Panzardo
2026-10-05 20:16         ` Maaz Mombasawala <maaz.mombasawala@broadcom.com>
2026-10-07  4:43           ` Aldo Ariel Panzardo [this message]
2026-10-07  4:46           ` [PATCH] drm/vmwgfx: do not hand the embedded sg_table to the PRIME core [IGT full log - UNFIXED vmwgfx] Aldo Ariel Panzardo
2026-10-07  4:47           ` [PATCH] drm/vmwgfx: do not hand the embedded sg_table to the PRIME core [IGT full log - FIXED vmwgfx] Aldo Ariel Panzardo

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261007044312.2152404-1-qwe.aldo@gmail.com \
    --to=qwe.aldo@gmail.com \
    --cc=bcm-kernel-feedback-list@broadcom.com \
    --cc=christian.koenig@amd.com \
    --cc=dri-devel@lists.freedesktop.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=maaz.mombasawala@broadcom.com \
    --cc=stable@vger.kernel.org \
    --cc=zack.rusin@broadcom.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®