提交 7398166d 编写于 作者: P Peter Maydell

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: NPeter Maydell <peter.maydell@linaro.org>
......@@ -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);
......
......@@ -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,85 +963,168 @@ 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) {
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 */
return 0;
/* 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->has_dirty && !vs->audio_cap && !vs->force_update)
return 0;
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);
}
/*
* Send screen updates to the vnc client using the server
* surface and server dirty map. guest surface updates
* happening in parallel don't disturb us, the next pass will
* send them to the client.
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.
*/
job = vnc_job_new(vs);
height = pixman_image_get_height(vd->server);
width = pixman_image_get_width(vd->server);
y = 0;
for (;;) {
int x, h;
unsigned long x2;
unsigned long offset = find_next_bit((unsigned long *) &vs->dirty,
height * VNC_DIRTY_BPL(vs),
y * VNC_DIRTY_BPL(vs));
if (offset == height * VNC_DIRTY_BPL(vs)) {
/* no more dirty bits */
break;
}
y = offset / VNC_DIRTY_BPL(vs);
x = offset % VNC_DIRTY_BPL(vs);
x2 = find_next_zero_bit((unsigned long *) &vs->dirty[y],
VNC_DIRTY_BPL(vs), x);
bitmap_clear(vs->dirty[y], x, x2 - x);
h = find_and_clear_dirty_height(vs, y, x, x2, height);
x2 = MIN(x2, width / VNC_DIRTY_PIXELS_PER_BIT);
if (x2 > x) {
n += vnc_job_add_rect(job, x * VNC_DIRTY_PIXELS_PER_BIT, y,
(x2 - x) * VNC_DIRTY_PIXELS_PER_BIT, h);
}
if (!x && x2 == width / VNC_DIRTY_PIXELS_PER_BIT) {
y += h;
if (y == height) {
break;
}
}
if (vs->output.offset < vs->throttle_output_offset &&
vs->job_update == VNC_STATE_UPDATE_NONE) {
return true;
}
vnc_job_push(job);
if (sync) {
vnc_jobs_join(vs);
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;
}
vs->force_update = 0;
vs->has_dirty = 0;
return n;
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->disconnecting) {
vnc_disconnect_finish(vs);
} else if (sync) {
vnc_jobs_join(vs);
return 0;
}
return 0;
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
* surface and server dirty map. guest surface updates
* happening in parallel don't disturb us, the next pass will
* send them to the client.
*/
job = vnc_job_new(vs);
height = pixman_image_get_height(vd->server);
width = pixman_image_get_width(vd->server);
y = 0;
for (;;) {
int x, h;
unsigned long x2;
unsigned long offset = find_next_bit((unsigned long *) &vs->dirty,
height * VNC_DIRTY_BPL(vs),
y * VNC_DIRTY_BPL(vs));
if (offset == height * VNC_DIRTY_BPL(vs)) {
/* no more dirty bits */
break;
}
y = offset / VNC_DIRTY_BPL(vs);
x = offset % VNC_DIRTY_BPL(vs);
x2 = find_next_zero_bit((unsigned long *) &vs->dirty[y],
VNC_DIRTY_BPL(vs), x);
bitmap_clear(vs->dirty[y], x, x2 - x);
h = find_and_clear_dirty_height(vs, y, x, x2, height);
x2 = MIN(x2, width / VNC_DIRTY_PIXELS_PER_BIT);
if (x2 > x) {
n += vnc_job_add_rect(job, x * VNC_DIRTY_PIXELS_PER_BIT, y,
(x2 - x) * VNC_DIRTY_PIXELS_PER_BIT, h);
}
if (!x && x2 == width / VNC_DIRTY_PIXELS_PER_BIT) {
y += h;
if (y == height) {
break;
}
}
}
vs->job_update = vs->update;
vs->update = VNC_STATE_UPDATE_NONE;
vnc_job_push(job);
vs->has_dirty = 0;
return n;
}
/* audio */
......@@ -1077,11 +1162,15 @@ static void audio_capture(void *opaque, void *buf, int size)
VncState *vs = opaque;
vnc_lock_output(vs);
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);
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;
......@@ -1246,12 +1335,13 @@ ssize_t vnc_client_write_buf(VncState *vs, const uint8_t *data, size_t datalen)
* will switch the FD poll() handler back to read monitoring.
*
* 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.
* the buffered output data if the socket would block. Returns
* 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,14 +2011,14 @@ 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;
}
} else {
vs->update = VNC_STATE_UPDATE_FORCE;
vnc_set_area_dirty(vs->dirty, vs->vd, x, y, w, h);
}
vs->force_update = 1;
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 */
}
......
......@@ -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);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册