vnc: limit memory usage (CVE-2017-15124)
-----BEGIN PGP SIGNATURE----- Version: GnuPG v2.0.22 (GNU/Linux) iQIcBAABAgAGBQJaWLCyAAoJEEy22O7T6HE4Ml4P/jPi2kCJ6pZCzOSkPqxQv5HU ScUIVidH4pvLQnyhGUNTYxkd7RwlG9M4LKoy6U0JTs6rh3/Jb91H/yX7EtXi7JkP vxuKO3UehDjnlRyS+g4VE/+VBJB4V4XTRqK7BNWFqpxLd+DsZ6scUOwGykF4mFzQ YV8a08IL8DZ3XtPjX5W1g0I7iGPgijZVFHGtteG5r+SWG877ACzduaYBGHoXL0vM HFOfbGmXVZrRFBCom/iQLLR4fsPm3ynMMk9bPqz+Tw/z7CnObdjkrMgaPV7soZzH +SK5O+aT5jyrFk8FufDr3AuI3nz7A5maOjT4Jin9089VomnV0O1sxjDwGC7D/OH/ tBZR8qWrRQ6mSRJQo+fZvCkXBYZvOFdjow1xDQahymQkvtQWWkwVH5Fpzz474VQP tIpoZFa5KlPWsz91/tgBo43Znjn+cccw0BzGRSWsM6dqP/C+gKO3+W3cOT8W8Skj lN88F3uHQhR2QZ0s4ZWSaVr7qMTI4OFkryRM4GXqwKL685/lRV8da6A+K9xuvXro jCcsu1vc24ZCnVIJ5BFQP2Gp7Xd8iD0RYe2wQF47mY0XBR7d1CwiqHKsEVZXj+EH A7hvvuEjJYM7R/sl5Yhw9yQWDeH0HTiKZPms9p84vGh2fxABEVPsPVqSApw5yfNz oT7Mk9nPanfsnDiOQ1R0 =Bf/5 -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/kraxel/tags/vnc-20180112-pull-request' into staging vnc: limit memory usage (CVE-2017-15124) # gpg: Signature made Fri 12 Jan 2018 12:57:22 GMT # gpg: using RSA key 0x4CB6D8EED3E87138 # gpg: Good signature from "Gerd Hoffmann (work) <kraxel@redhat.com>" # gpg: aka "Gerd Hoffmann <gerd@kraxel.org>" # gpg: aka "Gerd Hoffmann (private) <kraxel@gmail.com>" # Primary key fingerprint: A032 8CFF B93A 17A7 9901 FE7D 4CB6 D8EE D3E8 7138 * remotes/kraxel/tags/vnc-20180112-pull-request: ui: mix misleading comments & return types of VNC I/O helper methods ui: add trace events related to VNC client throttling ui: place a hard cap on VNC server output buffer size ui: fix VNC client throttling when forced update is requested ui: fix VNC client throttling when audio capture is active ui: refactor code for determining if an update should be sent to the client ui: correctly reset framebuffer update state after processing dirty regions ui: introduce enum to track VNC client framebuffer update request state ui: track how much decoded data we consumed when doing SASL encoding ui: avoid pointless VNC updates if framebuffer isn't dirty ui: remove redundant indentation in vnc_client_update ui: remove unreachable code in vnc_update_client ui: remove 'sync' parameter from vnc_update_client vnc: fix debug spelling Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
		
						commit
						7398166ddf
					
				@ -35,6 +35,13 @@ vnc_client_connect(void *state, void *ioc) "VNC client connect state=%p ioc=%p"
 | 
			
		||||
vnc_client_disconnect_start(void *state, void *ioc) "VNC client disconnect start state=%p ioc=%p"
 | 
			
		||||
vnc_client_disconnect_finish(void *state, void *ioc) "VNC client disconnect finish state=%p ioc=%p"
 | 
			
		||||
vnc_client_io_wrap(void *state, void *ioc, const char *type) "VNC client I/O wrap state=%p ioc=%p type=%s"
 | 
			
		||||
vnc_client_throttle_threshold(void *state, void *ioc, size_t oldoffset, size_t offset, int client_width, int client_height, int bytes_per_pixel, void *audio_cap) "VNC client throttle threshold state=%p ioc=%p oldoffset=%zu newoffset=%zu width=%d height=%d bpp=%d audio=%p"
 | 
			
		||||
