9ae3a8
From a12ad57f4b108dc98987b2002169ccfbbd76721d Mon Sep 17 00:00:00 2001
9ae3a8
From: "Daniel P. Berrange" <berrange@redhat.com>
9ae3a8
Date: Thu, 8 Feb 2018 17:50:36 +0100
9ae3a8
Subject: [PATCH 22/27] ui: fix VNC client throttling when audio capture is
9ae3a8
 active
9ae3a8
MIME-Version: 1.0
9ae3a8
Content-Type: text/plain; charset=UTF-8
9ae3a8
Content-Transfer-Encoding: 8bit
9ae3a8
9ae3a8
RH-Author: Daniel P. Berrange <berrange@redhat.com>
9ae3a8
Message-id: <20180208175041.5634-23-berrange@redhat.com>
9ae3a8
Patchwork-id: 78958
9ae3a8
O-Subject: [RHEL-7.5 qemu-kvm PATCH v1 22/27] ui: fix VNC client throttling when audio capture is active
9ae3a8
Bugzilla: 1527405
9ae3a8
RH-Acked-by: Laszlo Ersek <lersek@redhat.com>
9ae3a8
RH-Acked-by: Gerd Hoffmann <kraxel@redhat.com>
9ae3a8
RH-Acked-by: Miroslav Rezanina <mrezanin@redhat.com>
9ae3a8
9ae3a8
From: "Daniel P. Berrange" <berrange@redhat.com>
9ae3a8
9ae3a8
The VNC server must throttle data sent to the client to prevent the 'output'
9ae3a8
buffer size growing without bound, if the client stops reading data off the
9ae3a8
socket (either maliciously or due to stalled/slow network connection).
9ae3a8
9ae3a8
The current throttling is very crude because it simply checks whether the
9ae3a8
output buffer offset is zero. This check must be disabled if audio capture is
9ae3a8
enabled, because when streaming audio the output buffer offset will rarely be
9ae3a8
zero due to queued audio data, and so this would starve framebuffer updates.
9ae3a8
9ae3a8
As a result, the VNC client can cause QEMU to allocate arbitrary amounts of RAM.
9ae3a8
They can first start something in the guest that triggers lots of framebuffer
9ae3a8
updates eg play a youtube video. Then enable audio capture, and simply never
9ae3a8
read data back from the server. This can easily make QEMU's VNC server send
9ae3a8
buffer consume 100MB of RAM per second, until the OOM killer starts reaping
9ae3a8
processes (hopefully the rogue QEMU process, but it might pick others...).
9ae3a8
9ae3a8
To address this we make the throttling more intelligent, so we can throttle
9ae3a8
when audio capture is active too. To determine how to throttle incremental
9ae3a8
updates or audio data, we calculate a size threshold. Normally the threshold is
9ae3a8
the approximate number of bytes associated with a single complete framebuffer
9ae3a8
update. ie width * height * bytes per pixel. We'll send incremental updates
9ae3a8
until we hit this threshold, at which point we'll stop sending updates until
9ae3a8
data has been written to the wire, causing the output buffer offset to fall
9ae3a8
back below the threshold.
9ae3a8
9ae3a8
If audio capture is enabled, we increase the size of the threshold to also
9ae3a8
allow for upto 1 seconds worth of audio data samples. ie nchannels * bytes
9ae3a8
per sample * frequency. This allows the output buffer to have a mixture of
9ae3a8
incremental framebuffer updates and audio data queued, but once the threshold
9ae3a8
is exceeded, audio data will be dropped and incremental updates will be
9ae3a8
throttled.
9ae3a8
9ae3a8
This unbounded memory growth affects all VNC server configurations supported by
9ae3a8
QEMU, with no workaround possible. The mitigating factor is that it can only be
9ae3a8
triggered by a client that has authenticated with the VNC server, and who is
9ae3a8
able to trigger a large quantity of framebuffer updates or audio samples from
9ae3a8
the guest OS. Mostly they'll just succeed in getting the OOM killer to kill
9ae3a8
their own QEMU process, but its possible other processes can get taken out as
9ae3a8
collateral damage.
9ae3a8
9ae3a8
This is a more general variant of the similar unbounded memory usage flaw in
9ae3a8
the websockets server, that was previously assigned CVE-2017-15268, and fixed
9ae3a8
in 2.11 by:
9ae3a8
9ae3a8
  commit a7b20a8efa28e5f22c26c06cd06c2f12bc863493
9ae3a8
  Author: Daniel P. Berrange <berrange@redhat.com>
