Hello,
I'm reporting an out-of-bounds read in the vmwgfx cursor-snoop path that is
reachable by any local unprivileged user via the DRM render node
(/dev/dri/renderD128, mode 0666 on stock installs). It reliably faults the
kernel (oops; a full panic when panic_on_oops=1, which is the default on
RHEL and derivatives). It is present in current mainline (torvalds/master,
HEAD b643e495ae92 as of 2026-08-09) and in all shipping stable/distro
kernels I checked.
I tell This is an incomplete-fix variant of CVE-2022-36280. Commit
4cf949c7fafe ("drm/vmwgfx: Validate the box size for the snooped cursor")
bounded the copybox width/height (the destination side), but left
cmd->dma.guest.pitch (the source-side stride) unbounded.
This report is not public. I'm happy to follow the kernel security process
and can provide the full PoC source on request.
*Affected code*
drivers/gpu/drm/vmwgfx/vmwgfx_cursor_plane.c : vmw_kms_cursor_snoop()
(historically drivers/gpu/drm/vmwgfx/vmwgfx_kms.c)
The source buffer is kmapped for a FIXED number of pages sized to the
snooped cursor (64 * image_pitch = 4 pages):
kmap_num = (VMW_CURSOR_SNOOP_HEIGHT * image_pitch) >> PAGE_SHIFT;
...
ret = ttm_bo_kmap(bo, kmap_offset, kmap_num, &map);
virtual = ttm_kmap_obj_virtual(&map, &is_iomem);
but the slow-path copy reads at an attacker-controlled stride,
cmd->dma.guest.pitch, with no bound:
if (box->w == VMW_CURSOR_SNOOP_WIDTH && cmd->dma.guest.pitch ==
image_pitch) {
memcpy(srf->snooper.image, virtual, VMW_CURSOR_SNOOP_HEIGHT *
image_pitch);
} else {
for (i = 0; i < box->h; i++) /* box->h <= 64 */
memcpy(srf->snooper.image + i * image_pitch,
virtual + i * cmd->dma.guest.pitch, /* guest.pitch
UNBOUNDED */
box->w * desc->pitchBytesPerBlock);
}
The validation block above this checks the box geometry (x/y/z,
srcx/srcy/srcz, d, box_count, w <= 64, h <= 64) and the guest offset
alignment, but never checks guest.pitch.
The equality test on the fast/slow branch (guest.pitch == image_pitch) only
*selects* the code path; a mismatching pitch falls straight into the
unbounded slow path. With, e.g., guest.pitch = 0x10000000 and box->h = 64,
iteration i=1 already reads 256 MB past the 4-page source mapping.
*Reachability (unprivileged, default config)*
· /dev/dri/renderD128 is world-accessible (0666) on stock systemd
installs.
· The trigger ioctls are all DRM_RENDER_ALLOW:
o DRM_VMW_ALLOC_DMABUF (source bo)
o DRM_VMW_CREATE_SURFACE (allocates srf->snooper.image for a 64x64
A8R8G8B8 scanout surface via vmw_cursor_snooper_create)
o DRM_VMW_EXECBUF (carries SVGA3dCmdSurfaceDMA -> vmw_cmd_dma()
-> vmw_kms_cursor_snoop())
· No capabilities, user namespaces, or races are required.
· Affects any Linux guest running vmwgfx (VMware and VirtualBox
VMSVGA).
*Impact*
Reliable unprivileged local denial of service. The OOB read faults on
unmapped memory and oopses the kernel; with panic_on_oops=1 (default on
RHEL/derivatives and common on hardened/server configs) it is a full kernel
panic / machine-down.
Escalation assessment (for completeness): there is no OOB *write* here –
the destination srf->snooper.image is bounded by the already-validated
box->w/h.
The snooped buffer is only forwarded to the hypervisor host on a subsequent
DRM-master cursor-plane update (vmw_cursor_plane_update_legacy ->
vmw_send_define_cursor_cmd), so an unprivileged process cannot read the
leaked bytes back.
A carefully tuned, non-faulting read could constitute a limited guest->host
kernel-memory disclosure if a privileged cursor update later occurs, but
the reliable, demonstrated outcome is the DoS.
*Reproduction*
Confirmed three ways:
· source analysis;
· a KASAN QEMU lab (qemu -vga vmware);
· a stock CentOS Stream 9 guest (kernel 5.14.0-731.el9) under
VirtualBox, run as an ordinary uid=1000 user.
Unprivileged PoC:
1. fd = open("/dev/dri/renderD128", O_RDWR)
2. DRM_VMW_CREATE_SURFACE: scanout=1, 1 mip level, size 64x64x1,
format SVGA3D_A8R8G8B8 -> allocates srf->snooper.image
3. DRM_VMW_ALLOC_DMABUF: size >= 4 pages -> source bo (handle H)
4. DRM_VMW_EXECBUF, one SVGA3dCmdSurfaceDMA (SVGA_3D_CMD_SURFACE_DMA):
host = { sid = surface, face = 0, mipmap = 0 }
guest = { ptr = { gmrId = H, offset = 0 }, pitch = 0x10000000 }
one SVGA3dCopyBox { w = 64, h = 64, d = 1, all others 0 }
+ SVGA3dCmdSurfaceDMASuffix { suffixSize = 12, ... }
Observed oops (stock 5.14.0-731.el9, uid 1000, VirtualBox VMSVGA):
BUG: unable to handle page fault for address: ffffd1a050a13000
#PF: supervisor read access in kernel mode
CPU: 0 PID: 3750 Comm: vmwpoc 5.14.0-731.el9.x86_64
RIP: 0010:memcpy+0x12/0x20
RSI: ffffd1a050a13000 RBP: ffffd1a040a13000 (RSI - RBP = 0x10000000 =
i*guest.pitch)
Call Trace:
vmw_kms_cursor_snoop+0x174/0x2c0 [vmwgfx]
vmw_cmd_dma+0xdf/0x170 [vmwgfx]
vmw_cmd_check+0xb1/0x240 [vmwgfx]
vmw_execbuf_process+0x4eb/0xdd0 [vmwgfx]
vmw_execbuf_ioctl+0xf4/0x170 [vmwgfx]
drm_ioctl_kernel / drm_ioctl / __x64_sys_ioctl / do_syscall_64
*Reporter*
Youness HFA from AGBS Pentest Team <[email protected]>
Happy to coordinate disclosure timing and CVE assignment per your process.