vnc_client_throttle_incremental(void *state, void *ioc, int job_update, size_t offset) "VNC client throttle incremental state=%p ioc=%p job-update=%d offset=%zu"
 | 
			
		||||
vnc_client_throttle_forced(void *state, void *ioc, int job_update, size_t offset) "VNC client throttle forced state=%p ioc=%p job-update=%d offset=%zu"
 | 
			
		||||
vnc_client_throttle_audio(void *state, void *ioc, size_t offset) "VNC client throttle audio state=%p ioc=%p offset=%zu"
 | 
			
		||||
vnc_client_unthrottle_forced(void *state, void *ioc) "VNC client unthrottle forced offset state=%p ioc=%p"
 | 
			
		||||
vnc_client_unthrottle_incremental(void *state, void *ioc, size_t offset) "VNC client unthrottle incremental state=%p ioc=%p offset=%zu"
 | 
			
		||||
vnc_client_output_limit(void *state, void *ioc, size_t offset, size_t threshold) "VNC client output limit state=%p ioc=%p offset=%zu threshold=%zu"
 | 
			
		||||
vnc_auth_init(void *display, int websock, int auth, int subauth) "VNC auth init state=%p websock=%d auth=%d subauth=%d"
 | 
			
		||||
vnc_auth_start(void *state, int method) "VNC client auth start state=%p method=%d"
 | 
			
		||||
vnc_auth_pass(void *state, int method) "VNC client auth passed state=%p method=%d"
 | 
			
		||||
 | 
			
		||||
@ -48,9 +48,9 @@ void vnc_sasl_client_cleanup(VncState *vs)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
long vnc_client_write_sasl(VncState *vs)
 | 
			
		||||
