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

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