Hello,

  GPT-6-Luna assisted with the initial analysis and drafting the proposed fix.
  I am responsible for this report.

  I found a kernel write page fault in the vmwgfx FIFO command path while
  fuzzing Linux 7.1.5. The failing ioctl was
  DRM_IOCTL_VMW_FENCE_EVENT. The driver reserved FIFO space, derived a command
  pointer from FIFO state, and then wrote the fence command through an
  unmapped address.

  The important crash excerpt is:

    BUG: unable to handle page fault for address: ffa000009e85232c
    #PF: supervisor write access in kernel mode
    RIP: vmw_cmd_send_fence+0x16e/0x290
    RDI: ffa000009e85232c  R14: ffa000009e85232c
    CR2: ffa000009e85232c
    Call Trace:
      vmw_execbuf_fence_commands+0x120/0x200
      vmw_fence_event_ioctl+0xc7/0x670
      drm_ioctl_kernel
      drm_ioctl
      vmw_generic_ioctl
      vmw_unlocked_ioctl
      __x64_sys_ioctl

  The source-level execution path is:

    __x64_sys_ioctl
      -> vmw_unlocked_ioctl()
      -> vmw_generic_ioctl()
      -> drm_ioctl()
      -> vmw_fence_event_ioctl()
      -> vmw_execbuf_fence_commands()
      -> vmw_cmd_send_fence()
      -> VMW_CMD_RESERVE()
      -> vmw_local_fifo_reserve()
      -> return fifo_mem + (next_cmd >> 2)
      -> vmw_cmd_send_fence() writes SVGA_CMD_FENCE through the returned pointer

  The relevant FIFO reservation code reads MAX, MIN, NEXT_CMD and STOP from the
  FIFO memory. It checks the requested size against `max - min`, but does not
  validate that the device-provided offsets are aligned and within the FIFO
  range before deriving the pointer. The faulting address is the pointer used by
  the write. The report does not directly print the raw NEXT_CMD value, so I do
  not claim an exact register value inferred from the fault address.

  The userspace entry point is DRM_IOCTL_VMW_FENCE_EVENT. Its argument requests
  a fence event; it does not contain the FIFO NEXT_CMD value.

  This ioctl is the userspace entry point, not the source of the bad FIFO
  pointer itself. vmw_fence_event_ioctl() requests a command-stream fence via
  vmw_execbuf_fence_commands(); vmw_cmd_send_fence() then reserves space for
  SVGA_CMD_FENCE through VMW_CMD_RESERVE() and vmw_local_fifo_reserve(). The
  reserve routine reads MAX, MIN, NEXT_CMD and STOP from device-backed FIFO
  state and uses NEXT_CMD to form `fifo_mem + (next_cmd >> 2)`. In this run the
  resulting address is the unmapped address reported by CR2, and the subsequent
  store of the fence command faults. The ioctl argument requests the fence
  event; it does not supply NEXT_CMD. The device model supplies the FIFO
  state observed by the driver. Since the report does not log raw register
  contents, it proves the bad computed pointer and write, but not the exact raw
  NEXT_CMD value or which FIFO register first became inconsistent.

  The syzkaller reproducer is available and can be provided privately to the
  maintainers on request; it is not included in this public report.

  The patch below rejects a missing local FIFO state and validates MIN, MAX,
  NEXT_CMD and STOP before calculating or using a FIFO command pointer. The
  crash was observed with the QEMU model, which does not implement full
  SVGA FIFO semantics; I have not established that a conforming VMware device
  can supply the same invalid state. The patch passes `git apply --check`, but
  has not yet had runtime regression testing.

  diff --git a/drivers/gpu/drm/vmwgfx/vmwgfx_cmd.c 
b/drivers/gpu/drm/vmwgfx/vmwgfx_cmd.c
  --- a/drivers/gpu/drm/vmwgfx/vmwgfx_cmd.c
  +++ b/drivers/gpu/drm/vmwgfx/vmwgfx_cmd.c
  @@ -302,9 +302,12 @@ static void *vmw_local_fifo_reserve(struct vmw_private 
*dev_priv,
    uint32_t max;
    uint32_t min;
    uint32_t next_cmd;
  - uint32_t reserveable = fifo_state->capabilities & SVGA_FIFO_CAP_RESERVE;
  + uint32_t reserveable;
    int ret;
  
  + if (unlikely(!fifo_state || !fifo_mem))
  +   return NULL;
  + reserveable = fifo_state->capabilities & SVGA_FIFO_CAP_RESERVE;
    mutex_lock(&fifo_state->fifo_mutex);
    max = vmw_fifo_mem_read(dev_priv, SVGA_FIFO_MAX);
    min = vmw_fifo_mem_read(dev_priv, SVGA_FIFO_MIN);
  @@ -311,4 +315,6 @@ static void *vmw_local_fifo_reserve(struct vmw_private 
*dev_priv,
    next_cmd = vmw_fifo_mem_read(dev_priv, SVGA_FIFO_NEXT_CMD);
  
  - if (unlikely(bytes >= (max - min)))
  + if (unlikely(min >= max || next_cmd < min || next_cmd >= max ||
  +        ((min | max | next_cmd) & 3) ||
  +        bytes >= (max - min)))
      goto out_err;
  @@ -321,6 +327,9 @@ static void *vmw_local_fifo_reserve(struct vmw_private 
*dev_priv,
    while (1) {
      uint32_t stop = vmw_fifo_mem_read(dev_priv, SVGA_FIFO_STOP);
      bool need_bounce = false;
      bool reserve_in_place = false;
  +
  +   if (unlikely(stop < min || stop >= max || (stop & 3)))
  +     goto out_err;
  
      if (next_cmd >= stop) {

  Regards,
  Yuhang Pan
  [email protected]

Reply via email to