hw/display/virtio-gpu.c | 16 ++++++++++++++++ 1 file changed, 16 insertions(+)
From: Marc-André Lureau <marcandre.lureau@redhat.com>
Validate that the framebuffer stride is at least width * bytes_per_pixel
in both virtio_gpu_scanout_blob_to_fb() and virtio_gpu_do_set_scanout().
A guest can set a very small stride while using a large width. The total
size check (offset + stride * height <= blob_size) passes because
stride * height is small, but pixman reads width * bytes_per_pixel per
row, causing heap OOB reads. The leaked data is rendered to the host
display.
The check is added in virtio_gpu_do_set_scanout() to cover all paths:
blob scanout, non-blob scanout and migration post_load. The additional
early check in virtio_gpu_scanout_blob_to_fb() rejects invalid blob
configurations early.
Fixes: CVE-2026-63109
Fixes: 144bd171c837 ("virtio-gpu: Support blob scanout using dmabuf fd")
Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3989
Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
---
hw/display/virtio-gpu.c | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/hw/display/virtio-gpu.c b/hw/display/virtio-gpu.c
index 89a73793a42..1d4b8ee4192 100644
--- a/hw/display/virtio-gpu.c
+++ b/hw/display/virtio-gpu.c
@@ -647,6 +647,14 @@ static bool virtio_gpu_do_set_scanout(VirtIOGPU *g,
return false;
}
+ if (fb->stride < (uint64_t)fb->width * fb->bytes_pp) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: stride %u too small for width %u at %u bpp\n",
+ __func__, fb->stride, fb->width, fb->bytes_pp);
+ *error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
+ return false;
+ }
+
g->parent_obj.enable = 1;
if (res->blob) {
@@ -754,6 +762,14 @@ bool virtio_gpu_scanout_blob_to_fb(struct virtio_gpu_framebuffer *fb,
fb->width = ss->width;
fb->height = ss->height;
fb->stride = ss->strides[0];
+
+ if (fb->stride < (uint64_t)fb->width * fb->bytes_pp) {
+ qemu_log_mask(LOG_GUEST_ERROR,
+ "%s: stride %u too small for width %u at %u bpp\n",
+ __func__, fb->stride, fb->width, fb->bytes_pp);
+ return false;
+ }
+
fb->offset = ss->offsets[0] + ss->r.x * fb->bytes_pp + ss->r.y * fb->stride;
fbend = fb->offset;
--
2.55.0
On 2026/07/16 5:10, marcandre.lureau@redhat.com wrote:
> From: Marc-André Lureau <marcandre.lureau@redhat.com>
>
> Validate that the framebuffer stride is at least width * bytes_per_pixel
> in both virtio_gpu_scanout_blob_to_fb() and virtio_gpu_do_set_scanout().
>
> A guest can set a very small stride while using a large width. The total
> size check (offset + stride * height <= blob_size) passes because
> stride * height is small, but pixman reads width * bytes_per_pixel per
> row, causing heap OOB reads. The leaked data is rendered to the host
> display.
>
> The check is added in virtio_gpu_do_set_scanout() to cover all paths:
> blob scanout, non-blob scanout and migration post_load. The additional
> early check in virtio_gpu_scanout_blob_to_fb() rejects invalid blob
> configurations early.
>
> Fixes: CVE-2026-63109
> Fixes: 144bd171c837 ("virtio-gpu: Support blob scanout using dmabuf fd")
> Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3989
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> ---
> hw/display/virtio-gpu.c | 16 ++++++++++++++++
> 1 file changed, 16 insertions(+)
>
> diff --git a/hw/display/virtio-gpu.c b/hw/display/virtio-gpu.c
> index 89a73793a42..1d4b8ee4192 100644
> --- a/hw/display/virtio-gpu.c
> +++ b/hw/display/virtio-gpu.c
> @@ -647,6 +647,14 @@ static bool virtio_gpu_do_set_scanout(VirtIOGPU *g,
> return false;
> }
>
> + if (fb->stride < (uint64_t)fb->width * fb->bytes_pp) {
I found that fb->bytes_pp cannot be trusted because it is restored
independently from the migration stream. I think that field should be
removed from virtio_gpu_framebuffer; its migration slot can be preserved
with VMSTATE_UNUSED_TEST(). Bytes per pixel can be derived from
fb->format, which Pixman actually uses.
Regards,
Akihiko Odaki
> + qemu_log_mask(LOG_GUEST_ERROR,
> + "%s: stride %u too small for width %u at %u bpp\n",
> + __func__, fb->stride, fb->width, fb->bytes_pp);
> + *error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
> + return false;
> + }
> +
> g->parent_obj.enable = 1;
>
> if (res->blob) {
> @@ -754,6 +762,14 @@ bool virtio_gpu_scanout_blob_to_fb(struct virtio_gpu_framebuffer *fb,
> fb->width = ss->width;
> fb->height = ss->height;
> fb->stride = ss->strides[0];
> +
> + if (fb->stride < (uint64_t)fb->width * fb->bytes_pp) {
> + qemu_log_mask(LOG_GUEST_ERROR,
> + "%s: stride %u too small for width %u at %u bpp\n",
> + __func__, fb->stride, fb->width, fb->bytes_pp);
> + return false;
> + }
> +
> fb->offset = ss->offsets[0] + ss->r.x * fb->bytes_pp + ss->r.y * fb->stride;
>
> fbend = fb->offset;
On 2026/07/16 5:10, marcandre.lureau@redhat.com wrote:
> From: Marc-André Lureau <marcandre.lureau@redhat.com>
>
> Validate that the framebuffer stride is at least width * bytes_per_pixel
> in both virtio_gpu_scanout_blob_to_fb() and virtio_gpu_do_set_scanout().
>
> A guest can set a very small stride while using a large width. The total
> size check (offset + stride * height <= blob_size) passes because
> stride * height is small, but pixman reads width * bytes_per_pixel per
> row, causing heap OOB reads. The leaked data is rendered to the host
> display.
>
> The check is added in virtio_gpu_do_set_scanout() to cover all paths:
> blob scanout, non-blob scanout and migration post_load. The additional
> early check in virtio_gpu_scanout_blob_to_fb() rejects invalid blob
> configurations early.
>
> Fixes: CVE-2026-63109
> Fixes: 144bd171c837 ("virtio-gpu: Support blob scanout using dmabuf fd")
> Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3989
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
Reviewed-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
On 2026/07/16 16:37, Akihiko Odaki wrote:
> On 2026/07/16 5:10, marcandre.lureau@redhat.com wrote:
>> From: Marc-André Lureau <marcandre.lureau@redhat.com>
>>
>> Validate that the framebuffer stride is at least width * bytes_per_pixel
>> in both virtio_gpu_scanout_blob_to_fb() and virtio_gpu_do_set_scanout().
>>
>> A guest can set a very small stride while using a large width. The total
>> size check (offset + stride * height <= blob_size) passes because
>> stride * height is small, but pixman reads width * bytes_per_pixel per
>> row, causing heap OOB reads. The leaked data is rendered to the host
>> display.
>>
>> The check is added in virtio_gpu_do_set_scanout() to cover all paths:
>> blob scanout, non-blob scanout and migration post_load. The additional
>> early check in virtio_gpu_scanout_blob_to_fb() rejects invalid blob
>> configurations early.
>>
>> Fixes: CVE-2026-63109
>> Fixes: 144bd171c837 ("virtio-gpu: Support blob scanout using dmabuf fd")
I cannot resolve commit 144bd171c837. Apparently the bug was introduced
by commit 7b5574225429 ("hw/display: check frame buffer can hold blob").
>> Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3989
>> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
>
> Reviewed-by: Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>
Hi
On Thu, Jul 16, 2026 at 12:04 PM Akihiko Odaki
<odaki@rsg.ci.i.u-tokyo.ac.jp> wrote:
>
> On 2026/07/16 16:37, Akihiko Odaki wrote:
> > On 2026/07/16 5:10, marcandre.lureau@redhat.com wrote:
> >> From: Marc-André Lureau <marcandre.lureau@redhat.com>
> >>
> >> Validate that the framebuffer stride is at least width * bytes_per_pixel
> >> in both virtio_gpu_scanout_blob_to_fb() and virtio_gpu_do_set_scanout().
> >>
> >> A guest can set a very small stride while using a large width. The total
> >> size check (offset + stride * height <= blob_size) passes because
> >> stride * height is small, but pixman reads width * bytes_per_pixel per
> >> row, causing heap OOB reads. The leaked data is rendered to the host
> >> display.
> >>
> >> The check is added in virtio_gpu_do_set_scanout() to cover all paths:
> >> blob scanout, non-blob scanout and migration post_load. The additional
> >> early check in virtio_gpu_scanout_blob_to_fb() rejects invalid blob
> >> configurations early.
> >>
> >> Fixes: CVE-2026-63109
> >> Fixes: 144bd171c837 ("virtio-gpu: Support blob scanout using dmabuf fd")
>
> I cannot resolve commit 144bd171c837. Apparently the bug was introduced
> by commit 7b5574225429 ("hw/display: check frame buffer can hold blob").
>
oh yes, that was an earlier version from a development branch, thanks..
Hi
On Thu, Jul 16, 2026 at 12:20 PM Marc-André Lureau
<marcandre.lureau@redhat.com> wrote:
>
> Hi
>
> On Thu, Jul 16, 2026 at 12:04 PM Akihiko Odaki
> <odaki@rsg.ci.i.u-tokyo.ac.jp> wrote:
> >
> > On 2026/07/16 16:37, Akihiko Odaki wrote:
> > > On 2026/07/16 5:10, marcandre.lureau@redhat.com wrote:
> > >> From: Marc-André Lureau <marcandre.lureau@redhat.com>
> > >>
> > >> Validate that the framebuffer stride is at least width * bytes_per_pixel
> > >> in both virtio_gpu_scanout_blob_to_fb() and virtio_gpu_do_set_scanout().
> > >>
> > >> A guest can set a very small stride while using a large width. The total
> > >> size check (offset + stride * height <= blob_size) passes because
> > >> stride * height is small, but pixman reads width * bytes_per_pixel per
> > >> row, causing heap OOB reads. The leaked data is rendered to the host
> > >> display.
> > >>
> > >> The check is added in virtio_gpu_do_set_scanout() to cover all paths:
> > >> blob scanout, non-blob scanout and migration post_load. The additional
> > >> early check in virtio_gpu_scanout_blob_to_fb() rejects invalid blob
> > >> configurations early.
> > >>
> > >> Fixes: CVE-2026-63109
> > >> Fixes: 144bd171c837 ("virtio-gpu: Support blob scanout using dmabuf fd")
> >
> > I cannot resolve commit 144bd171c837. Apparently the bug was introduced
> > by commit 7b5574225429 ("hw/display: check frame buffer can hold blob").
> >
>
> oh yes, that was an earlier version from a development branch, thanks..
I'd rather reference 32db3c63ae11 ("virtio-gpu: Add
virtio_gpu_set_scanout_blob") that introduced the issue
On 2026/07/16 17:31, Marc-André Lureau wrote:
> Hi
>
> On Thu, Jul 16, 2026 at 12:20 PM Marc-André Lureau
> <marcandre.lureau@redhat.com> wrote:
>>
>> Hi
>>
>> On Thu, Jul 16, 2026 at 12:04 PM Akihiko Odaki
>> <odaki@rsg.ci.i.u-tokyo.ac.jp> wrote:
>>>
>>> On 2026/07/16 16:37, Akihiko Odaki wrote:
>>>> On 2026/07/16 5:10, marcandre.lureau@redhat.com wrote:
>>>>> From: Marc-André Lureau <marcandre.lureau@redhat.com>
>>>>>
>>>>> Validate that the framebuffer stride is at least width * bytes_per_pixel
>>>>> in both virtio_gpu_scanout_blob_to_fb() and virtio_gpu_do_set_scanout().
>>>>>
>>>>> A guest can set a very small stride while using a large width. The total
>>>>> size check (offset + stride * height <= blob_size) passes because
>>>>> stride * height is small, but pixman reads width * bytes_per_pixel per
>>>>> row, causing heap OOB reads. The leaked data is rendered to the host
>>>>> display.
>>>>>
>>>>> The check is added in virtio_gpu_do_set_scanout() to cover all paths:
>>>>> blob scanout, non-blob scanout and migration post_load. The additional
>>>>> early check in virtio_gpu_scanout_blob_to_fb() rejects invalid blob
>>>>> configurations early.
>>>>>
>>>>> Fixes: CVE-2026-63109
>>>>> Fixes: 144bd171c837 ("virtio-gpu: Support blob scanout using dmabuf fd")
>>>
>>> I cannot resolve commit 144bd171c837. Apparently the bug was introduced
>>> by commit 7b5574225429 ("hw/display: check frame buffer can hold blob").
>>>
>>
>> oh yes, that was an earlier version from a development branch, thanks..
>
> I'd rather reference 32db3c63ae11 ("virtio-gpu: Add
> virtio_gpu_set_scanout_blob") that introduced the issue
I don't think commit 32db3c63ae11 ("virtio-gpu: Add
virtio_gpu_set_scanout_blob") introduced this particular issue. Its
original bounds check accounted for the complete last row:
offset + stride * (height - 1) + bytes_per_pixel * width
So, a short stride could make rows overlap, but Pixman would still
remain within the validated blob extent.
Commit 7b5574225429 ("hw/display: check frame buffer can hold blob")
changed the check to offset + stride * height, which underestimates
Pixman's access when stride < width * bytes_per_pixel. That change made
the described OOB read possible, so I think it is the appropriate Fixes:
commit.
Regards,
Akihiko Odaki
On Thu, Jul 16, 2026 at 12:11 AM <marcandre.lureau@redhat.com> wrote:
>
> From: Marc-André Lureau <marcandre.lureau@redhat.com>
>
> Validate that the framebuffer stride is at least width * bytes_per_pixel
> in both virtio_gpu_scanout_blob_to_fb() and virtio_gpu_do_set_scanout().
>
> A guest can set a very small stride while using a large width. The total
> size check (offset + stride * height <= blob_size) passes because
> stride * height is small, but pixman reads width * bytes_per_pixel per
> row, causing heap OOB reads. The leaked data is rendered to the host
> display.
>
> The check is added in virtio_gpu_do_set_scanout() to cover all paths:
> blob scanout, non-blob scanout and migration post_load. The additional
> early check in virtio_gpu_scanout_blob_to_fb() rejects invalid blob
> configurations early.
>
> Fixes: CVE-2026-63109
> Fixes: 144bd171c837 ("virtio-gpu: Support blob scanout using dmabuf fd")
+
Reported-by: Tristan @TristanInSec
> Resolves: https://gitlab.com/qemu-project/qemu/-/work_items/3989
> Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> ---
> hw/display/virtio-gpu.c | 16 ++++++++++++++++
> 1 file changed, 16 insertions(+)
>
> diff --git a/hw/display/virtio-gpu.c b/hw/display/virtio-gpu.c
> index 89a73793a42..1d4b8ee4192 100644
> --- a/hw/display/virtio-gpu.c
> +++ b/hw/display/virtio-gpu.c
> @@ -647,6 +647,14 @@ static bool virtio_gpu_do_set_scanout(VirtIOGPU *g,
> return false;
> }
>
> + if (fb->stride < (uint64_t)fb->width * fb->bytes_pp) {
> + qemu_log_mask(LOG_GUEST_ERROR,
> + "%s: stride %u too small for width %u at %u bpp\n",
> + __func__, fb->stride, fb->width, fb->bytes_pp);
> + *error = VIRTIO_GPU_RESP_ERR_INVALID_PARAMETER;
> + return false;
> + }
> +
> g->parent_obj.enable = 1;
>
> if (res->blob) {
> @@ -754,6 +762,14 @@ bool virtio_gpu_scanout_blob_to_fb(struct virtio_gpu_framebuffer *fb,
> fb->width = ss->width;
> fb->height = ss->height;
> fb->stride = ss->strides[0];
> +
> + if (fb->stride < (uint64_t)fb->width * fb->bytes_pp) {
> + qemu_log_mask(LOG_GUEST_ERROR,
> + "%s: stride %u too small for width %u at %u bpp\n",
> + __func__, fb->stride, fb->width, fb->bytes_pp);
> + return false;
> + }
> +
> fb->offset = ss->offsets[0] + ss->r.x * fb->bytes_pp + ss->r.y * fb->stride;
>
> fbend = fb->offset;
> --
> 2.55.0
>
>
© 2016 - 2026 Red Hat, Inc.