Subject: [SECURITY] drm/virtio: unvalidated response length from virtqueue
From: Frédéric David Blum <[email protected]>
Hi,
I am reporting a missing length validation in the virtio-gpu driver
(drivers/gpu/drm/virtio/virtgpu_vq.c) that allows a misbehaving or
malicious hypervisor to influence kernel GPU state from outside the guest.
A patch is included inline at the end of this message.
--- Summary ---
virtqueue_get_buf() returns via its `len` output parameter the number of
bytes the device actually wrote into the response buffer. The function
reclaim_vbufs() has always discarded this value entirely. As a result,
virtio_gpu_dequeue_ctrl_func() casts entry->resp_buf to
struct virtio_gpu_ctrl_hdr * and reads resp->type, resp->flags, and
resp->fence_id without any guarantee that the device wrote a complete
header.
--- Impact ---
A misbehaving or malicious hypervisor can:
1. Report len=0 while writing no valid data into the shared buffer.
The driver reads stale or uninitialized header fields.
2. Write a partial header and report a short len. resp->flags may
have the VIRTIO_GPU_FLAG_FENCE bit set spuriously, passing a
garbage fence_id to virtio_gpu_fence_event_process() and
corrupting GPU timeline / fence signalling state.
3. In a compromised-host scenario (guest running against an untrusted
hypervisor), this provides a low-cost primitive for influencing
kernel GPU state from outside the guest without triggering any
current driver-side error path.
Affected code is present since the initial driver commit dc5698e80cf7
("Add virtio gpu driver.") and is still present in 7.1-rc3.
--- Analogy ---
This is the same class of bug fixed for virtio_bt in:
commit 73e2a2cc9b3e ("Bluetooth: virtio_bt: clamp rx length before skb_put")
commit 7c5cef27b8f7 ("Bluetooth: virtio_bt: validate rx pkt_type header
length")
Those commits validated the `len` from virtqueue_get_buf() before using
the received data. virtio-gpu has the same structural omission.
--- Fix ---
Store the len reported by virtqueue_get_buf() in a new resp_len field on
struct virtio_gpu_vbuffer. In virtio_gpu_dequeue_ctrl_func(), reject
responses shorter than sizeof(struct virtio_gpu_ctrl_hdr) with a
ratelimited error and skip processing (the vbuf is still freed correctly
in the subsequent cleanup loop). Apply the same guard to the cursor
dequeue path before the trace call.
The patch is clean against checkpatch.pl --no-tree (0 errors, 0 warnings).
--- Discovery ---
This issue was identified via cross-subsystem commit graph analysis using
Hokmah (hokmah.dev), which correlated the virtio_bt length-clamping
commits with the virtio-gpu response path and flagged the missing
equivalent check.
Regards,
Frédéric David Blum <[email protected]>
---
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Frédéric David Blum <[email protected]>
Date: Mon, 11 May 2026 00:00:00 +0000
Subject: [PATCH] drm/virtio: validate response length from virtqueue
virtqueue_get_buf() returns the number of bytes the device actually wrote
into the response buffer via the `len` output parameter. reclaim_vbufs()
has always discarded this value entirely, meaning
virtio_gpu_dequeue_ctrl_func() subsequently casts entry->resp_buf to
struct virtio_gpu_ctrl_hdr * and reads type, flags, and fence_id without
any guarantee that the device wrote a complete header.
A misbehaving or malicious hypervisor can return len=0 while writing
arbitrary data (or no data at all) into the shared response buffer,
causing the driver to:
- misread resp->type and suppress or fabricate error logging,
- misread resp->flags and trigger spurious fence events,
- pass a garbage fence_id to virtio_gpu_fence_event_process(),
corrupting GPU timeline state.
This mirrors the class of bug fixed for virtio_bt in commits:
73e2a2cc9b3e ("Bluetooth: virtio_bt: clamp rx length before skb_put")
7c5cef27b8f7 ("Bluetooth: virtio_bt: validate rx pkt_type header length")
Fix it by storing the len reported by virtqueue_get_buf() in a new
resp_len field on struct virtio_gpu_vbuffer. In
virtio_gpu_dequeue_ctrl_func(), reject responses shorter than
sizeof(struct virtio_gpu_ctrl_hdr) with a ratelimited error and skip
processing; the vbuf is still freed correctly in the subsequent cleanup
loop. Apply the same guard to the cursor dequeue path before the trace.
Discovered using Hokmah (hokmah.dev) cross-subsystem security analysis.
Fixes: dc5698e80cf7 ("Add virtio gpu driver.")
Cc: David Airlie <[email protected]>
Cc: Gerd Hoffmann <[email protected]>
Cc: [email protected]
Signed-off-by: Frédéric David Blum <[email protected]>
---
drivers/gpu/drm/virtio/virtgpu_drv.h | 1 +
drivers/gpu/drm/virtio/virtgpu_vq.c | 19 ++++++++++++++++---
2 files changed, 17 insertions(+), 3 deletions(-)
diff --git a/drivers/gpu/drm/virtio/virtgpu_drv.h
b/drivers/gpu/drm/virtio/virtgpu_drv.h
index f17660a..bfc8f1c 100644
--- a/drivers/gpu/drm/virtio/virtgpu_drv.h
+++ b/drivers/gpu/drm/virtio/virtgpu_drv.h
@@ -164,6 +164,7 @@ struct virtio_gpu_vbuffer {
char *resp_buf;
int resp_size;
+ unsigned int resp_len;
virtio_gpu_resp_cb resp_cb;
void *resp_cb_data;
diff --git a/drivers/gpu/drm/virtio/virtgpu_vq.c
b/drivers/gpu/drm/virtio/virtgpu_vq.c
index 6786581..f4f93ea 100644
--- a/drivers/gpu/drm/virtio/virtgpu_vq.c
+++ b/drivers/gpu/drm/virtio/virtgpu_vq.c
@@ -215,6 +215,7 @@ static void reclaim_vbufs(struct virtqueue *vq, struct
list_head *reclaim_list)
int freed = 0;
while ((vbuf = virtqueue_get_buf(vq, &len))) {
+ vbuf->resp_len = len;
list_add_tail(&vbuf->list, reclaim_list);
freed++;
}
@@ -244,6 +245,12 @@ void virtio_gpu_dequeue_ctrl_func(struct work_struct *work)
list_for_each_entry(entry, &reclaim_list, list) {
resp = (struct virtio_gpu_ctrl_hdr *)entry->resp_buf;
+ if (entry->resp_len < sizeof(*resp)) {
+ DRM_ERROR_RATELIMITED("short ctrl response: %u < %zu\n",
+ entry->resp_len, sizeof(*resp));
+ continue;
+ }
+
trace_virtio_gpu_cmd_response(vgdev->ctrlq.vq, resp, entry->seqno);
if (resp->type != cpu_to_le32(VIRTIO_GPU_RESP_OK_NODATA)) {
@@ -294,7 +301,12 @@ void virtio_gpu_dequeue_cursor_func(struct work_struct
*work)
struct virtio_gpu_ctrl_hdr *resp =
(struct virtio_gpu_ctrl_hdr *)entry->resp_buf;
- trace_virtio_gpu_cmd_response(vgdev->cursorq.vq, resp,
entry->seqno);
+ if (entry->resp_len >= sizeof(*resp))
+ trace_virtio_gpu_cmd_response(vgdev->cursorq.vq, resp,
+ entry->seqno);
+ else
+ DRM_ERROR_RATELIMITED("short cursor response: %u < %zu\n",
+ entry->resp_len, sizeof(*resp));
list_del(&entry->list);
free_vbuf(vgdev, entry);
}
--
2.39.0