Blame SOURCES/kvm-ui-fix-VNC-client-throttling-when-forced-update-is-r.patch

4a2fec
From bf44407132cb0c39e59bb2f7f72bde9a7b085c70 Mon Sep 17 00:00:00 2001
4a2fec
From: "Daniel P. Berrange" <berrange@redhat.com>
4a2fec
Date: Mon, 5 Feb 2018 11:10:07 +0100
4a2fec
Subject: [PATCH 13/20] ui: fix VNC client throttling when forced update is
4a2fec
 requested
4a2fec
MIME-Version: 1.0
4a2fec
Content-Type: text/plain; charset=UTF-8
4a2fec
Content-Transfer-Encoding: 8bit
4a2fec
4a2fec
RH-Author: Daniel P. Berrange <berrange@redhat.com>
4a2fec
Message-id: <20180205111012.6210-13-berrange@redhat.com>
4a2fec
Patchwork-id: 78882
4a2fec
O-Subject: [RHV-7.5 qemu-kvm-rhev PATCH v2 12/17] ui: fix VNC client throttling when forced update is requested
4a2fec
Bugzilla: 1527404
4a2fec
RH-Acked-by: Gerd Hoffmann <kraxel@redhat.com>
4a2fec
RH-Acked-by: Laszlo Ersek <lersek@redhat.com>
4a2fec
RH-Acked-by: Thomas Huth <thuth@redhat.com>
4a2fec
4a2fec
From: "Daniel P. Berrange" <berrange@redhat.com>
4a2fec
4a2fec
The VNC server must throttle data sent to the client to prevent the 'output'
4a2fec
buffer size growing without bound, if the client stops reading data off the
4a2fec
socket (either maliciously or due to stalled/slow network connection).
4a2fec
4a2fec
The current throttling is very crude because it simply checks whether the
4a2fec
output buffer offset is zero. This check is disabled if the client has requested
4a2fec
a forced update, because we want to send these as soon as possible.
4a2fec
4a2fec
As a result, the VNC client can cause QEMU to allocate arbitrary amounts of RAM.
4a2fec
They can first start something in the guest that triggers lots of framebuffer
4a2fec
updates eg play a youtube video. Then repeatedly send full framebuffer update
4a2fec
requests, but never read data back from the server. This can easily make QEMU's
4a2fec
VNC server send buffer consume 100MB of RAM per second, until the OOM killer
4a2fec
starts reaping processes (hopefully the rogue QEMU process, but it might pick
4a2fec
others...).
4a2fec
4a2fec
To address this we make the throttling more intelligent, so we can throttle
4a2fec
full updates. When we get a forced update request, we keep track of exactly how
4a2fec
much data we put on the output buffer. We will not process a subsequent forced
4a2fec
update request until this data has been fully sent on the wire. We always allow
4a2fec
one forced update request to be in flight, regardless of what data is queued
4a2fec
for incremental updates or audio data. The slight complication is that we do
4a2fec
not initially know how much data an update will send, as this is done in the
4a2fec
background by the VNC job thread. So we must track the fact that the job thread
4a2fec
has an update pending, and not process any further updates until this job is
4a2fec
has been completed & put data on the output buffer.
4a2fec
4a2fec
This unbounded memory growth affects all VNC server configurations supported by
4a2fec
QEMU, with no workaround possible. The mitigating factor is that it can only be
4a2fec
triggered by a client that has authenticated with the VNC server, and who is
4a2fec
able to trigger a large quantity of framebuffer updates or audio samples from
4a2fec
the guest OS. Mostly they'll just succeed in getting the OOM killer to kill
4a2fec
their own QEMU process, but its possible other processes can get taken out as
4a2fec
collateral damage.
4a2fec
4a2fec
This is a more general variant of the similar unbounded memory usage flaw in
4a2fec
the websockets server, that was previously assigned CVE-2017-15268, and fixed
4a2fec
in 2.11 by:
4a2fec
4a2fec
  commit a7b20a8efa28e5f22c26c06cd06c2f12bc863493
