Hi Zack,

Thank you for the detailed review. Here is v2 as a two-patch series
addressing all your feedback:

  1/2: return an error and drop the surface reference when backup is
       missing (unchanged from v2 RESEND, included for series base)
  2/2: close the TOCTOU on guest_memory_bo (v2, description and
       Fixes tag corrected per your review)

Changes in 2/2 since v1:
  - Description rewritten: the racing writer is a backup switch via
    SVGA_3D_CMD_BIND_GB_SURFACE (vmw_resource_unreserve), not eviction
  - Fixes tag corrected to a97e21923b42
  - Rebased on top of 1/2 (no longer duplicates ret = -EINVAL)
  - mutex scope kept to the minimum: NULL check, handle creation,
    buffer_map_handle and buffer_size reads


1. Validation test for patch 1/2
---------------------------------

A test program creates a shareable surface WITHOUT a backup buffer
(drm_surface_flags=shareable, buffer_handle=SVGA3D_INVALID_ID, no
drm_vmw_surface_flag_create_buffer), then calls the surface reference
ioctl via DRM_VMW_GB_SURFACE_REF_EXT.

  UNFIXED module:  ioctl() returns 0 (success) without filling the
                   reply structure — the kernel handler never sets ret.
  FIXED module:    ioctl() returns -1 with errno set to EINVAL. Kernel
                   dmesg shows:
                     [drm:vmw_gb_surface_reference_internal [vmwgfx]]
                     *ERROR* Shared GB surface is missing a backup buffer.

The test exercises the missing-backup error path, but does not
independently verify reference cleanup. The cleanup was verified by
source inspection — see section 2 below.

Test program (test_no_backup.c, requires <drm.h> and the kernel UAPI
vmwgfx_drm.h):

  #include <stdio.h>
  #include <string.h>
  #include <fcntl.h>
  #include <unistd.h>
  #include <sys/ioctl.h>
  #include <errno.h>
  #include <stdint.h>
  #include <drm.h>
  #include "vmwgfx_drm.h"

  #define VMW_IOCTL_GB_SURFACE_CREATE_EXT \
      DRM_IOWR(DRM_COMMAND_BASE + DRM_VMW_GB_SURFACE_CREATE_EXT, \
               union drm_vmw_gb_surface_create_ext_arg)
  #define VMW_IOCTL_GB_SURFACE_REF_EXT \
      DRM_IOWR(DRM_COMMAND_BASE + DRM_VMW_GB_SURFACE_REF_EXT, \
               union drm_vmw_gb_surface_reference_ext_arg)

  int main(void) {
      int fd = open("/dev/dri/renderD128", O_RDWR);
      if (fd < 0) { perror("open"); return 77; }

      union drm_vmw_gb_surface_create_ext_arg create;
      memset(&create, 0, sizeof(create));
      create.req.base.format = 2;
      create.req.base.mip_levels = 1;
      create.req.base.array_size = 1;
      create.req.base.drm_surface_flags = drm_vmw_surface_flag_shareable;
      create.req.base.buffer_handle = 0xFFFFFFFF;
      create.req.base.base_size.width = 64;
      create.req.base.base_size.height = 64;
      create.req.base.base_size.depth = 1;

      if (ioctl(fd, VMW_IOCTL_GB_SURFACE_CREATE_EXT, &create) < 0)
          { printf("create failed: %s\n", strerror(errno)); return 77; }
      if (create.rep.buffer_handle != 0xFFFFFFFF)
          { printf("surface has backup, skip\n"); return 77; }

      union drm_vmw_gb_surface_reference_ext_arg ref;
      memset(&ref, 0, sizeof(ref));
      ref.req.sid = create.rep.handle;

      int ret = ioctl(fd, VMW_IOCTL_GB_SURFACE_REF_EXT, &ref);
      if (ret < 0 && errno == EINVAL)
          { printf("PASS: -EINVAL\n"); return 0; }
      if (ret == 0)
          { printf("FAIL: success (ret=0)\n"); return 1; }
      printf("OTHER: errno=%d\n", errno);
      return 1;
  }

  Build:  gcc -I/usr/include/libdrm -I$HOME -o test_no_backup test_no_backup.c
  Run as a user with read/write access to the render node:
          ./test_no_backup


2. Reference cleanup verification (source inspection)
------------------------------------------------------

vmw_surface_handle_reference() acquires two refs:

  (a) ttm_base_object_lookup_for_ref() — kref_get on base->refcount
      (the "lookup ref")
  (b) ttm_ref_object_add() — acquires a per-file reference in tfile
      (the "per-file ref")