size_t vnc_client_write_sasl(VncState *vs)
 | 
			
		||||
{
 | 
			
		||||
    long ret;
 | 
			
		||||
    size_t ret;
 | 
			
		||||
 | 
			
		||||
    VNC_DEBUG("Write SASL: Pending output %p size %zd offset %zd "
 | 
			
		||||
              "Encoded: %p size %d offset %d\n",
 | 
			
		||||
@ -67,6 +67,7 @@ long vnc_client_write_sasl(VncState *vs)
 | 
			
		||||
        if (err != SASL_OK)
 | 
			
		||||
            return vnc_client_io_error(vs, -1, NULL);
 | 
			
		||||
 | 
			
		||||
        vs->sasl.encodedRawLength = vs->output.offset;
 | 
			
		||||
        vs->sasl.encodedOffset = 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@ -78,7 +79,12 @@ long vnc_client_write_sasl(VncState *vs)
 | 
			
		||||
 | 
			
		||||
    vs->sasl.encodedOffset += ret;
 | 
			
		||||
    if (vs->sasl.encodedOffset == vs->sasl.encodedLength) {
 | 
			
		||||
        vs->output.offset = 0;
 | 
			
		||||
        if (vs->sasl.encodedRawLength >= vs->force_update_offset) {
 | 
			
		||||
            vs->force_update_offset = 0;
 | 
			
		||||
        } else {
 | 
			
		||||
            vs->force_update_offset -= vs->sasl.encodedRawLength;
 | 
			
		||||
        }
 | 
			
		||||
        vs->output.offset -= vs->sasl.encodedRawLength;
 | 
			
		||||
        vs->sasl.encoded = NULL;
 | 
			
		||||
        vs->sasl.encodedOffset = vs->sasl.encodedLength = 0;
 | 
			
		||||
    }
 | 
			
		||||
@ -100,9 +106,9 @@ long vnc_client_write_sasl(VncState *vs)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
long vnc_client_read_sasl(VncState *vs)
 | 
			
		||||
size_t vnc_client_read_sasl(VncState *vs)
 | 
			
		||||
{
 | 
			
		||||
    long ret;
 | 
			
		||||
    size_t ret;
 | 
			
		||||
    uint8_t encoded[4096];
 | 
			
		||||
    const char *decoded;
 | 
			
		||||
    unsigned int decodedLen;
 | 
			
		||||
 | 
			
		||||
@ -53,6 +53,7 @@ struct VncStateSASL {
 | 
			
		||||
     */
 | 
			
		||||
    const uint8_t *encoded;
 | 
			
		||||
    unsigned int encodedLength;
 | 
			
		||||
    unsigned int encodedRawLength;
 | 
			
		||||
    unsigned int encodedOffset;
 | 
			
		||||
    char *username;
 | 
			
		||||
    char *mechlist;
 | 
			
		||||
@ -64,8 +65,8 @@ struct VncDisplaySASL {
 | 
			
		||||
 | 
			
		||||
void vnc_sasl_client_cleanup(VncState *vs);
 | 
			
		||||
 | 
			
		||||
long vnc_client_read_sasl(VncState *vs);
 | 
			
		||||
long vnc_client_write_sasl(VncState *vs);
 | 
			
		||||
size_t vnc_client_read_sasl(VncState *vs);
 | 
			
		||||
size_t vnc_client_write_sasl(VncState *vs);
 | 
			
		||||
 | 
			
		||||
void start_auth_sasl(VncState *vs);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -152,6 +152,11 @@ void vnc_jobs_consume_buffer(VncState *vs)
 | 
			
		||||
                vs->ioc, G_IO_IN | G_IO_OUT, vnc_client_io, vs, NULL);
 | 
			
		||||
        }
 | 
			
		||||
        buffer_move(&vs->output, &vs->jobs_buffer);
 | 
			
		||||
 | 
			
		||||
        if (vs->job_update == VNC_STATE_UPDATE_FORCE) {
 | 
			
		||||
            vs->force_update_offset = vs->output.offset;
 | 
			
		||||
        }
 | 
			
		||||
        vs->job_update = VNC_STATE_UPDATE_NONE;
 | 
			
		||||
    }
 | 
			
		||||
    flush = vs->ioc != NULL && vs->abort != true;
 | 
			
		||||
    vnc_unlock_output(vs);
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										222
									
								
								ui/vnc.c
									
									
									
									
									
								
							
							
						
						
									
										222
									
								
								ui/vnc.c
									
									
									
									
									
								
							@ -60,6 +60,7 @@ static QTAILQ_HEAD(, VncDisplay) vnc_displays =
 | 
			
		||||
 | 
			
		||||
static int vnc_cursor_define(VncState *vs);
 | 
			
		||||
static void vnc_release_modifiers(VncState *vs);
 | 
			
		||||
static void vnc_update_throttle_offset(VncState *vs);
 | 
			
		||||
 | 
			
		||||
static void vnc_set_share_mode(VncState *vs, VncShareMode mode)
 | 
			
		||||
{
 | 
			
		||||
@ -596,7 +597,7 @@ VncInfo2List *qmp_query_vnc_servers(Error **errp)
 | 
			
		||||
   3) resolutions > 1024
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
static int vnc_update_client(VncState *vs, int has_dirty, bool sync);
 | 
			
		||||
static int vnc_update_client(VncState *vs, int has_dirty);
 | 
			
		||||
static void vnc_disconnect_start(VncState *vs);
 | 
			
		||||
 | 
			
		||||
static void vnc_colordepth(VncState *vs);
 | 
			
		||||
@ -766,6 +767,7 @@ static void vnc_dpy_switch(DisplayChangeListener *dcl,
 | 
			
		||||
        vnc_set_area_dirty(vs->dirty, vd, 0, 0,
 | 
			
		||||
                           vnc_width(vd),
 | 
			
		||||
                           vnc_height(vd));
 | 
			
		||||
        vnc_update_throttle_offset(vs);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -961,27 +963,121 @@ static int find_and_clear_dirty_height(VncState *vs,
 | 
			
		||||
    return h;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int vnc_update_client(VncState *vs, int has_dirty, bool sync)
 | 
			
		||||
/*
 | 
			
		||||
 * Figure out how much pending data we should allow in the output
 | 
			
		||||
 * buffer before we throttle incremental display updates, and/or
 | 
			
		||||
 * drop audio samples.
 | 
			
		||||
 *
 | 
			
		||||
 * We allow for equiv of 1 full display's worth of FB updates,
 | 
			
		||||
 * and 1 second of audio samples. If audio backlog was larger
 | 
			
		||||
 * than that the client would already suffering awful audio
 | 
			
		||||
 * glitches, so dropping samples is no worse really).
 | 
			
		||||
 */
 | 
			
		||||
static void vnc_update_throttle_offset(VncState *vs)
 | 
			
		||||
{
 | 
			
		||||
    if (vs->disconnecting) {
 | 
			
		||||
        vnc_disconnect_finish(vs);
 | 
			
		||||
        return 0;
 | 
			
		||||
    size_t offset =
 | 
			
		||||
        vs->client_width * vs->client_height * vs->client_pf.bytes_per_pixel;
 | 
			
		||||
 | 
			
		||||
    if (vs->audio_cap) {
 | 
			
		||||
        int freq = vs->as.freq;
 | 
			
		||||
        /* We don't limit freq when reading settings from client, so
 | 
			
		||||
         * it could be upto MAX_INT in size. 48khz is a sensible
 | 
			
		||||
         * upper bound for trustworthy clients */
 | 
			
		||||
        int bps;
 | 
			
		||||
        if (freq > 48000) {
 | 
			
		||||
            freq = 48000;
 | 
			
		||||
        }
 | 
			
		||||
        switch (vs->as.fmt) {
 | 
			
		||||
        default:
 | 
			
		||||
        case  AUD_FMT_U8:
 | 
			
		||||
        case  AUD_FMT_S8:
 | 
			
		||||
            bps = 1;
 | 
			
		||||
            break;
 | 
			
		||||
        case  AUD_FMT_U16:
 | 
			
		||||
        case  AUD_FMT_S16:
 | 
			
		||||
            bps = 2;
 | 
			
		||||
            break;
 | 
			
		||||
        case  AUD_FMT_U32:
 | 
			
		||||
        case  AUD_FMT_S32:
 | 
			
		||||
            bps = 4;
 | 
			
		||||
            break;
 | 
			
		||||
        }
 | 
			
		||||
        offset += freq * bps * vs->as.nchannels;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    vs->has_dirty += has_dirty;
 | 
			
		||||
    if (vs->need_update && !vs->disconnecting) {
 | 
			
		||||
    /* Put a floor of 1MB on offset, so that if we have a large pending
 | 
			
		||||
     * buffer and the display is resized to a small size & back again
 | 
			
		||||
     * we don't suddenly apply a tiny send limit
 | 
			
		||||
     */
 | 
			
		||||
    offset = MAX(offset, 1024 * 1024);
 | 
			
		||||
 | 
			
		||||
    if (vs->throttle_output_offset != offset) {
 | 
			
		||||
        trace_vnc_client_throttle_threshold(
 | 
			
		||||
            vs, vs->ioc, vs->throttle_output_offset, offset, vs->client_width,
 | 
			
		||||
            vs->client_height, vs->client_pf.bytes_per_pixel, vs->audio_cap);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    vs->throttle_output_offset = offset;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static bool vnc_should_update(VncState *vs)
 | 
			
		||||
{
 | 
			
		||||
    switch (vs->update) {
 | 
			
		||||
    case VNC_STATE_UPDATE_NONE:
 | 
			
		||||
        break;
 | 
			
		||||
    case VNC_STATE_UPDATE_INCREMENTAL:
 | 
			
		||||
        /* Only allow incremental updates if the pending send queue
 | 
			
		||||
         * is less than the permitted threshold, and the job worker
 | 
			
		||||
         * is completely idle.
 | 
			
		||||
         */
 | 
			
		||||
        if (vs->output.offset < vs->throttle_output_offset &&
 | 
			
		||||
            vs->job_update == VNC_STATE_UPDATE_NONE) {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        trace_vnc_client_throttle_incremental(
 | 
			
		||||
            vs, vs->ioc, vs->job_update, vs->output.offset);
 | 
			
		||||
        break;
 | 
			
		||||
    case VNC_STATE_UPDATE_FORCE:
 | 
			
		||||
        /* Only allow forced updates if the pending send queue
 | 
			
		||||
         * does not contain a previous forced update, and the
 | 
			
		||||
         * job worker is completely idle.
 | 
			
		||||
         *
 | 
			
		||||
         * Note this means we'll queue a forced update, even if
 | 
			
		||||
         * the output buffer size is otherwise over the throttle
 | 
			
		||||
         * output limit.
 | 
			
		||||
         */
 | 
			
		||||
        if (vs->force_update_offset == 0 &&
 | 
			
		||||
            vs->job_update == VNC_STATE_UPDATE_NONE) {
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
        trace_vnc_client_throttle_forced(
 | 
			
		||||
            vs, vs->ioc, vs->job_update, vs->force_update_offset);
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int vnc_update_client(VncState *vs, int has_dirty)
 | 
			
		||||
{
 | 
			
		||||
    VncDisplay *vd = vs->vd;
 | 
			
		||||
    VncJob *job;
 | 
			
		||||
    int y;
 | 
			
		||||
    int height, width;
 | 
			
		||||
    int n = 0;
 | 
			
		||||
 | 
			
		||||
        if (vs->output.offset && !vs->audio_cap && !vs->force_update)
 | 
			
		||||
            /* kernel send buffers are full -> drop frames to throttle */
 | 
			
		||||
    if (vs->disconnecting) {
 | 
			
		||||
        vnc_disconnect_finish(vs);
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
        if (!vs->has_dirty && !vs->audio_cap && !vs->force_update)
 | 
			
		||||
    vs->has_dirty += has_dirty;
 | 
			
		||||
    if (!vnc_should_update(vs)) {
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (!vs->has_dirty && vs->update != VNC_STATE_UPDATE_FORCE) {
 | 
			
		||||
        return 0;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /*
 | 
			
		||||
     * Send screen updates to the vnc client using the server
 | 
			
		||||
@ -1024,24 +1120,13 @@ static int vnc_update_client(VncState *vs, int has_dirty, bool sync)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    vs->job_update = vs->update;
 | 
			
		||||
    vs->update = VNC_STATE_UPDATE_NONE;
 | 
			
		||||
    vnc_job_push(job);
 | 
			
		||||
        if (sync) {
 | 
			
		||||
            vnc_jobs_join(vs);
 | 
			
		||||
        }
 | 
			
		||||
        vs->force_update = 0;
 | 
			
		||||
    vs->has_dirty = 0;
 | 
			
		||||
    return n;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
    if (vs->disconnecting) {
 | 
			
		||||
        vnc_disconnect_finish(vs);
 | 
			
		||||
    } else if (sync) {
 | 
			
		||||
        vnc_jobs_join(vs);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/* audio */
 | 
			
		||||
static void audio_capture_notify(void *opaque, audcnotification_e cmd)
 | 
			
		||||
{
 | 
			
		||||
@ -1077,11 +1162,15 @@ static void audio_capture(void *opaque, void *buf, int size)
 | 
			
		||||
    VncState *vs = opaque;
 | 
			
		||||
 | 
			
		||||
    vnc_lock_output(vs);
 | 
			
		||||
    if (vs->output.offset < vs->throttle_output_offset) {
 | 
			
		||||
        vnc_write_u8(vs, VNC_MSG_SERVER_QEMU);
 | 
			
		||||
        vnc_write_u8(vs, VNC_MSG_SERVER_QEMU_AUDIO);
 | 
			
		||||
        vnc_write_u16(vs, VNC_MSG_SERVER_QEMU_AUDIO_DATA);
 | 
			
		||||
        vnc_write_u32(vs, size);
 | 
			
		||||
        vnc_write(vs, buf, size);
 | 
			
		||||
    } else {
 | 
			
		||||
        trace_vnc_client_throttle_audio(vs, vs->ioc, vs->output.offset);
 | 
			
		||||
    }
 | 
			
		||||
    vnc_unlock_output(vs);
 | 
			
		||||
    vnc_flush(vs);
 | 
			
		||||
}
 | 
			
		||||
@ -1183,7 +1272,7 @@ void vnc_disconnect_finish(VncState *vs)
 | 
			
		||||
    g_free(vs);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
ssize_t vnc_client_io_error(VncState *vs, ssize_t ret, Error **errp)
 | 
			
		||||
size_t vnc_client_io_error(VncState *vs, ssize_t ret, Error **errp)
 | 
			
		||||
{
 | 
			
		||||
    if (ret <= 0) {
 | 
			
		||||
        if (ret == 0) {
 | 
			
		||||
@ -1226,9 +1315,9 @@ void vnc_client_error(VncState *vs)
 | 
			
		||||
 *
 | 
			
		||||
 * Returns the number of bytes written, which may be less than
 | 
			
		||||
 * the requested 'datalen' if the socket would block. Returns
 | 
			
		||||
 * -1 on error, and disconnects the client socket.
 | 
			
		||||
 * 0 on I/O error, and disconnects the client socket.
 | 
			
		||||
 */
 | 
			
		||||
ssize_t vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen)
 | 
			
		||||
size_t vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen)
 | 
			
		||||
{
 | 
			
		||||
    Error *err = NULL;
 | 
			
		||||
    ssize_t ret;
 | 
			
		||||
@ -1247,11 +1336,12 @@ ssize_t vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen)
 | 
			
		||||
 *
 | 
			
		||||
 * Returns the number of bytes written, which may be less than
 | 
			
		||||
 * the buffered output data if the socket would block.  Returns
 | 
			
		||||
 * -1 on error, and disconnects the client socket.
 | 
			
		||||
 * 0 on I/O error, and disconnects the client socket.
 | 
			
		||||
 */
 | 
			
		||||
static ssize_t vnc_client_write_plain(VncState *vs)
 | 
			
		||||
static size_t vnc_client_write_plain(VncState *vs)
 | 
			
		||||
{
 | 
			
		||||
    ssize_t ret;
 | 
			
		||||
    size_t offset;
 | 
			
		||||
    size_t ret;
 | 
			
		||||
 | 
			
		||||
#ifdef CONFIG_VNC_SASL
 | 
			
		||||
    VNC_DEBUG("Write Plain: Pending output %p size %zd offset %zd. Wait SSF %d\n",
 | 
			
		||||
@ -1270,7 +1360,20 @@ static ssize_t vnc_client_write_plain(VncState *vs)
 | 
			
		||||
    if (!ret)
 | 
			
		||||
        return 0;
 | 
			
		||||
 | 
			
		||||
    if (ret >= vs->force_update_offset) {
 | 
			
		||||
        if (vs->force_update_offset != 0) {
 | 
			
		||||
            trace_vnc_client_unthrottle_forced(vs, vs->ioc);
 | 
			
		||||
        }
 | 
			
		||||
        vs->force_update_offset = 0;
 | 
			
		||||
    } else {
 | 
			
		||||
        vs->force_update_offset -= ret;
 | 
			
		||||
    }
 | 
			
		||||
    offset = vs->output.offset;
 | 
			
		||||
    buffer_advance(&vs->output, ret);
 | 
			
		||||
    if (offset >= vs->throttle_output_offset &&
 | 
			
		||||
        vs->output.offset < vs->throttle_output_offset) {
 | 
			
		||||
        trace_vnc_client_unthrottle_incremental(vs, vs->ioc, vs->output.offset);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    if (vs->output.offset == 0) {
 | 
			
		||||
        if (vs->ioc_tag) {
 | 
			
		||||
@ -1339,9 +1442,9 @@ void vnc_read_when(VncState *vs, VncReadEvent *func, size_t expecting)
 | 
			
		||||
 *
 | 
			
		||||
 * Returns the number of bytes read, which may be less than
 | 
			
		||||
 * the requested 'datalen' if the socket would block. Returns
 | 
			
		||||
 * -1 on error, and disconnects the client socket.
 | 
			
		||||
 * 0 on I/O error or EOF, and disconnects the client socket.
 | 
			
		||||
 */
 | 
			
		||||
ssize_t vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen)
 | 
			
		||||
size_t vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen)
 | 
			
		||||
{
 | 
			
		||||
    ssize_t ret;
 | 
			
		||||
    Error *err = NULL;
 | 
			
		||||
@ -1357,12 +1460,13 @@ ssize_t vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen)
 | 
			
		||||
 * when not using any SASL SSF encryption layers. Will read as much
 | 
			
		||||
 * data as possible without blocking.
 | 
			
		||||
 *
 | 
			
		||||
 * Returns the number of bytes read. Returns -1 on error, and
 | 
			
		||||
 * disconnects the client socket.
 | 
			
		||||
 * Returns the number of bytes read, which may be less than
 | 
			
		||||
 * the requested 'datalen' if the socket would block. Returns
 | 
			
		||||
 * 0 on I/O error or EOF, and disconnects the client socket.
 | 
			
		||||
 */
 | 
			
		||||
static ssize_t vnc_client_read_plain(VncState *vs)
 | 
			
		||||
static size_t vnc_client_read_plain(VncState *vs)
 | 
			
		||||
{
 | 
			
		||||
    ssize_t ret;
 | 
			
		||||
    size_t ret;
 | 
			
		||||
    VNC_DEBUG("Read plain %p size %zd offset %zd\n",
 | 
			
		||||
              vs->input.buffer, vs->input.capacity, vs->input.offset);
 | 
			
		||||
    buffer_reserve(&vs->input, 4096);
 | 
			
		||||
@ -1388,7 +1492,7 @@ static void vnc_jobs_bh(void *opaque)
 | 
			
		||||
 */
 | 
			
		||||
static int vnc_client_read(VncState *vs)
 | 
			
		||||
{
 | 
			
		||||
    ssize_t ret;
 | 
			
		||||
    size_t ret;
 | 
			
		||||
 | 
			
		||||
#ifdef CONFIG_VNC_SASL
 | 
			
		||||
    if (vs->sasl.conn && vs->sasl.runSSF)
 | 
			
		||||
@ -1439,8 +1543,39 @@ gboolean vnc_client_io(QIOChannel *ioc G_GNUC_UNUSED,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
 * Scale factor to apply to vs->throttle_output_offset when checking for
 | 
			
		||||
 * hard limit. Worst case normal usage could be x2, if we have a complete
 | 
			
		||||
 * incremental update and complete forced update in the output buffer.
 | 
			
		||||
 * So x3 should be good enough, but we pick x5 to be conservative and thus
 | 
			
		||||
 * (hopefully) never trigger incorrectly.
 | 
			
		||||
 */
 | 
			
		||||
#define VNC_THROTTLE_OUTPUT_LIMIT_SCALE 5
 | 
			
		||||
 | 
			
		||||
void vnc_write(VncState *vs, const void *data, size_t len)
 | 
			
		||||
{
 | 
			
		||||
    if (vs->disconnecting) {
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    /* Protection against malicious client/guest to prevent our output
 | 
			
		||||
     * buffer growing without bound if client stops reading data. This
 | 
			
		||||
     * should rarely trigger, because we have earlier throttling code
 | 
			
		||||
     * which stops issuing framebuffer updates and drops audio data
 | 
			
		||||
     * if the throttle_output_offset value is exceeded. So we only reach
 | 
			
		||||
     * this higher level if a huge number of pseudo-encodings get
 | 
			
		||||
     * triggered while data can't be sent on the socket.
 | 
			
		||||
     *
 | 
			
		||||
     * NB throttle_output_offset can be zero during early protocol
 | 
			
		||||
     * handshake, or from the job thread's VncState clone
 | 
			
		||||
     */
 | 
			
		||||
    if (vs->throttle_output_offset != 0 &&
 | 
			
		||||
        vs->output.offset > (vs->throttle_output_offset *
 | 
			
		||||
                             VNC_THROTTLE_OUTPUT_LIMIT_SCALE)) {
 | 
			
		||||
        trace_vnc_client_output_limit(vs, vs->ioc, vs->output.offset,
 | 
			
		||||
                                      vs->throttle_output_offset);
 | 
			
		||||
        vnc_disconnect_start(vs);
 | 
			
		||||
        return;
 | 
			
		||||
    }
 | 
			
		||||
    buffer_reserve(&vs->output, len);
 | 
			
		||||
 | 
			
		||||
    if (vs->ioc != NULL && buffer_empty(&vs->output)) {
 | 
			
		||||
@ -1876,15 +2011,15 @@ static void ext_key_event(VncState *vs, int down,
 | 
			
		||||
static void framebuffer_update_request(VncState *vs, int incremental,
 | 
			
		||||
                                       int x, int y, int w, int h)
 | 
			
		||||
{
 | 
			
		||||
    vs->need_update = 1;
 | 
			
		||||
 | 
			
		||||
    if (incremental) {
 | 
			
		||||
        return;
 | 
			
		||||
        if (vs->update != VNC_STATE_UPDATE_FORCE) {
 | 
			
		||||
            vs->update = VNC_STATE_UPDATE_INCREMENTAL;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    vs->force_update = 1;
 | 
			
		||||
    } else {
 | 
			
		||||
        vs->update = VNC_STATE_UPDATE_FORCE;
 | 
			
		||||
        vnc_set_area_dirty(vs->dirty, vs->vd, x, y, w, h);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void send_ext_key_event_ack(VncState *vs)
 | 
			
		||||
{
 | 
			
		||||
@ -2255,7 +2390,7 @@ static int protocol_client_msg(VncState *vs, uint8_t *data, size_t len)
 | 
			
		||||
                }
 | 
			
		||||
                vs->as.nchannels = read_u8(data, 5);
 | 
			
		||||
                if (vs->as.nchannels != 1 && vs->as.nchannels != 2) {
 | 
			
		||||
                    VNC_DEBUG("Invalid audio channel coount %d\n",
 | 
			
		||||
                    VNC_DEBUG("Invalid audio channel count %d\n",
 | 
			
		||||
                              read_u8(data, 5));
 | 
			
		||||
                    vnc_client_error(vs);
 | 
			
		||||
                    break;
 | 
			
		||||
@ -2281,6 +2416,7 @@ static int protocol_client_msg(VncState *vs, uint8_t *data, size_t len)
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    vnc_update_throttle_offset(vs);
 | 
			
		||||
    vnc_read_when(vs, protocol_client_msg, 1);
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
@ -2863,7 +2999,7 @@ static void vnc_refresh(DisplayChangeListener *dcl)
 | 
			
		||||
    vnc_unlock_display(vd);
 | 
			
		||||
 | 
			
		||||
    QTAILQ_FOREACH_SAFE(vs, &vd->clients, next, vn) {
 | 
			
		||||
        rects += vnc_update_client(vs, has_dirty, false);
 | 
			
		||||
        rects += vnc_update_client(vs, has_dirty);
 | 
			
		||||
        /* vs might be free()ed here */
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										28
									
								
								ui/vnc.h
									
									
									
									
									
								
							
							
						
						
									
										28
									
								
								ui/vnc.h
									
									
									
									
									
								
							@ -252,6 +252,12 @@ struct VncJob
 | 
			
		||||
    QTAILQ_ENTRY(VncJob) next;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
typedef enum {
 | 
			
		||||
    VNC_STATE_UPDATE_NONE,
 | 
			
		||||
    VNC_STATE_UPDATE_INCREMENTAL,
 | 
			
		||||
    VNC_STATE_UPDATE_FORCE,
 | 
			
		||||
} VncStateUpdate;
 | 
			
		||||
 | 
			
		||||
struct VncState
 | 
			
		||||
{
 | 
			
		||||
    QIOChannelSocket *sioc; /* The underlying socket */
 | 
			
		||||
@ -264,8 +270,8 @@ struct VncState
 | 
			
		||||
                           * vnc-jobs-async.c */
 | 
			
		||||
 | 
			
		||||
    VncDisplay *vd;
 | 
			
		||||
    int need_update;
 | 
			
		||||
    int force_update;
 | 
			
		||||
    VncStateUpdate update; /* Most recent pending request from client */
 | 
			
		||||
    VncStateUpdate job_update; /* Currently processed by job thread */
 | 
			
		||||
    int has_dirty;
 | 
			
		||||
    uint32_t features;
 | 
			
		||||
    int absolute;
 | 
			
		||||
@ -293,6 +299,18 @@ struct VncState
 | 
			
		||||
 | 
			
		||||
    VncClientInfo *info;
 | 
			
		||||
 | 
			
		||||
    /* Job thread bottom half has put data for a forced update
 | 
			
		||||
     * into the output buffer. This offset points to the end of
 | 
			
		||||
     * the update data in the output buffer. This lets us determine
 | 
			
		||||
     * when a force update is fully sent to the client, allowing
 | 
			
		||||
     * us to process further forced updates. */
 | 
			
		||||
    size_t force_update_offset;
 | 
			
		||||
    /* We allow multiple incremental updates or audio capture
 | 
			
		||||
     * samples to be queued in output buffer, provided the
 | 
			
		||||
     * buffer size doesn't exceed this threshold. The value
 | 
			
		||||
     * is calculating dynamically based on framebuffer size
 | 
			
		||||
     * and audio sample settings in vnc_update_throttle_offset() */
 | 
			
		||||
    size_t throttle_output_offset;
 | 
			
		||||
    Buffer output;
 | 
			
		||||
    Buffer input;
 | 
			
		||||
    /* current output mode information */
 | 
			
		||||
@ -506,8 +524,8 @@ gboolean vnc_client_io(QIOChannel *ioc,
 | 
			
		||||
                       GIOCondition condition,
 | 
			
		||||
                       void *opaque);
 | 
			
		||||
 | 
			
		||||
ssize_t vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen);
 | 
			
		||||
ssize_t vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen);
 | 
			
		||||
size_t vnc_client_read_buf(VncState *vs, uint8_t *data, size_t datalen);
 | 
			
		||||
size_t vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen);
 | 
			
		||||
 | 
			
		||||
/* Protocol I/O functions */
 | 
			
		||||
void vnc_write(VncState *vs, const void *data, size_t len);
 | 
			
		||||
@ -526,7 +544,7 @@ uint32_t read_u32(uint8_t *data, size_t offset);
 | 
			
		||||
 | 
			
		||||
/* Protocol stage functions */
 | 
			
		||||
void vnc_client_error(VncState *vs);
 | 
			
		||||
ssize_t vnc_client_io_error(VncState *vs, ssize_t ret, Error **errp);
 | 
			
		||||
size_t vnc_client_io_error(VncState *vs, ssize_t ret, Error **errp);
 | 
			
		||||
 | 
			
		||||
void start_client_init(VncState *vs);
 | 
			
		||||
void start_auth_vnc(VncState *vs);
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user