[PATCH] hw/display/virtio-gpu: validate stride against width on scanout

marcandre.lureau@redhat.com posted 1 patch 1 week, 2 days ago
Patches applied successfully (tree, apply log)
git fetch https://github.com/patchew-project/qemu tags/patchew/20260715201055.2465320-1-marcandre.lureau@redhat.com
Maintainers: "Alex Bennée" <alex.bennee@linaro.org>, Akihiko Odaki <odaki@rsg.ci.i.u-tokyo.ac.jp>, Dmitry Osipenko <dmitry.osipenko@collabora.com>, "Michael S. Tsirkin" <mst@redhat.com>
hw/display/virtio-gpu.c | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
[PATCH] hw/display/virtio-gpu: validate stride against width on scanout
Posted by marcandre.lureau@redhat.com 1 week, 2 days ago
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


Re: [PATCH] hw/display/virtio-gpu: validate stride against width on scanout
Posted by Akihiko Odaki 1 week, 1 day ago
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;


Re: [PATCH] hw/display/virtio-gpu: validate stride against width on scanout
Posted by Akihiko Odaki 1 week, 2 days ago
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>

Re: [PATCH] hw/display/virtio-gpu: validate stride against width on scanout
Posted by Akihiko Odaki 1 week, 2 days ago
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>


Re: [PATCH] hw/display/virtio-gpu: validate stride against width on scanout
Posted by Marc-André Lureau 1 week, 2 days ago
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..
Re: [PATCH] hw/display/virtio-gpu: validate stride against width on scanout
Posted by Marc-André Lureau 1 week, 2 days ago
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
Re: [PATCH] hw/display/virtio-gpu: validate stride against width on scanout
Posted by Akihiko Odaki 1 week, 2 days ago
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

Re: [PATCH] hw/display/virtio-gpu: validate stride against width on scanout
Posted by Marc-André Lureau 1 week, 2 days ago
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
>
>