Invalid FIFO offsets lead to a write page fault
From: 潘煜杭
Date: Wed Oct 28 2026 - 05:31:30 EST
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
242270054@xxxxxxxxxx