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_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_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_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_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_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"
|
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 "
|
VNC_DEBUG("Write SASL: Pending output %p size %zd offset %zd "
|
||||||
"Encoded: %p size %d offset %d\n",
|
"Encoded: %p size %d offset %d\n",
|
||||||
|
@ -67,6 +67,7 @@ long vnc_client_write_sasl(VncState *vs)
|
||||||
if (err != SASL_OK)
|
if (err != SASL_OK)
|
||||||
return vnc_client_io_error(vs, -1, NULL);
|
return vnc_client_io_error(vs, -1, NULL);
|
||||||
|
|
||||||
|
vs->sasl.encodedRawLength = vs->output.offset;
|
||||||
vs->sasl.encodedOffset = 0;
|
vs->sasl.encodedOffset = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,7 +79,12 @@ long vnc_client_write_sasl(VncState *vs)
|
||||||
|
|
||||||
vs->sasl.encodedOffset += ret;
|
vs->sasl.encodedOffset += ret;
|
||||||
if (vs->sasl.encodedOffset == vs->sasl.encodedLength) {
|
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.encoded = NULL;
|
||||||
vs->sasl.encodedOffset = vs->sasl.encodedLength = 0;
|
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];
|
uint8_t encoded[4096];
|
||||||
const char *decoded;
|
const char *decoded;
|
||||||
unsigned int decodedLen;
|
unsigned int decodedLen;
|
||||||
|
|
|
@ -53,6 +53,7 @@ struct VncStateSASL {
|
||||||
*/
|
*/
|
||||||
const uint8_t *encoded;
|
const uint8_t *encoded;
|
||||||
unsigned int encodedLength;
|
unsigned int encodedLength;
|
||||||
|
unsigned int encodedRawLength;
|
||||||
unsigned int encodedOffset;
|
unsigned int encodedOffset;
|
||||||
char *username;
|
char *username;
|
||||||
char *mechlist;
|
char *mechlist;
|
||||||
|
@ -64,8 +65,8 @@ struct VncDisplaySASL {
|
||||||
|
|
||||||
void vnc_sasl_client_cleanup(VncState *vs);
|
void vnc_sasl_client_cleanup(VncState *vs);
|
||||||
|
|
||||||
long vnc_client_read_sasl(VncState *vs);
|
size_t vnc_client_read_sasl(VncState *vs);
|
||||||
long vnc_client_write_sasl(VncState *vs);
|
size_t vnc_client_write_sasl(VncState *vs);
|
||||||
|
|
||||||
void start_auth_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);
|
vs->ioc, G_IO_IN | G_IO_OUT, vnc_client_io, vs, NULL);
|
||||||
}
|
}
|
||||||
buffer_move(&vs->output, &vs->jobs_buffer);
|
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;
|
flush = vs->ioc != NULL && vs->abort != true;
|
||||||
vnc_unlock_output(vs);
|
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 int vnc_cursor_define(VncState *vs);
|
||||||
static void vnc_release_modifiers(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)
|
static void vnc_set_share_mode(VncState *vs, VncShareMode mode)
|
||||||
{
|
{
|
||||||
|
@ -596,7 +597,7 @@ VncInfo2List *qmp_query_vnc_servers(Error **errp)
|
||||||
3) resolutions > 1024
|
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_disconnect_start(VncState *vs);
|
||||||
|
|
||||||
static void vnc_colordepth(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_set_area_dirty(vs->dirty, vd, 0, 0,
|
||||||
vnc_width(vd),
|
vnc_width(vd),
|
||||||
vnc_height(vd));
|
vnc_height(vd));
|
||||||
|
vnc_update_throttle_offset(vs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -961,27 +963,121 @@ static int find_and_clear_dirty_height(VncState *vs,
|
||||||
return h;
|
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) {
|
size_t offset =
|
||||||
vnc_disconnect_finish(vs);
|
vs->client_width * vs->client_height * vs->client_pf.bytes_per_pixel;
|
||||||
return 0;
|
|
||||||
|
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;
|
/* Put a floor of 1MB on offset, so that if we have a large pending
|
||||||
if (vs->need_update && !vs->disconnecting) {
|
* 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;
|
VncDisplay *vd = vs->vd;
|
||||||
VncJob *job;
|
VncJob *job;
|
||||||
int y;
|
int y;
|
||||||
int height, width;
|
int height, width;
|
||||||
int n = 0;
|
int n = 0;
|
||||||
|
|
||||||
if (vs->output.offset && !vs->audio_cap && !vs->force_update)
|
if (vs->disconnecting) {
|
||||||
/* kernel send buffers are full -> drop frames to throttle */
|
vnc_disconnect_finish(vs);
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (!vs->has_dirty && !vs->audio_cap && !vs->force_update)
|
vs->has_dirty += has_dirty;
|
||||||
|
if (!vnc_should_update(vs)) {
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!vs->has_dirty && vs->update != VNC_STATE_UPDATE_FORCE) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Send screen updates to the vnc client using the server
|
* 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);
|
vnc_job_push(job);
|
||||||
if (sync) {
|
|
||||||
vnc_jobs_join(vs);
|
|
||||||
}
|
|
||||||
vs->force_update = 0;
|
|
||||||
vs->has_dirty = 0;
|
vs->has_dirty = 0;
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (vs->disconnecting) {
|
|
||||||
vnc_disconnect_finish(vs);
|
|
||||||
} else if (sync) {
|
|
||||||
vnc_jobs_join(vs);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* audio */
|
/* audio */
|
||||||
static void audio_capture_notify(void *opaque, audcnotification_e cmd)
|
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;
|
VncState *vs = opaque;
|
||||||
|
|
||||||
vnc_lock_output(vs);
|
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);
|
||||||
vnc_write_u8(vs, VNC_MSG_SERVER_QEMU_AUDIO);
|
vnc_write_u8(vs, VNC_MSG_SERVER_QEMU_AUDIO);
|
||||||
vnc_write_u16(vs, VNC_MSG_SERVER_QEMU_AUDIO_DATA);
|
vnc_write_u16(vs, VNC_MSG_SERVER_QEMU_AUDIO_DATA);
|
||||||
vnc_write_u32(vs, size);
|
vnc_write_u32(vs, size);
|
||||||
vnc_write(vs, buf, size);
|
vnc_write(vs, buf, size);
|
||||||
|
} else {
|
||||||
|
trace_vnc_client_throttle_audio(vs, vs->ioc, vs->output.offset);
|
||||||
|
}
|
||||||
vnc_unlock_output(vs);
|
vnc_unlock_output(vs);
|
||||||
vnc_flush(vs);
|
vnc_flush(vs);
|
||||||
}
|
}
|
||||||
|
@ -1183,7 +1272,7 @@ void vnc_disconnect_finish(VncState *vs)
|
||||||
g_free(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) {
|
||||||
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
|
* Returns the number of bytes written, which may be less than
|
||||||
* the requested 'datalen' if the socket would block. Returns
|
* 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;
|
Error *err = NULL;
|
||||||
ssize_t ret;
|
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
|
* Returns the number of bytes written, which may be less than
|
||||||
* the buffered output data if the socket would block. Returns
|
* 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
|
#ifdef CONFIG_VNC_SASL
|
||||||
VNC_DEBUG("Write Plain: Pending output %p size %zd offset %zd. Wait SSF %d\n",
|
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)
|
if (!ret)
|
||||||
return 0;
|
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);
|
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->output.offset == 0) {
|
||||||
if (vs->ioc_tag) {
|
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
|
* Returns the number of bytes read, which may be less than
|
||||||
* the requested 'datalen' if the socket would block. Returns
|
* 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;
|
ssize_t ret;
|
||||||
Error *err = NULL;
|
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
|
* when not using any SASL SSF encryption layers. Will read as much
|
||||||
* data as possible without blocking.
|
* data as possible without blocking.
|
||||||
*
|
*
|
||||||
* Returns the number of bytes read. Returns -1 on error, and
|
* Returns the number of bytes read, which may be less than
|
||||||
* disconnects the client socket.
|
* 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",
|
VNC_DEBUG("Read plain %p size %zd offset %zd\n",
|
||||||
vs->input.buffer, vs->input.capacity, vs->input.offset);
|
vs->input.buffer, vs->input.capacity, vs->input.offset);
|
||||||
buffer_reserve(&vs->input, 4096);
|
buffer_reserve(&vs->input, 4096);
|
||||||
|
@ -1388,7 +1492,7 @@ static void vnc_jobs_bh(void *opaque)
|
||||||
*/
|
*/
|
||||||
static int vnc_client_read(VncState *vs)
|
static int vnc_client_read(VncState *vs)
|
||||||
{
|
{
|
||||||
ssize_t ret;
|
size_t ret;
|
||||||
|
|
||||||
#ifdef CONFIG_VNC_SASL
|
#ifdef CONFIG_VNC_SASL
|
||||||
if (vs->sasl.conn && vs->sasl.runSSF)
|
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)
|
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);
|
buffer_reserve(&vs->output, len);
|
||||||
|
|
||||||
if (vs->ioc != NULL && buffer_empty(&vs->output)) {
|
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,
|
static void framebuffer_update_request(VncState *vs, int incremental,
|
||||||
int x, int y, int w, int h)
|
int x, int y, int w, int h)
|
||||||
{
|
{
|
||||||
vs->need_update = 1;
|
|
||||||
|
|
||||||
if (incremental) {
|
if (incremental) {
|
||||||
return;
|
if (vs->update != VNC_STATE_UPDATE_FORCE) {
|
||||||
|
vs->update = VNC_STATE_UPDATE_INCREMENTAL;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
vs->force_update = 1;
|
vs->update = VNC_STATE_UPDATE_FORCE;
|
||||||
vnc_set_area_dirty(vs->dirty, vs->vd, x, y, w, h);
|
vnc_set_area_dirty(vs->dirty, vs->vd, x, y, w, h);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
static void send_ext_key_event_ack(VncState *vs)
|
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);
|
vs->as.nchannels = read_u8(data, 5);
|
||||||
if (vs->as.nchannels != 1 && vs->as.nchannels != 2) {
|
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));
|
read_u8(data, 5));
|
||||||
vnc_client_error(vs);
|
vnc_client_error(vs);
|
||||||
break;
|
break;
|
||||||
|
@ -2281,6 +2416,7 @@ static int protocol_client_msg(VncState *vs, uint8_t *data, size_t len)
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vnc_update_throttle_offset(vs);
|
||||||
vnc_read_when(vs, protocol_client_msg, 1);
|
vnc_read_when(vs, protocol_client_msg, 1);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -2863,7 +2999,7 @@ static void vnc_refresh(DisplayChangeListener *dcl)
|
||||||
vnc_unlock_display(vd);
|
vnc_unlock_display(vd);
|
||||||
|
|
||||||
QTAILQ_FOREACH_SAFE(vs, &vd->clients, next, vn) {
|
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 */
|
/* vs might be free()ed here */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
28
ui/vnc.h
28
ui/vnc.h
|
@ -252,6 +252,12 @@ struct VncJob
|
||||||
QTAILQ_ENTRY(VncJob) next;
|
QTAILQ_ENTRY(VncJob) next;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
VNC_STATE_UPDATE_NONE,
|
||||||
|
VNC_STATE_UPDATE_INCREMENTAL,
|
||||||
|
VNC_STATE_UPDATE_FORCE,
|
||||||
|
} VncStateUpdate;
|
||||||
|
|
||||||
struct VncState
|
struct VncState
|
||||||
{
|
{
|
||||||
QIOChannelSocket *sioc; /* The underlying socket */
|
QIOChannelSocket *sioc; /* The underlying socket */
|
||||||
|
@ -264,8 +270,8 @@ struct VncState
|
||||||
* vnc-jobs-async.c */
|
* vnc-jobs-async.c */
|
||||||
|
|
||||||
VncDisplay *vd;
|
VncDisplay *vd;
|
||||||
int need_update;
|
VncStateUpdate update; /* Most recent pending request from client */
|
||||||
int force_update;
|
VncStateUpdate job_update; /* Currently processed by job thread */
|
||||||
int has_dirty;
|
int has_dirty;
|
||||||
uint32_t features;
|
uint32_t features;
|
||||||
int absolute;
|
int absolute;
|
||||||
|
@ -293,6 +299,18 @@ struct VncState
|
||||||
|
|
||||||
VncClientInfo *info;
|
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 output;
|
||||||
Buffer input;
|
Buffer input;
|
||||||
/* current output mode information */
|
/* current output mode information */
|
||||||
|
@ -506,8 +524,8 @@ gboolean vnc_client_io(QIOChannel *ioc,
|
||||||
GIOCondition condition,
|
GIOCondition condition,
|
||||||
void *opaque);
|
void *opaque);
|
||||||
|
|
||||||
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 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);
|
||||||
|
|
||||||
/* Protocol I/O functions */
|
/* Protocol I/O functions */
|
||||||
void vnc_write(VncState *vs, const void *data, size_t len);
|
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 */
|
/* Protocol stage functions */
|
||||||
void vnc_client_error(VncState *vs);
|
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_client_init(VncState *vs);
|
||||||
void start_auth_vnc(VncState *vs);
|
void start_auth_vnc(VncState *vs);
|
||||||
|
|
Loading…
Reference in New Issue