From: "Daniel P. Berrangé" <berrange@redhat.com>
To: qemu-devel@nongnu.org
Cc: "Daniel P. Berrangé" <berrange@redhat.com>,
"Grant Millar | Cylo" <rid@cylo.io>,
"Marc-André Lureau" <marcandre.lureau@gmail.com>
Subject: [PATCH 2/2] io: fix use after free in websocket handshake code
Date: Tue, 30 Sep 2025 12:08:34 +0100 [thread overview]
Message-ID: <20250930110834.2551757-3-berrange@redhat.com> (raw)
In-Reply-To: <20250930110834.2551757-1-berrange@redhat.com>
If the QIOChannelWebsock object is freed while it is waiting to
complete a handshake, a GSource is leaked. This can lead to the
callback firing later on and triggering a use-after-free in the
use of the channel. This was observed in the VNC server with the
following trace from valgrind:
==2523108== Invalid read of size 4
==2523108== at 0x4054A24: vnc_disconnect_start (vnc.c:1296)
==2523108== by 0x4054A24: vnc_client_error (vnc.c:1392)
==2523108== by 0x4068A09: vncws_handshake_done (vnc-ws.c:105)
==2523108== by 0x44863B4: qio_task_complete (task.c:197)
==2523108== by 0x448343D: qio_channel_websock_handshake_io (channel-websock.c:588)
==2523108== by 0x6EDB862: UnknownInlinedFun (gmain.c:3398)
==2523108== by 0x6EDB862: g_main_context_dispatch_unlocked.lto_priv.0 (gmain.c:4249)
==2523108== by 0x6EDBAE4: g_main_context_dispatch (gmain.c:4237)
==2523108== by 0x45EC79F: glib_pollfds_poll (main-loop.c:287)
==2523108== by 0x45EC79F: os_host_main_loop_wait (main-loop.c:310)
==2523108== by 0x45EC79F: main_loop_wait (main-loop.c:589)
==2523108== by 0x423A56D: qemu_main_loop (runstate.c:835)
==2523108== by 0x454F300: qemu_default_main (main.c:37)
==2523108== by 0x73D6574: (below main) (libc_start_call_main.h:58)
==2523108== Address 0x57a6e0dc is 28 bytes inside a block of size 103,608 free'd
==2523108== at 0x5F2FE43: free (vg_replace_malloc.c:989)
==2523108== by 0x6EDC444: g_free (gmem.c:208)
==2523108== by 0x4053F23: vnc_update_client (vnc.c:1153)
==2523108== by 0x4053F23: vnc_refresh (vnc.c:3225)
==2523108== by 0x4042881: dpy_refresh (console.c:880)
==2523108== by 0x4042881: gui_update (console.c:90)
==2523108== by 0x45EFA1B: timerlist_run_timers.part.0 (qemu-timer.c:562)
==2523108== by 0x45EFC8F: timerlist_run_timers (qemu-timer.c:495)
==2523108== by 0x45EFC8F: qemu_clock_run_timers (qemu-timer.c:576)
==2523108== by 0x45EFC8F: qemu_clock_run_all_timers (qemu-timer.c:663)
==2523108== by 0x45EC765: main_loop_wait (main-loop.c:600)
==2523108== by 0x423A56D: qemu_main_loop (runstate.c:835)
==2523108== by 0x454F300: qemu_default_main (main.c:37)
==2523108== by 0x73D6574: (below main) (libc_start_call_main.h:58)
==2523108== Block was alloc'd at
==2523108== at 0x5F343F3: calloc (vg_replace_malloc.c:1675)
==2523108== by 0x6EE2F81: g_malloc0 (gmem.c:133)
==2523108== by 0x4057DA3: vnc_connect (vnc.c:3245)
==2523108== by 0x448591B: qio_net_listener_channel_func (net-listener.c:54)
==2523108== by 0x6EDB862: UnknownInlinedFun (gmain.c:3398)
==2523108== by 0x6EDB862: g_main_context_dispatch_unlocked.lto_priv.0 (gmain.c:4249)
==2523108== by 0x6EDBAE4: g_main_context_dispatch (gmain.c:4237)
==2523108== by 0x45EC79F: glib_pollfds_poll (main-loop.c:287)
==2523108== by 0x45EC79F: os_host_main_loop_wait (main-loop.c:310)
==2523108== by 0x45EC79F: main_loop_wait (main-loop.c:589)
==2523108== by 0x423A56D: qemu_main_loop (runstate.c:835)
==2523108== by 0x454F300: qemu_default_main (main.c:37)
==2523108== by 0x73D6574: (below main) (libc_start_call_main.h:58)
==2523108==
The above can be reproduced by launching QEMU with
$ qemu-system-x86_64 -vnc localhost:0,websocket=5700
and then repeatedly running:
for i in {1..100}; do
(echo -n "GET / HTTP/1.1" && sleep 0.05) | nc -w 1 localhost 5700 &
done
Reported-by: Grant Millar | Cylo <rid@cylo.io>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
include/io/channel-websock.h | 1 +
io/channel-websock.c | 16 ++++++++++------
2 files changed, 11 insertions(+), 6 deletions(-)
diff --git a/include/io/channel-websock.h b/include/io/channel-websock.h
index e180827c57..d1e760e449 100644
--- a/include/io/channel-websock.h
+++ b/include/io/channel-websock.h
@@ -61,6 +61,7 @@ struct QIOChannelWebsock {
size_t payload_remain;
size_t pong_remain;
QIOChannelWebsockMask mask;
+ guint hs_io_tag;
guint io_tag;
Error *io_err;
gboolean io_eof;
diff --git a/io/channel-websock.c b/io/channel-websock.c
index 56d53355d5..588c313dfb 100644
--- a/io/channel-websock.c
+++ b/io/channel-websock.c
@@ -597,7 +597,7 @@ static gboolean qio_channel_websock_handshake_io(QIOChannel *ioc,
error_propagate(&wioc->io_err, err);
trace_qio_channel_websock_handshake_reply(ioc);
- qio_channel_add_watch(
+ wioc->hs_io_tag = qio_channel_add_watch(
wioc->master,
G_IO_OUT,
qio_channel_websock_handshake_send,
@@ -907,11 +907,12 @@ void qio_channel_websock_handshake(QIOChannelWebsock *ioc,
trace_qio_channel_websock_handshake_start(ioc);
trace_qio_channel_websock_handshake_pending(ioc, G_IO_IN);
- qio_channel_add_watch(ioc->master,
- G_IO_IN,
- qio_channel_websock_handshake_io,
- task,
- NULL);
+ ioc->hs_io_tag = qio_channel_add_watch(
+ ioc->master,
+ G_IO_IN,
+ qio_channel_websock_handshake_io,
+ task,
+ NULL);
}
@@ -1212,6 +1213,9 @@ static int qio_channel_websock_close(QIOChannel *ioc,
buffer_free(&wioc->encinput);
buffer_free(&wioc->encoutput);
buffer_free(&wioc->rawinput);
+ if (wioc->hs_io_tag) {
+ g_clear_handle_id(&wioc->hs_io_tag, g_source_remove);
+ }
if (wioc->io_tag) {
g_clear_handle_id(&wioc->io_tag, g_source_remove);
}
--
2.50.1
next prev parent reply other threads:[~2025-09-30 11:10 UTC|newest]
Thread overview: 7+ messages / expand[flat|nested] mbox.gz Atom feed top
2025-09-30 11:08 [PATCH 0/2] io: fix crash in VNC websock server when client quits early Daniel P. Berrangé
2025-09-30 11:08 ` [PATCH 1/2] io: move websock resource release to close method Daniel P. Berrangé
2025-09-30 11:21 ` Marc-André Lureau
2025-09-30 11:28 ` Daniel P. Berrangé
2025-09-30 11:08 ` Daniel P. Berrangé [this message]
2025-09-30 11:26 ` [PATCH 2/2] io: fix use after free in websocket handshake code Marc-André Lureau
2025-09-30 11:30 ` Daniel P. Berrangé
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20250930110834.2551757-3-berrange@redhat.com \
--to=berrange@redhat.com \
--cc=marcandre.lureau@gmail.com \
--cc=qemu-devel@nongnu.org \
--cc=rid@cylo.io \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.