[PULL 02/23] ui/vnc: take account of client byte order in pixman format

Daniel P. Berrangé posted 23 patches 5 months, 3 weeks ago
Maintainers: "Daniel P. Berrangé" <berrange@redhat.com>, "Marc-André Lureau" <marcandre.lureau@redhat.com>, Paolo Bonzini <pbonzini@redhat.com>, "Philippe Mathieu-Daudé" <philmd@linaro.org>, Eric Blake <eblake@redhat.com>, Markus Armbruster <armbru@redhat.com>
[PULL 02/23] ui/vnc: take account of client byte order in pixman format
Posted by Daniel P. Berrangé 5 months, 3 weeks ago
The set_pixel_conversion() method is responsible for determining whether
the VNC client pixel format matches the server format, and thus whether
we can use the fast path "copy" impl for sending pixels, or must use
the generic impl with bit swizzling.

The VNC server format is set at build time to VNC_SERVER_FB_FORMAT,
which corresponds to PIXMAN_x8r8g8b8.

The qemu_pixman_get_format() method is then responsible for converting
the VNC pixel format into a pixman format.

The VNC client pixel shifts are relative to the associated endianness.

The pixman formats are always relative to the host native endianness.

The qemu_pixman_get_format() method does not take into account the
VNC client endianness, and is thus returning a pixman format that is
only valid with the host endianness matches that of the VNC client.

This has been broken since pixman was introduced to the VNC server:

  commit 9f64916da20eea67121d544698676295bbb105a7
  Author: Gerd Hoffmann <kraxel@redhat.com>
  Date:   Wed Oct 10 13:29:43 2012 +0200

    pixman/vnc: use pixman images in vnc.

The flaw can be demonstrated using the Tigervnc client by using

   vncviewer -AutoSelect=0 -PreferredEncoding=raw server:display

connecting from a LE client to a QEMU on a BE server, or the
reverse.

The bug was masked, however, because almost all VNC clients will
advertize support for the "tight" encoding and the QEMU VNC server
will prefer "tight" if advertized.

The tight_pack24 method is responsible for taking a set of pixels
which have already been converted into client endianness and then
repacking them into the TPIXEL format which the RFB spec defines
as

  "TPIXEL is only 3 bytes long, where the first byte is the
   red component, the second byte is the green component,
   and the third byte is the blue component of the pixel
   color value"

IOW, the TPIXEL format is fixed on the wire, regardless of what
the VNC client declare as its endianness.

Since the VNC pixel encoding code was failing to honour the endian
flag of the client, the tight_pack24 method was always operating
on data in native endianness. Its impl cancelled out the VNC pixel
encoding bug.

With the VNC pixel encoding code now fixed, the tight_pack24 method
needs to take into account that it is operating on data in client
endianness, not native endianness. It thus may need to invert the
pixel shifts.

Reviewed-by: Philippe Mathieu-Daudé <philmd@linaro.org>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
 include/ui/qemu-pixman.h |  4 ++--
 ui/qemu-pixman.c         | 15 ++++++++-------
 ui/vnc-enc-tight.c       |  2 +-
 ui/vnc.c                 |  3 ++-
 4 files changed, 13 insertions(+), 11 deletions(-)

diff --git a/include/ui/qemu-pixman.h b/include/ui/qemu-pixman.h
index 193bc046d1..2ca0ed7029 100644
--- a/include/ui/qemu-pixman.h
+++ b/include/ui/qemu-pixman.h
@@ -75,12 +75,12 @@ PixelFormat qemu_pixelformat_from_pixman(pixman_format_code_t format);
 pixman_format_code_t qemu_default_pixman_format(int bpp, bool native_endian);
 pixman_format_code_t qemu_drm_format_to_pixman(uint32_t drm_format);
 uint32_t qemu_pixman_to_drm_format(pixman_format_code_t pixman);
-int qemu_pixman_get_type(int rshift, int gshift, int bshift);
+int qemu_pixman_get_type(int rshift, int gshift, int bshift, int endian);
 bool qemu_pixman_check_format(DisplayChangeListener *dcl,
                               pixman_format_code_t format);
 
 #ifdef CONFIG_PIXMAN