On the SUCCESS path (ret == 0, falls through to out_bad_resource):
  - ttm_base_object_unref(&base) releases the lookup ref (a).
  - The per-file ref (b) stays alive; userspace releases it later
    via DRM_VMW_UNREF_SURFACE → ttm_ref_object_base_unref().

On ERROR paths (ret != 0, goto out_bad_resource):
  - if (ret) ttm_ref_object_base_unref() undoes the per-file
    acquisition (b).
  - ttm_base_object_unref(&base) releases the lookup ref (a).
  - No GEM handle needs to be released on these error paths
    (drm_gem_handle_create either was not reached or failed).

Both paths are balanced.


3. Race analysis (source inspection)
--------------------------------------

Writers of res->guest_memory_bo in vmwgfx (at commit 6edd14dd67d7):

  vmwgfx_resource.c:473  vmw_resource_unreserve(): = vmw_user_bo_ref(new)
  vmwgfx_resource.c:483  vmw_resource_unreserve(): = NULL
    Both under cmdbuf_mutex, called from vmw_execbuf_process
    (vmwgfx_execbuf.c:4242) and vmw_resource_pin
    (vmwgfx_resource.c:1012).

  vmwgfx_resource.c:229  vmw_resource_init(): = NULL (construction)
  vmwgfx_resource.c:354  vmw_resource_buf_alloc(): = gbo (init path)
  vmwgfx_surface.c:~1618 vmw_gb_surface_define_internal(): via
    vmw_user_bo_lookup (construction, surface not yet registered)
  vmwgfx_bo.c:63         vmw_bo_free(): = NULL (destroy)
  vmwgfx_cotable.c:485/496 vmw_cotable_resize(): cotable-only
  vmwgfx_shader.c:185    vmw_dx_shader_unscrub(): shader init

The construction-time writers run before the surface is registered
in the per-file object table, so no concurrent reference ioctl can
reach them. vmw_bo_free runs during destroy. vmw_cotable_resize and
vmw_dx_shader_unscrub operate on resource types not reachable via
DRM_VMW_GB_SURFACE_REF_EXT. vmw_resource_unreserve is the remaining
concurrent writer; both its writes are under cmdbuf_mutex. The patch
moves the reads of guest_memory_bo inside cmdbuf_mutex, serializing
them against these writes.

The race was not reproduced with a dedicated test. The fix is based on
inspection of the locking protocol.


4. IGT regression test
-----------------------

Environment:
  Host: VMware Workstation 26.0.0 build 25388281
  Guest: Debian 12, kernel 7.3.0-rc4+ (commit 6edd14dd67d7)
  IGT: version 2.6 (commit 5e43e9df3a15, x86_64)
  Kernel config: CONFIG_KASAN=y (generic, inline),
    CONFIG_INIT_STACK_ALL_ZERO=y, CONFIG_DRM_VMWGFX=m
  Module swap: sudo rmmod vmwgfx && sudo insmod <module>.ko
  Module files on disk:
    UNFIXED (vmwgfx-UNFIXED.ko): md5 43a0f8caaf651a274d44a3faa51d3929
    FIXED (vmwgfx-TOCTOU-FIXED.ko): md5 01c6785d713de957fc92478cb213f0b4

Results (18 SUCCESS, 9 FAIL, identical in both runs):

  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
  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
    tri-no-sync-coherent:                      FAIL     FAIL
    tri-2d:                                    SUCCESS  SUCCESS
  vmw_prime:
    basic-vgem:                                SUCCESS  SUCCESS
    tri-map-gem:                               FAIL     FAIL
    tri-map-dmabuf:                            FAIL     FAIL
    draw-dumb-buffer:                          FAIL     FAIL
    buffer-surface-fb-sharing-sync-readback:   FAIL     FAIL
    buffer-surface-fb-sharing-sync:            FAIL     FAIL
    buffer-surface-fb-sharing:                 FAIL     FAIL

The same nine subtests failed in both runs. No additional failures
were observed with the two-patch series applied.

Aldo Ariel Panzardo (2):
  drm/vmwgfx: return an error when a shared GB surface has no backup
    buffer
  drm/vmwgfx: close TOCTOU on guest_memory_bo in surface reference
    ioctl

 drivers/gpu/drm/vmwgfx/vmwgfx_surface.c | 22 ++++++++++++++++------
 1 file changed, 16 insertions(+), 6 deletions(-)
--
2.43.0

Reply via email to