9ae3a8
  Date:   Mon Oct 9 14:43:42 2017 +0100
9ae3a8
9ae3a8
    io: monitor encoutput buffer size from websocket GSource
9ae3a8
9ae3a8
This new general memory usage flaw has been assigned CVE-2017-15124, and is
9ae3a8
partially fixed by this patch.
9ae3a8
9ae3a8
RHEL-7 note: minimal context differences in "struct VncState" due to
9ae3a8
downstream lacking (a) commit fb6ba0d5256c ("qapi event: convert VNC
9ae3a8
events", 2014-06-23), part of v2.1.0, and (b) commit 8e9b0d24fb98 ("ui:
9ae3a8
convert VNC websockets to use crypto APIs", 2015-07-08), part of v2.4.0.
9ae3a8
9ae3a8
Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
9ae3a8
Reviewed-by: Darren Kenny <darren.kenny@oracle.com>
9ae3a8
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
9ae3a8
Message-id: 20171218191228.31018-10-berrange@redhat.com
9ae3a8
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
9ae3a8
(cherry picked from commit e2b72cb6e0443d90d7ab037858cb6834b6cca852)
9ae3a8
Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com>
9ae3a8
---
9ae3a8
 ui/vnc.c | 72 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
9ae3a8
 ui/vnc.h |  6 ++++++
9ae3a8
 2 files changed, 70 insertions(+), 8 deletions(-)
9ae3a8
9ae3a8
diff --git a/ui/vnc.c b/ui/vnc.c
9ae3a8
index a7ec8cc..952a051 100644
9ae3a8
--- a/ui/vnc.c
9ae3a8
+++ b/ui/vnc.c
9ae3a8
@@ -47,6 +47,7 @@ static VncDisplay *vnc_display; /* needed for info vnc */
9ae3a8
 
9ae3a8
 static int vnc_cursor_define(VncState *vs);
9ae3a8
 static void vnc_release_modifiers(VncState *vs);
9ae3a8
+static void vnc_update_throttle_offset(VncState *vs);
9ae3a8
 
9ae3a8
 static void vnc_set_share_mode(VncState *vs, VncShareMode mode)
9ae3a8
 {
9ae3a8
@@ -664,6 +665,7 @@ static void vnc_dpy_switch(DisplayChangeListener *dcl,
9ae3a8
         vnc_set_area_dirty(vs->dirty, vd, 0, 0,
9ae3a8
                            vnc_width(vd),
9ae3a8
                            vnc_height(vd));
9ae3a8
+        vnc_update_throttle_offset(vs);
9ae3a8
     }
9ae3a8
 }
9ae3a8
 
9ae3a8
@@ -846,16 +848,67 @@ static int find_and_clear_dirty_height(struct VncState *vs,
9ae3a8
     return h;
9ae3a8
 }
9ae3a8
 
9ae3a8
+/*
9ae3a8
+ * Figure out how much pending data we should allow in the output
9ae3a8
+ * buffer before we throttle incremental display updates, and/or
9ae3a8
+ * drop audio samples.
9ae3a8
+ *
9ae3a8
+ * We allow for equiv of 1 full display's worth of FB updates,
9ae3a8
+ * and 1 second of audio samples. If audio backlog was larger
9ae3a8
+ * than that the client would already suffering awful audio
9ae3a8
+ * glitches, so dropping samples is no worse really).
9ae3a8
+ */
9ae3a8
+static void vnc_update_throttle_offset(VncState *vs)
9ae3a8
+{
9ae3a8
+    size_t offset =
9ae3a8
+        vs->client_width * vs->client_height * vs->client_pf.bytes_per_pixel;
9ae3a8
+
9ae3a8
+    if (vs->audio_cap) {
9ae3a8
+        int freq = vs->as.freq;
9ae3a8
+        /* We don't limit freq when reading settings from client, so
9ae3a8
+         * it could be upto MAX_INT in size. 48khz is a sensible
9ae3a8
+         * upper bound for trustworthy clients */
9ae3a8
+        int bps;
9ae3a8
+        if (freq > 48000) {
9ae3a8
+            freq = 48000;
9ae3a8
+        }
9ae3a8
+        switch (vs->as.fmt) {
9ae3a8
+        default:
9ae3a8
+        case  AUD_FMT_U8:
9ae3a8
+        case  AUD_FMT_S8:
9ae3a8
+            bps = 1;
9ae3a8
+            break;
9ae3a8
+        case  AUD_FMT_U16:
9ae3a8
+        case  AUD_FMT_S16:
9ae3a8
+            bps = 2;
9ae3a8
+            break;
9ae3a8
+        case  AUD_FMT_U32:
9ae3a8
+        case  AUD_FMT_S32:
9ae3a8
+            bps = 4;
9ae3a8
+            break;
9ae3a8
+        }
9ae3a8
+        offset += freq * bps * vs->as.nchannels;
9ae3a8
+    }
9ae3a8
+
9ae3a8
+    /* Put a floor of 1MB on offset, so that if we have a large pending
9ae3a8
+     * buffer and the display is resized to a small size & back again
9ae3a8
+     * we don't suddenly apply a tiny send limit
9ae3a8
+     */
9ae3a8
+    offset = MAX(offset, 1024 * 1024);
9ae3a8
+
9ae3a8
+    vs->throttle_output_offset = offset;
9ae3a8
+}
9ae3a8
+
9ae3a8
 static bool vnc_should_update(VncState *vs)
9ae3a8
 {
9ae3a8
     switch (vs->update) {
9ae3a8
     case VNC_STATE_UPDATE_NONE:
9ae3a8
         break;
9ae3a8
     case VNC_STATE_UPDATE_INCREMENTAL:
9ae3a8
-        /* Only allow incremental updates if the output buffer
9ae3a8
-         * is empty, or if audio capture is enabled.
9ae3a8
+        /* Only allow incremental updates if the pending send queue
9ae3a8
+         * is less than the permitted threshold
9ae3a8
          */
9ae3a8
-        if (!vs->output.offset || vs->audio_cap) {
9ae3a8
+        if (vs->output.offset < vs->throttle_output_offset) {
9ae3a8
             return true;
9ae3a8
         }
9ae3a8
         break;
9ae3a8
@@ -963,11 +1016,13 @@ static void audio_capture(void *opaque, void *buf, int size)
9ae3a8
     VncState *vs = opaque;
9ae3a8
 
9ae3a8
     vnc_lock_output(vs);
9ae3a8
-    vnc_write_u8(vs, VNC_MSG_SERVER_QEMU);
9ae3a8
-    vnc_write_u8(vs, VNC_MSG_SERVER_QEMU_AUDIO);
9ae3a8
-    vnc_write_u16(vs, VNC_MSG_SERVER_QEMU_AUDIO_DATA);
9ae3a8
-    vnc_write_u32(vs, size);
9ae3a8
-    vnc_write(vs, buf, size);
9ae3a8
+    if (vs->output.offset < vs->throttle_output_offset) {
9ae3a8
+        vnc_write_u8(vs, VNC_MSG_SERVER_QEMU);
9ae3a8
+        vnc_write_u8(vs, VNC_MSG_SERVER_QEMU_AUDIO);
9ae3a8
+        vnc_write_u16(vs, VNC_MSG_SERVER_QEMU_AUDIO_DATA);
9ae3a8
+        vnc_write_u32(vs, size);
9ae3a8
+        vnc_write(vs, buf, size);
9ae3a8
+    }
9ae3a8
     vnc_unlock_output(vs);
9ae3a8
     vnc_flush(vs);
9ae3a8
 }
9ae3a8
@@ -2214,6 +2269,7 @@ static int protocol_client_msg(VncState *vs, uint8_t *data, size_t len)
9ae3a8
         break;
9ae3a8
     }
9ae3a8
 
9ae3a8
+    vnc_update_throttle_offset(vs);
9ae3a8
     vnc_read_when(vs, protocol_client_msg, 1);
9ae3a8
     return 0;
9ae3a8
 }
9ae3a8
diff --git a/ui/vnc.h b/ui/vnc.h
9ae3a8
index f19fd0a..d7eede3 100644
9ae3a8
--- a/ui/vnc.h
9ae3a8
+++ b/ui/vnc.h
9ae3a8
@@ -301,6 +301,12 @@ struct VncState
9ae3a8
 
9ae3a8
     QObject *info;
9ae3a8
 
9ae3a8
+    /* We allow multiple incremental updates or audio capture
9ae3a8
+     * samples to be queued in output buffer, provided the
9ae3a8
+     * buffer size doesn't exceed this threshold. The value
9ae3a8
+     * is calculating dynamically based on framebuffer size
9ae3a8
+     * and audio sample settings in vnc_update_throttle_offset() */
9ae3a8
+    size_t throttle_output_offset;
9ae3a8
     Buffer output;
9ae3a8
     Buffer input;
9ae3a8
 #ifdef CONFIG_VNC_WS
9ae3a8
-- 
9ae3a8
1.8.3.1
9ae3a8