-pixman_format_code_t qemu_pixman_get_format(PixelFormat *pf);
+pixman_format_code_t qemu_pixman_get_format(PixelFormat *pf, int endian);
 pixman_image_t *qemu_pixman_linebuf_create(pixman_format_code_t format,
                                            int width);
 void qemu_pixman_linebuf_fill(pixman_image_t *linebuf, pixman_image_t *fb,
diff --git a/ui/qemu-pixman.c b/ui/qemu-pixman.c
index 6ef4376f4e..ef4e71da11 100644
--- a/ui/qemu-pixman.c
+++ b/ui/qemu-pixman.c
@@ -126,33 +126,34 @@ uint32_t qemu_pixman_to_drm_format(pixman_format_code_t pixman_format)
     return 0;
 }
 
-int qemu_pixman_get_type(int rshift, int gshift, int bshift)
+int qemu_pixman_get_type(int rshift, int gshift, int bshift, int endian)
 {
     int type = PIXMAN_TYPE_OTHER;
+    bool native_endian = (endian == G_BYTE_ORDER);
 
     if (rshift > gshift && gshift > bshift) {
         if (bshift == 0) {
-            type = PIXMAN_TYPE_ARGB;
+            type = native_endian ? PIXMAN_TYPE_ARGB : PIXMAN_TYPE_BGRA;
         } else {
-            type = PIXMAN_TYPE_RGBA;
+            type = native_endian ? PIXMAN_TYPE_RGBA : PIXMAN_TYPE_ABGR;
         }
     } else if (rshift < gshift && gshift < bshift) {
         if (rshift == 0) {
-            type = PIXMAN_TYPE_ABGR;
+            type = native_endian ? PIXMAN_TYPE_ABGR : PIXMAN_TYPE_RGBA;
         } else {
-            type = PIXMAN_TYPE_BGRA;
+            type = native_endian ? PIXMAN_TYPE_BGRA : PIXMAN_TYPE_ARGB;
         }
     }
     return type;
 }
 
 #ifdef CONFIG_PIXMAN