4a2fec
  Author: Daniel P. Berrange <berrange@redhat.com>
4a2fec
  Date:   Mon Oct 9 14:43:42 2017 +0100
4a2fec
4a2fec
    io: monitor encoutput buffer size from websocket GSource
4a2fec
4a2fec
This new general memory usage flaw has been assigned CVE-2017-15124, and is
4a2fec
partially fixed by this patch.
4a2fec
4a2fec
Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
4a2fec
Reviewed-by: Darren Kenny <darren.kenny@oracle.com>
4a2fec
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
4a2fec
Message-id: 20171218191228.31018-11-berrange@redhat.com
4a2fec
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
4a2fec
(cherry picked from commit ada8d2e4369ea49677d8672ac81bce73eefd5b54)
4a2fec
Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com>
4a2fec
---
4a2fec
 ui/vnc-auth-sasl.c |  5 +++++
4a2fec
 ui/vnc-jobs.c      |  5 +++++
4a2fec
 ui/vnc.c           | 28 ++++++++++++++++++++++++----
4a2fec
 ui/vnc.h           |  7 +++++++
4a2fec
 4 files changed, 41 insertions(+), 4 deletions(-)
4a2fec
4a2fec
diff --git a/ui/vnc-auth-sasl.c b/ui/vnc-auth-sasl.c
4a2fec
index 761493b..8c1cdde 100644
4a2fec
--- a/ui/vnc-auth-sasl.c
4a2fec
+++ b/ui/vnc-auth-sasl.c
4a2fec
@@ -79,6 +79,11 @@ long vnc_client_write_sasl(VncState *vs)
4a2fec
 
4a2fec
     vs->sasl.encodedOffset += ret;
4a2fec
     if (vs->sasl.encodedOffset == vs->sasl.encodedLength) {
4a2fec
+        if (vs->sasl.encodedRawLength >= vs->force_update_offset) {
4a2fec
+            vs->force_update_offset = 0;
4a2fec
+        } else {
4a2fec
+            vs->force_update_offset -= vs->sasl.encodedRawLength;
4a2fec
+        }
4a2fec
         vs->output.offset -= vs->sasl.encodedRawLength;
4a2fec
         vs->sasl.encoded = NULL;
4a2fec
         vs->sasl.encodedOffset = vs->sasl.encodedLength = 0;
4a2fec
diff --git a/ui/vnc-jobs.c b/ui/vnc-jobs.c
4a2fec
index f786777..e326679 100644
4a2fec
--- a/ui/vnc-jobs.c
4a2fec
+++ b/ui/vnc-jobs.c
4a2fec
@@ -152,6 +152,11 @@ void vnc_jobs_consume_buffer(VncState *vs)
4a2fec
                 vs->ioc, G_IO_IN | G_IO_OUT, vnc_client_io, vs, NULL);
4a2fec
         }
4a2fec
         buffer_move(&vs->output, &vs->jobs_buffer);
4a2fec
+
4a2fec
+        if (vs->job_update == VNC_STATE_UPDATE_FORCE) {
4a2fec
+            vs->force_update_offset = vs->output.offset;
4a2fec
+        }
4a2fec
+        vs->job_update = VNC_STATE_UPDATE_NONE;
4a2fec
     }
4a2fec
     flush = vs->ioc != NULL && vs->abort != true;
4a2fec
     vnc_unlock_output(vs);
4a2fec
diff --git a/ui/vnc.c b/ui/vnc.c
4a2fec
index e132b74..7f2210f 100644
4a2fec
--- a/ui/vnc.c
4a2fec
+++ b/ui/vnc.c
4a2fec
@@ -1021,14 +1021,28 @@ static bool vnc_should_update(VncState *vs)
4a2fec
         break;
4a2fec
     case VNC_STATE_UPDATE_INCREMENTAL:
4a2fec
         /* Only allow incremental updates if the pending send queue
4a2fec
-         * is less than the permitted threshold
4a2fec
+         * is less than the permitted threshold, and the job worker
4a2fec
+         * is completely idle.
4a2fec
          */
4a2fec
-        if (vs->output.offset < vs->throttle_output_offset) {
4a2fec
+        if (vs->output.offset < vs->throttle_output_offset &&
4a2fec
+            vs->job_update == VNC_STATE_UPDATE_NONE) {
4a2fec
             return true;
4a2fec
         }
4a2fec
         break;
4a2fec
     case VNC_STATE_UPDATE_FORCE:
4a2fec
-        return true;
4a2fec
+        /* Only allow forced updates if the pending send queue
4a2fec
+         * does not contain a previous forced update, and the
4a2fec
+         * job worker is completely idle.
4a2fec
+         *
4a2fec
+         * Note this means we'll queue a forced update, even if
4a2fec
+         * the output buffer size is otherwise over the throttle
4a2fec
+         * output limit.
4a2fec
+         */
4a2fec
+        if (vs->force_update_offset == 0 &&
4a2fec
+            vs->job_update == VNC_STATE_UPDATE_NONE) {
4a2fec
+            return true;
4a2fec
+        }
4a2fec
+        break;
4a2fec
     }
4a2fec
     return false;
4a2fec
 }
4a2fec
@@ -1096,8 +1110,9 @@ static int vnc_update_client(VncState *vs, int has_dirty)
4a2fec
         }
4a2fec
     }
4a2fec
 
4a2fec
-    vnc_job_push(job);
4a2fec
+    vs->job_update = vs->update;
4a2fec
     vs->update = VNC_STATE_UPDATE_NONE;
4a2fec
+    vnc_job_push(job);
4a2fec
     vs->has_dirty = 0;
4a2fec
     return n;
4a2fec
 }
4a2fec
@@ -1332,6 +1347,11 @@ static ssize_t vnc_client_write_plain(VncState *vs)
4a2fec
     if (!ret)
4a2fec
         return 0;
4a2fec
 
4a2fec
+    if (ret >= vs->force_update_offset) {
4a2fec
+        vs->force_update_offset = 0;
4a2fec
+    } else {
4a2fec
+        vs->force_update_offset -= ret;
4a2fec
+    }
4a2fec
     buffer_advance(&vs->output, ret);
4a2fec
 
4a2fec
     if (vs->output.offset == 0) {
4a2fec
diff --git a/ui/vnc.h b/ui/vnc.h
4a2fec
index 8fe6959..3f4cd4d 100644
4a2fec
--- a/ui/vnc.h
4a2fec
+++ b/ui/vnc.h
4a2fec
@@ -271,6 +271,7 @@ struct VncState
4a2fec
 
4a2fec
     VncDisplay *vd;
4a2fec
     VncStateUpdate update; /* Most recent pending request from client */
4a2fec
+    VncStateUpdate job_update; /* Currently processed by job thread */
4a2fec
     int has_dirty;
4a2fec
     uint32_t features;
4a2fec
     int absolute;
4a2fec
@@ -298,6 +299,12 @@ struct VncState
4a2fec
 
4a2fec
     VncClientInfo *info;
4a2fec
 
4a2fec
+    /* Job thread bottom half has put data for a forced update
4a2fec
+     * into the output buffer. This offset points to the end of
4a2fec
+     * the update data in the output buffer. This lets us determine
4a2fec
+     * when a force update is fully sent to the client, allowing
4a2fec
+     * us to process further forced updates. */
4a2fec
+    size_t force_update_offset;
4a2fec
     /* We allow multiple incremental updates or audio capture
4a2fec
      * samples to be queued in output buffer, provided the
4a2fec
      * buffer size doesn't exceed this threshold. The value
4a2fec
-- 
4a2fec
1.8.3.1
4a2fec