-pixman_format_code_t qemu_pixman_get_format(PixelFormat *pf)
+pixman_format_code_t qemu_pixman_get_format(PixelFormat *pf, int endian)
 {
     pixman_format_code_t format;
     int type;
 
-    type = qemu_pixman_get_type(pf->rshift, pf->gshift, pf->bshift);
+    type = qemu_pixman_get_type(pf->rshift, pf->gshift, pf->bshift, endian);
     format = PIXMAN_FORMAT(pf->bits_per_pixel, type,
                            pf->abits, pf->rbits, pf->gbits, pf->bbits);
     if (!pixman_format_supported_source(format)) {
diff --git a/ui/vnc-enc-tight.c b/ui/vnc-enc-tight.c
index f8aaa8f346..a5bdc19ebb 100644
--- a/ui/vnc-enc-tight.c
+++ b/ui/vnc-enc-tight.c
@@ -891,7 +891,7 @@ static void tight_pack24(VncState *vs, uint8_t *buf, size_t count, size_t *ret)
 
     buf8 = buf;
 
-    if (1 /* FIXME */) {
+    if (vs->client_endian == G_BYTE_ORDER) {
         rshift = vs->client_pf.rshift;
         gshift = vs->client_pf.gshift;
         bshift = vs->client_pf.bshift;
diff --git a/ui/vnc.c b/ui/vnc.c
index ab18172c4d..d095cd7da3 100644
--- a/ui/vnc.c
+++ b/ui/vnc.c
@@ -2240,7 +2240,8 @@ static void set_encodings(VncState *vs, int32_t *encodings, size_t n_encodings)
 
 static void set_pixel_conversion(VncState *vs)
 {
-    pixman_format_code_t fmt = qemu_pixman_get_format(&vs->client_pf);
+    pixman_format_code_t fmt = qemu_pixman_get_format(&vs->client_pf,
+                                                      vs->client_endian);
 
     if (fmt == VNC_SERVER_FB_FORMAT) {
         vs->write_pixels = vnc_write_pixels_copy;
-- 
2.49.0


Re: [PULL 02/23] ui/vnc: take account of client byte order in pixman format
Posted by Thomas Huth 5 months, 2 weeks ago
On 22/05/2025 12.29, Daniel P. Berrangé wrote:
> The set_pixel_conversion() method is responsible for determining whether
> the VNC client pixel format matches the server format, and thus whether
> we can use the fast path "copy" impl for sending pixels, or must use
> the generic impl with bit swizzling.
> 
> The VNC server format is set at build time to VNC_SERVER_FB_FORMAT,
> which corresponds to PIXMAN_x8r8g8b8.
> 
> The qemu_pixman_get_format() method is then responsible for converting
> the VNC pixel format into a pixman format.
> 
> The VNC client pixel shifts are relative to the associated endianness.
> 
> The pixman formats are always relative to the host native endianness.
> 
> The qemu_pixman_get_format() method does not take into account the
> VNC client endianness, and is thus returning a pixman format that is
> only valid with the host endianness matches that of the VNC client.
...

  Hi Daniel,

this patch breaks the output in the TigerVNC viewer for me.
If I run "./qemu-system-x86_64 -vnc :1" on my laptop, and then connect to it 
via "vncviewer :1", the output of the BIOS now appears in yellow letters 
(instead of grey ones).

FWIW, the output of TigerVNC viewer is:

  TigerVNC viewer v1.15.0
  Built on: 2025-04-08 00:00
  Copyright (C) 1999-2025 TigerVNC team and many others (see README.rst)
  See https://www.tigervnc.org for information on TigerVNC.

  Tue Jun  3 13:17:50 2025
   DecodeManager: Detected 16 CPU core(s)
   DecodeManager: Creating 4 decoder thread(s)
   CConn:       Connected to host localhost port 5901
   CConnection: Server supports RFB protocol version 3.8
   CConnection: Using RFB protocol version 3.8
   CConnection: Choosing security type None(1)
   CConn:       Using pixel format depth 24 (32bpp) little-endian rgb888

Could you please have a look what's going wrong here?

  Thanks,
   Thomas


Re: [PULL 02/23] ui/vnc: take account of client byte order in pixman format
Posted by Daniel P. Berrangé 5 months, 2 weeks ago
On Tue, Jun 03, 2025 at 01:18:55PM +0200, Thomas Huth wrote:
> On 22/05/2025 12.29, Daniel P. Berrangé wrote:
> > The set_pixel_conversion() method is responsible for determining whether
> > the VNC client pixel format matches the server format, and thus whether
> > we can use the fast path "copy" impl for sending pixels, or must use
> > the generic impl with bit swizzling.
> > 
> > The VNC server format is set at build time to VNC_SERVER_FB_FORMAT,
> > which corresponds to PIXMAN_x8r8g8b8.
> > 
> > The qemu_pixman_get_format() method is then responsible for converting
> > the VNC pixel format into a pixman format.
> > 
> > The VNC client pixel shifts are relative to the associated endianness.
> > 
> > The pixman formats are always relative to the host native endianness.
> > 
> > The qemu_pixman_get_format() method does not take into account the
> > VNC client endianness, and is thus returning a pixman format that is
> > only valid with the host endianness matches that of the VNC client.
> ...
> 
>  Hi Daniel,
> 
> this patch breaks the output in the TigerVNC viewer for me.
> If I run "./qemu-system-x86_64 -vnc :1" on my laptop, and then connect to it
> via "vncviewer :1", the output of the BIOS now appears in yellow letters
> (instead of grey ones).

It turns out that historically we never set the 'client_be' flag
when a client does NOT send a "set pixel format" message. By luck
this was OK for little endian platforms as the default value of
0 matched little endian.

When I replaced 'client_be' with "client_endian", the default
value of 0 matches neither big or little endian.

I didn't see this with remote-viewer as it unconditionally
sends "set pixel format", but tigervnc always uses the server's
default pixel format.

So this patch is fine, but it exposes a pre-existing latent
bug there was probably causing problems on big endian platforms
in the past, but now causes problems on little endian platforms.

With regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|


Re: [PULL 02/23] ui/vnc: take account of client byte order in pixman format
Posted by Philippe Mathieu-Daudé 5 months, 2 weeks ago
On 3/6/25 15:49, Daniel P. Berrangé wrote:
> On Tue, Jun 03, 2025 at 01:18:55PM +0200, Thomas Huth wrote:
>> On 22/05/2025 12.29, Daniel P. Berrangé wrote:
>>> The set_pixel_conversion() method is responsible for determining whether
>>> the VNC client pixel format matches the server format, and thus whether
>>> we can use the fast path "copy" impl for sending pixels, or must use
>>> the generic impl with bit swizzling.
>>>
>>> The VNC server format is set at build time to VNC_SERVER_FB_FORMAT,
>>> which corresponds to PIXMAN_x8r8g8b8.
>>>
>>> The qemu_pixman_get_format() method is then responsible for converting
>>> the VNC pixel format into a pixman format.
>>>
>>> The VNC client pixel shifts are relative to the associated endianness.
>>>
>>> The pixman formats are always relative to the host native endianness.
>>>
>>> The qemu_pixman_get_format() method does not take into account the
>>> VNC client endianness, and is thus returning a pixman format that is
>>> only valid with the host endianness matches that of the VNC client.
>> ...
>>
>>   Hi Daniel,
>>
>> this patch breaks the output in the TigerVNC viewer for me.
>> If I run "./qemu-system-x86_64 -vnc :1" on my laptop, and then connect to it
>> via "vncviewer :1", the output of the BIOS now appears in yellow letters
>> (instead of grey ones).
> 
> It turns out that historically we never set the 'client_be' flag
> when a client does NOT send a "set pixel format" message. By luck
> this was OK for little endian platforms as the default value of
> 0 matched little endian.
> 
> When I replaced 'client_be' with "client_endian", the default
> value of 0 matches neither big or little endian.
> 
> I didn't see this with remote-viewer as it unconditionally
> sends "set pixel format", but tigervnc always uses the server's
> default pixel format.
> 
> So this patch is fine, but it exposes a pre-existing latent
> bug there was probably causing problems on big endian platforms
> in the past, but now causes problems on little endian platforms.

Nice :)


Re: [PULL 02/23] ui/vnc: take account of client byte order in pixman format
Posted by Daniel P. Berrangé 5 months, 2 weeks ago
On Tue, Jun 03, 2025 at 01:18:55PM +0200, Thomas Huth wrote:
> On 22/05/2025 12.29, Daniel P. Berrangé wrote:
> > The set_pixel_conversion() method is responsible for determining whether
> > the VNC client pixel format matches the server format, and thus whether
> > we can use the fast path "copy" impl for sending pixels, or must use
> > the generic impl with bit swizzling.
> > 
> > The VNC server format is set at build time to VNC_SERVER_FB_FORMAT,
> > which corresponds to PIXMAN_x8r8g8b8.
> > 
> > The qemu_pixman_get_format() method is then responsible for converting
> > the VNC pixel format into a pixman format.
> > 
> > The VNC client pixel shifts are relative to the associated endianness.
> > 
> > The pixman formats are always relative to the host native endianness.
> > 
> > The qemu_pixman_get_format() method does not take into account the
> > VNC client endianness, and is thus returning a pixman format that is
> > only valid with the host endianness matches that of the VNC client.
> ...
> 
>  Hi Daniel,
> 
> this patch breaks the output in the TigerVNC viewer for me.
> If I run "./qemu-system-x86_64 -vnc :1" on my laptop, and then connect to it
> via "vncviewer :1", the output of the BIOS now appears in yellow letters
> (instead of grey ones).
> 
> FWIW, the output of TigerVNC viewer is:
> 
>  TigerVNC viewer v1.15.0
>  Built on: 2025-04-08 00:00
>  Copyright (C) 1999-2025 TigerVNC team and many others (see README.rst)
>  See https://www.tigervnc.org for information on TigerVNC.
> 
>  Tue Jun  3 13:17:50 2025
>   DecodeManager: Detected 16 CPU core(s)
>   DecodeManager: Creating 4 decoder thread(s)
>   CConn:       Connected to host localhost port 5901
>   CConnection: Server supports RFB protocol version 3.8
>   CConnection: Using RFB protocol version 3.8
>   CConnection: Choosing security type None(1)
>   CConn:       Using pixel format depth 24 (32bpp) little-endian rgb888
> 
> Could you please have a look what's going wrong here?

Yes, I can reproduce too, will check this out.


With regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|